protected override async Task <bool> HandleForbiddenAsync(ChallengeContext context) { // Stop processing the request if no OpenID Connect // message has been found in the current context. var request = Context.GetOpenIdConnectRequest(); if (request == null) { return(false); } var response = new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.AccessDenied, ErrorDescription = "The authorization grant has been denied by the resource owner", RedirectUri = request.RedirectUri, State = request.State }; // Create a new ticket containing an empty identity and // the authentication properties extracted from the challenge. var ticket = new AuthenticationTicket( new ClaimsPrincipal(), new AuthenticationProperties(context.Properties), context.AuthenticationScheme); var notification = new ApplyAuthorizationResponseContext(Context, Options, ticket, request, response); await Options.Provider.ApplyAuthorizationResponse(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } return(await SendErrorRedirectAsync(request, response)); }
private async Task <bool> SendAuthorizationResponseAsync(OpenIdConnectResponse response, AuthenticationTicket ticket = null) { var request = Context.GetOpenIdConnectRequest(); Context.SetOpenIdConnectResponse(response); response.SetProperty(OpenIdConnectConstants.Properties.MessageType, OpenIdConnectConstants.MessageTypes.AuthorizationResponse); // Note: as this stage, the request may be null (e.g if it couldn't be extracted from the HTTP request). var notification = new ApplyAuthorizationResponseContext(Context, Scheme, Options, ticket, request, response) { RedirectUri = request?.GetProperty <string>(OpenIdConnectConstants.Properties.ValidatedRedirectUri), ResponseMode = request?.ResponseMode }; // If the response_mode parameter was not specified, try to infer it. if (string.IsNullOrEmpty(notification.ResponseMode) && !string.IsNullOrEmpty(notification.RedirectUri)) { notification.ResponseMode = request.IsFormPostResponseMode() ? OpenIdConnectConstants.ResponseModes.FormPost : request.IsFragmentResponseMode() ? OpenIdConnectConstants.ResponseModes.Fragment : request.IsQueryResponseMode() ? OpenIdConnectConstants.ResponseModes.Query : null; } await Provider.ApplyAuthorizationResponse(notification); if (notification.Result != null) { if (notification.Result.Handled) { Logger.LogDebug("The authorization request was handled in user code."); return(true); } else if (notification.Result.Skipped) { Logger.LogDebug("The default authorization request handling was skipped from user code."); return(false); } } // Directly display an error page if redirect_uri cannot be used to // redirect the user agent back to the client application. if (!string.IsNullOrEmpty(response.Error) && string.IsNullOrEmpty(notification.RedirectUri)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { // Return false to allow the rest of // the pipeline to handle the request. return(false); } Logger.LogInformation("The authorization response was successfully returned " + "as a plain-text document: {Response}.", response); return(await SendNativePageAsync(response)); } // At this stage, throw an exception if the request was not properly extracted. if (request == null) { throw new InvalidOperationException("The authorization response cannot be returned."); } // Attach the request state to the authorization response. if (string.IsNullOrEmpty(response.State)) { response.State = request.State; } // Create a new parameters dictionary holding the name/value pairs. var parameters = new Dictionary <string, string>(); foreach (var parameter in response.GetParameters()) { // Ignore null or empty parameters, including JSON // objects that can't be represented as strings. var value = (string)parameter.Value; if (string.IsNullOrEmpty(value)) { continue; } parameters.Add(parameter.Key, value); } // Note: at this stage, the redirect_uri parameter MUST be trusted. switch (notification.ResponseMode) { case OpenIdConnectConstants.ResponseModes.FormPost: { Logger.LogInformation("The authorization response was successfully returned to " + "'{RedirectUri}' using the form post response mode: {Response}.", notification.RedirectUri, response); using (var buffer = new MemoryStream()) using (var writer = new StreamWriter(buffer)) { writer.WriteLine("<!doctype html>"); writer.WriteLine("<html>"); writer.WriteLine("<body>"); // While the redirect_uri parameter should be guarded against unknown values // by OpenIdConnectServerProvider.ValidateAuthorizationRequest, // it's still safer to encode it to avoid cross-site scripting attacks // if the authorization server has a relaxed policy concerning redirect URIs. writer.WriteLine($@"<form name=""form"" method=""post"" action=""{Options.HtmlEncoder.Encode(notification.RedirectUri)}"">"); foreach (var parameter in parameters) { var key = Options.HtmlEncoder.Encode(parameter.Key); var value = Options.HtmlEncoder.Encode(parameter.Value); writer.WriteLine($@"<input type=""hidden"" name=""{key}"" value=""{value}"" />"); } writer.WriteLine(@"<noscript>Click here to finish the authorization process: <input type=""submit"" /></noscript>"); writer.WriteLine("</form>"); writer.WriteLine("<script>document.form.submit();</script>"); writer.WriteLine("</body>"); writer.WriteLine("</html>"); writer.Flush(); Response.StatusCode = 200; Response.ContentLength = buffer.Length; Response.ContentType = "text/html;charset=UTF-8"; Response.Headers["Cache-Control"] = "no-cache"; Response.Headers["Pragma"] = "no-cache"; Response.Headers["Expires"] = "-1"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted); return(true); } } case OpenIdConnectConstants.ResponseModes.Fragment: { Logger.LogInformation("The authorization response was successfully returned to " + "'{RedirectUri}' using the fragment response mode: {Response}.", notification.RedirectUri, response); var location = notification.RedirectUri; var appender = new OpenIdConnectServerHelpers.Appender(location, '#'); foreach (var parameter in parameters) { appender.Append(parameter.Key, parameter.Value); } Response.Redirect(appender.ToString()); return(true); } case OpenIdConnectConstants.ResponseModes.Query: { Logger.LogInformation("The authorization response was successfully returned to " + "'{RedirectUri}' using the query response mode: {Response}.", notification.RedirectUri, response); var location = QueryHelpers.AddQueryString(notification.RedirectUri, parameters); Response.Redirect(location); return(true); } default: { Logger.LogError("The authorization request was rejected because the 'response_mode' " + "parameter was invalid: {ResponseMode}.", request.ResponseMode); return(await SendNativePageAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "The specified 'response_mode' parameter is not supported." })); } } }
/// <summary> /// Called before the AuthorizationEndpoint redirects its response to the caller. /// The response could contain an access token when using implicit flow or /// an authorization code when using the authorization code flow. /// If the web application wishes to produce the authorization response directly in the AuthorizationEndpoint call it may write to the /// context.Response directly and should call context.RequestCompleted to stop other handlers from executing. /// This call may also be used to add additional response parameters to the authorization response. /// </summary> /// <param name="context">The context of the event carries information in and results out.</param> /// <returns>Task to enable asynchronous execution</returns> public virtual Task ApplyAuthorizationResponse(ApplyAuthorizationResponseContext context) => OnApplyAuthorizationResponse(context);
private async Task <bool> SendAuthorizationResponseAsync(OpenIdConnectResponse response, AuthenticationTicket ticket = null) { var request = Context.GetOpenIdConnectRequest(); if (request == null) { request = new OpenIdConnectRequest(); } Context.SetOpenIdConnectResponse(response); var notification = new ApplyAuthorizationResponseContext(Context, Options, ticket, request, response); await Options.Provider.ApplyAuthorizationResponse(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } if (!string.IsNullOrEmpty(response.Error)) { // Directly display an error page if redirect_uri cannot be used to // redirect the user agent back to the client application. if (string.IsNullOrEmpty(response.RedirectUri)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { // Return false to allow the rest of // the pipeline to handle the request. return(false); } return(await SendNativePageAsync(response)); } } // Create a new parameters dictionary holding the name/value pairs. var parameters = new Dictionary <string, string>(); foreach (var parameter in response.GetParameters()) { // Don't include redirect_uri in the parameters dictionary. if (string.Equals(parameter.Key, OpenIdConnectConstants.Parameters.RedirectUri, StringComparison.Ordinal)) { continue; } var value = parameter.Value as JValue; if (value == null) { Logger.LogWarning("A parameter whose type was incompatible was ignored and excluded " + "from the authorization response: '{Parameter}'.", parameter.Key); continue; } parameters.Add(parameter.Key, (string)value); } // Note: at this stage, the redirect_uri parameter MUST be trusted. if (request.IsFormPostResponseMode()) { using (var buffer = new MemoryStream()) using (var writer = new StreamWriter(buffer)) { writer.WriteLine("<!doctype html>"); writer.WriteLine("<html>"); writer.WriteLine("<body>"); // While the redirect_uri parameter should be guarded against unknown values // by OpenIdConnectServerProvider.ValidateAuthorizationRequest, // it's still safer to encode it to avoid cross-site scripting attacks // if the authorization server has a relaxed policy concerning redirect URIs. writer.WriteLine($"<form name='form' method='post' action='{Options.HtmlEncoder.Encode(response.RedirectUri)}'>"); foreach (var parameter in parameters) { var key = Options.HtmlEncoder.Encode(parameter.Key); var value = Options.HtmlEncoder.Encode(parameter.Value); writer.WriteLine($"<input type='hidden' name='{key}' value='{value}' />"); } writer.WriteLine("<noscript>Click here to finish the authorization process: <input type='submit' /></noscript>"); writer.WriteLine("</form>"); writer.WriteLine("<script>document.form.submit();</script>"); writer.WriteLine("</body>"); writer.WriteLine("</html>"); writer.Flush(); Response.StatusCode = 200; Response.ContentLength = buffer.Length; Response.ContentType = "text/html;charset=UTF-8"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted); return(true); } } else if (request.IsFragmentResponseMode()) { var location = response.RedirectUri; var appender = new Appender(location, '#'); foreach (var parameter in parameters) { appender.Append(parameter.Key, parameter.Value); } Response.Redirect(appender.ToString()); return(true); } else if (request.IsQueryResponseMode()) { var location = QueryHelpers.AddQueryString(response.RedirectUri, parameters); Response.Redirect(location); return(true); } Logger.LogError("The authorization request was rejected because the 'response_mode' " + "parameter was invalid: {ResponseMode}.", request.ResponseMode); return(await SendNativePageAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = "response_mode unsupported" })); }
private async Task <bool> SendAuthorizationResponseAsync( OpenIdConnectMessage request, OpenIdConnectMessage response, AuthenticationTicket ticket = null) { if (request == null) { request = new OpenIdConnectMessage(); } var notification = new ApplyAuthorizationResponseContext(Context, Options, ticket, request, response); await Options.Provider.ApplyAuthorizationResponse(notification); if (notification.HandledResponse) { return(true); } else if (notification.Skipped) { return(false); } if (!string.IsNullOrEmpty(response.Error)) { // When returning an error, remove the authorization request from the ASP.NET context // to inform the application code that there's nothing more to handle. Context.SetOpenIdConnectRequest(request: null); // Directly display an error page if redirect_uri cannot be used to // redirect the user agent back to the client application. if (string.IsNullOrEmpty(response.RedirectUri)) { // Apply a 400 status code by default. Response.StatusCode = 400; if (Options.ApplicationCanDisplayErrors) { Context.SetOpenIdConnectResponse(response); // Return false to allow the rest of // the pipeline to handle the request. return(false); } return(await SendNativePageAsync(response)); } } // Note: at this stage, the redirect_uri parameter MUST be trusted. if (request.IsFormPostResponseMode()) { using (var buffer = new MemoryStream()) using (var writer = new StreamWriter(buffer)) { writer.WriteLine("<!doctype html>"); writer.WriteLine("<html>"); writer.WriteLine("<body>"); // While the redirect_uri parameter should be guarded against unknown values // by IOpenIdConnectServerProvider.ValidateAuthorizationRequest, // it's still safer to encode it to avoid cross-site scripting attacks // if the authorization server has a relaxed policy concerning redirect URIs. writer.WriteLine($"<form name='form' method='post' action='{Options.HtmlEncoder.Encode(response.RedirectUri)}'>"); foreach (var parameter in response.Parameters) { // Don't include redirect_uri in the form. if (string.Equals(parameter.Key, OpenIdConnectParameterNames.RedirectUri, StringComparison.Ordinal)) { continue; } var key = Options.HtmlEncoder.Encode(parameter.Key); var value = Options.HtmlEncoder.Encode(parameter.Value); writer.WriteLine($"<input type='hidden' name='{key}' value='{value}' />"); } writer.WriteLine("<noscript>Click here to finish the authorization process: <input type='submit' /></noscript>"); writer.WriteLine("</form>"); writer.WriteLine("<script>document.form.submit();</script>"); writer.WriteLine("</body>"); writer.WriteLine("</html>"); writer.Flush(); Response.StatusCode = 200; Response.ContentLength = buffer.Length; Response.ContentType = "text/html;charset=UTF-8"; buffer.Seek(offset: 0, loc: SeekOrigin.Begin); await buffer.CopyToAsync(Response.Body, 4096, Context.RequestAborted); return(true); } } else if (request.IsFragmentResponseMode()) { var location = response.RedirectUri; var appender = new Appender(location, '#'); foreach (var parameter in response.Parameters) { // Don't include redirect_uri in the fragment. if (string.Equals(parameter.Key, OpenIdConnectParameterNames.RedirectUri, StringComparison.Ordinal)) { continue; } appender.Append(parameter.Key, parameter.Value); } Response.Redirect(appender.ToString()); return(true); } else if (request.IsQueryResponseMode()) { var location = response.RedirectUri; foreach (var parameter in response.Parameters) { // Don't include redirect_uri in the query string. if (string.Equals(parameter.Key, OpenIdConnectParameterNames.RedirectUri, StringComparison.Ordinal)) { continue; } location = QueryHelpers.AddQueryString(location, parameter.Key, parameter.Value); } Response.Redirect(location); return(true); } return(await SendNativePageAsync(response)); }
protected override async Task HandleSignInAsync(SignInContext context) { // request may be null when no authorization request has been received // or has been already handled by InvokeAuthorizationEndpointAsync. var request = Context.GetOpenIdConnectRequest(); if (request == null) { return; } // Stop processing the request if there's no response grant that matches // the authentication type associated with this middleware instance // or if the response status code doesn't indicate a successful response. if (context == null || Response.StatusCode != 200) { return; } if (!context.Principal.HasClaim(claim => claim.Type == ClaimTypes.NameIdentifier)) { throw new InvalidOperationException("The authentication ticket was rejected because it didn't " + "contain the mandatory ClaimTypes.NameIdentifier claim."); } var response = new OpenIdConnectMessage { RedirectUri = request.RedirectUri, State = request.State }; if (!string.IsNullOrEmpty(request.Nonce)) { // Keep the original nonce parameter for later comparison. context.Properties[OpenIdConnectConstants.Properties.Nonce] = request.Nonce; } if (!string.IsNullOrEmpty(request.RedirectUri)) { // Keep the original redirect_uri parameter for later comparison. context.Properties[OpenIdConnectConstants.Properties.RedirectUri] = request.RedirectUri; } // Always include the "openid" scope when the developer doesn't explicitly call SetScopes. // Note: the application is allowed to specify a different "scopes" // parameter when calling AuthenticationManager.SignInAsync: in this case, // don't replace the "scopes" property stored in the authentication ticket. if (!context.Properties.ContainsKey(OpenIdConnectConstants.Properties.Scopes) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId)) { context.Properties[OpenIdConnectConstants.Properties.Scopes] = OpenIdConnectConstants.Scopes.OpenId; } string audiences; // When a "resources" property cannot be found in the authentication properties, infer it from the "audiences" property. if (!context.Properties.ContainsKey(OpenIdConnectConstants.Properties.Resources) && context.Properties.TryGetValue(OpenIdConnectConstants.Properties.Audiences, out audiences)) { context.Properties[OpenIdConnectConstants.Properties.Resources] = audiences; } // Determine whether an authorization code should be returned // and invoke SerializeAuthorizationCodeAsync if necessary. if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code)) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = new AuthenticationProperties(context.Properties).Copy(); // properties.IssuedUtc and properties.ExpiresUtc are always // explicitly set to null to avoid aligning the expiration date // of the authorization code with the lifetime of the other tokens. properties.IssuedUtc = properties.ExpiresUtc = null; response.Code = await SerializeAuthorizationCodeAsync(context.Principal, properties, request, response); // Ensure that an authorization code is issued to avoid returning an invalid response. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations if (string.IsNullOrEmpty(response.Code)) { throw new InvalidOperationException("An error occurred during the serialization of the " + "authorization code and a null value was returned."); } } // Determine whether an access token should be returned // and invoke SerializeAccessTokenAsync if necessary. if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token)) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = new AuthenticationProperties(context.Properties).Copy(); // Note: when the "resource" parameter added to the OpenID Connect response // is identical to the request parameter, returning it is not necessary. var resources = properties.GetProperty(OpenIdConnectConstants.Properties.Resources); if (!string.IsNullOrEmpty(request.Resource) && !string.IsNullOrEmpty(resources) && !string.Equals(request.Resource, resources, StringComparison.Ordinal)) { response.Resource = resources; } // Note: when the "scope" parameter added to the OpenID Connect response // is identical to the request parameter, returning it is not necessary. var scopes = properties.GetProperty(OpenIdConnectConstants.Properties.Scopes); if (!string.IsNullOrEmpty(request.Scope) && !string.IsNullOrEmpty(scopes) && !string.Equals(request.Scope, scopes, StringComparison.Ordinal)) { response.Scope = scopes; } response.TokenType = OpenIdConnectConstants.TokenTypes.Bearer; response.AccessToken = await SerializeAccessTokenAsync(context.Principal, properties, request, response); // Ensure that an access token is issued to avoid returning an invalid response. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations if (string.IsNullOrEmpty(response.AccessToken)) { throw new InvalidOperationException("An error occurred during the serialization of the " + "access token and a null value was returned."); } // properties.ExpiresUtc is automatically set by SerializeAccessTokenAsync but the end user // is free to set a null value directly in the SerializeAccessToken event. if (properties.ExpiresUtc.HasValue && properties.ExpiresUtc > Options.SystemClock.UtcNow) { var lifetime = properties.ExpiresUtc.Value - Options.SystemClock.UtcNow; var expiration = (long)(lifetime.TotalSeconds + .5); response.ExpiresIn = expiration.ToString(CultureInfo.InvariantCulture); } } // Determine whether an identity token should be returned // and invoke SerializeIdentityTokenAsync if necessary. // Note: the identity token MUST be created after the authorization code // and the access token to create appropriate at_hash and c_hash claims. if (request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken)) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = new AuthenticationProperties(context.Properties).Copy(); response.IdToken = await SerializeIdentityTokenAsync(context.Principal, properties, request, response); // Ensure that an identity token is issued to avoid returning an invalid response. // See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations if (string.IsNullOrEmpty(response.IdToken)) { throw new InvalidOperationException("An error occurred during the serialization of the " + "identity token and a null value was returned."); } } // Remove the OpenID Connect request from the distributed cache. var identifier = request.GetRequestId(); if (!string.IsNullOrEmpty(identifier)) { await Options.Cache.RemoveAsync($"asos-request:{identifier}"); } var ticket = new AuthenticationTicket(context.Principal, new AuthenticationProperties(context.Properties), context.AuthenticationScheme); var notification = new ApplyAuthorizationResponseContext(Context, Options, ticket, request, response); await Options.Provider.ApplyAuthorizationResponse(notification); if (notification.HandledResponse) { return; } else if (notification.Skipped) { return; } await SendAuthorizationResponseAsync(request, response); }