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; } // Note: a dictionary is deliberately not used here to allow multiple parameters with the // same name to be specified. While initially not allowed by the core OAuth2 specification, // this is now accepted by derived drafts like the OAuth2 token exchange specification. // For consistency, multiple parameters with the same name are also supported by this endpoint. var parameters = new List <KeyValuePair <string, string> >(); foreach (var parameter in response.GetParameters()) { var values = (string[])parameter.Value; if (values == null) { continue; } foreach (var value in values) { parameters.Add(new KeyValuePair <string, string>(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 = notification.RedirectUri; foreach (var parameter in parameters) { location = QueryHelpers.AddQueryString(location, parameter.Key, parameter.Value); } 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> /// Represents an event called before the authorization response is returned to the caller. /// </summary> /// <param name="context">The context instance associated with this event.</param> /// <returns>A <see cref="Task"/> that can be used to monitor the asynchronous operation.</returns> public virtual Task ApplyAuthorizationResponse(ApplyAuthorizationResponseContext context) => OnApplyAuthorizationResponse(context);