/// <summary> /// OAuth 2.0 validation /// </summary> /// <returns></returns> private JwtSecurityToken ValidateOAuth20() { // OAuth 2 Specs for Platform Originating Messages // https://www.imsglobal.org/spec/security/v1p0/#platform-originating-messages // Get the JWT Token from the id_token form field. // We could detect the LTI version by checking which OAuth Form Fields are present. // Presence of id_token indicates OAuth 2. Once the token is unpacked, we can check the LTI Version Claim for the exact LTI version string ltiLaunchJwtToken = _formData["id_token"]; if (ltiLaunchJwtToken == null) { return(null); } JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); TokenValidationParameters validationParameters = new TokenValidationParameters { // OAuth 2.0 Required Validations (https://www.imsglobal.org/spec/security/v1p0/#message-security-and-message-signing) // 5.1.3 Authentication Response Validation // 5.1.3.1 IssuerSigningKeys = OutOfBandData.GetPlatformSigningKeys(), ValidateIssuerSigningKey = true, RequireSignedTokens = true, // 5.1.3.2 ValidIssuer = OutOfBandData.PlatforIssuerId, ValidateIssuer = true, // 5.1.3.3 ValidAudience = OutOfBandData.ToolClientId, ValidateAudience = true, // 5.1.3.4, 5.1.3.5 related to multiple audiences - skip for now // 5.1.3.7, 5.1.3.8 ValidateLifetime = true, RequireExpirationTime = true, // 5.1.3.9 TokenReplayCache = _replayCache, ValidateTokenReplay = true, ClockSkew = new TimeSpan(0, 0, 15) }; // Validate token. ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(ltiLaunchJwtToken, validationParameters, out SecurityToken validatedToken); // Set the ClaimsPrincipal on the current thread. Thread.CurrentPrincipal = claimsPrincipal; // Set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment. if (HttpContext.Current != null) { HttpContext.Current.User = claimsPrincipal; } // If the token is scoped, verify that required permission is set in the scope claim. // See https://www.imsglobal.org/spec/security/v1p0/#access-token-management // 7.1 Access Token Management if (ClaimsPrincipal.Current.FindFirst(scopeClaimType) != null && ClaimsPrincipal.Current.FindFirst(scopeClaimType).Value != "user_impersonation") { return(null); } // now we have validated token and can use the claims containing usable LTI Launch parameters var launchToken = (JwtSecurityToken)validatedToken; return(launchToken); }
protected void Page_Load(object sender, EventArgs e) { // Initial page load - redirect to the IMS reference platform (acting as the LMS) set up on the IMS website if (!Request.HttpMethod.Equals("POST", StringComparison.InvariantCultureIgnoreCase)) { Response.Redirect("https://lti-ri.imsglobal.org/platforms/44/resource_links", true); return; } // OAuth 2 Specs for Platform Originating Messages // https://www.imsglobal.org/spec/security/v1p0/#platform-originating-messages // Get the JWT Token from the id_token form field. // We could detect the LTI version by checking which OAuth Form Fields are present. // Presence of id_token indicates OAuth 2. Once the token is unpacked, we can check the LTI Version Claim for the exact LTI version string ltiLaunchJwtToken = Request.Form["id_token"]; if (ltiLaunchJwtToken == null) { CompleteRequest(HttpStatusCode.Forbidden, "JWT token was not provided in id_token form field"); return; } JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); TokenValidationParameters validationParameters = new TokenValidationParameters { // OAuth 2.0 Required Validations (https://www.imsglobal.org/spec/security/v1p0/#message-security-and-message-signing) // 5.1.3 Authentication Response Validation // 5.1.3.1 IssuerSigningKeys = OutOfBandData.GetPlatformSigningKeys(), ValidateIssuerSigningKey = true, RequireSignedTokens = true, // 5.1.3.2 ValidIssuer = OutOfBandData.PlatforIssuerId, ValidateIssuer = true, // 5.1.3.3 ValidAudience = OutOfBandData.ToolClientId, ValidateAudience = true, // 5.1.3.4, 5.1.3.5 related to multiple audiences - skip for now // 5.1.3.7, 5.1.3.8 ValidateLifetime = true, RequireExpirationTime = true, // 5.1.3.9 TokenReplayCache = _replayCache, ValidateTokenReplay = true, ClockSkew = new TimeSpan(0, 0, 15) }; try { // Validate token. ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(ltiLaunchJwtToken, validationParameters, out SecurityToken validatedToken); // Set the ClaimsPrincipal on the current thread. Thread.CurrentPrincipal = claimsPrincipal; // Set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment. if (HttpContext.Current != null) { HttpContext.Current.User = claimsPrincipal; } // If the token is scoped, verify that required permission is set in the scope claim. // See https://www.imsglobal.org/spec/security/v1p0/#access-token-management // 7.1 Access Token Management if (ClaimsPrincipal.Current.FindFirst(scopeClaimType) != null && ClaimsPrincipal.Current.FindFirst(scopeClaimType).Value != "user_impersonation") { CompleteRequest(HttpStatusCode.Forbidden, "Invalid scope"); return; } // now we have validated token and can use the claims containing usable LTI Launch parameters var launchToken = (JwtSecurityToken)validatedToken; // Could validate schema here... // LTI 1.3 Spec: https://www.imsglobal.org/spec/lti/v1p3 // COULD USE EXTENSION POINT ON ValidationParameters PERHAPS // 2.1.3 lti_deployment_id must be present if (!launchToken.Payload.ContainsKey("https://purl.imsglobal.org/spec/lti/claim/deployment_id")) { CompleteRequest(HttpStatusCode.Unauthorized, "Deployment id missing"); return; } // 4.3 Required Message Claims // 4.3.1 Message type claim string messageType = launchToken.Payload.ContainsKey("https://purl.imsglobal.org/spec/lti/claim/message_type") ? launchToken.Payload["https://purl.imsglobal.org/spec/lti/claim/message_type"].ToString() : ""; if (messageType != "LtiResourceLinkRequest") { CompleteRequest(HttpStatusCode.BadRequest, "message_type claim missing or invalid"); return; } // there are additional validations... OutputTokenInfo(launchToken.Payload); } catch (SecurityTokenValidationException stve) { CompleteRequest(HttpStatusCode.Unauthorized, stve.Message); return; } catch (Exception ex2) { CompleteRequest(HttpStatusCode.InternalServerError, ex2.Message); return; } CompleteRequest(HttpStatusCode.OK, ""); }