/// <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 AuthorizationEndpointResponse(AuthorizationEndpointResponseContext context) => OnAuthorizationEndpointResponse(context);
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 (Response.HasStarted) { Logger.LogCritical( "OpenIdConnectServerHandler.TeardownCoreAsync cannot be called when " + "the response headers have already been sent back to the user agent. " + "Make sure the response body has not been altered and that no middleware " + "has attempted to write to the response stream during this request."); return; } if (!context.Principal.HasClaim(claim => claim.Type == ClaimTypes.NameIdentifier)) { Logger.LogError("The returned identity doesn't contain the mandatory ClaimTypes.NameIdentifier claim."); await SendNativeErrorPageAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "The mandatory ClaimTypes.NameIdentifier claim was not found." }); return; } // redirect_uri is added to the response message since it's not a mandatory parameter // in OAuth 2.0 and can be set or replaced from the ValidateClientRedirectUri event. 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; } // Note: the application is allowed to specify a different "scope" // parameter when calling AuthenticationManager.SignInAsync: in this case, // don't replace the "scope" property stored in the authentication ticket. if (!context.Properties.ContainsKey(OpenIdConnectConstants.Properties.Scopes) && request.HasScope(OpenIdConnectConstants.Scopes.OpenId)) { // Always include the "openid" scope when the developer didn't explicitly call SetScopes. context.Properties[OpenIdConnectConstants.Properties.Scopes] = OpenIdConnectConstants.Scopes.OpenId; } // 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)) { Logger.LogError("SerializeAuthorizationCodeAsync returned no authorization code"); await SendNativeErrorPageAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "no valid authorization code was issued" }); return; } } // 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(); string resources; if (!properties.Items.TryGetValue(OpenIdConnectConstants.Properties.Resources, out resources)) { Logger.LogInformation("No explicit resource has been associated with the authentication ticket: " + "the access token will thus be issued without any audience attached."); } // Note: when the "resource" parameter added to the OpenID Connect response // is identical to the request parameter, setting it is not necessary. if (!string.IsNullOrEmpty(request.Resource) && !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, setting it is not necessary. string scopes; properties.Items.TryGetValue(OpenIdConnectConstants.Properties.Scopes, out scopes); if (!string.IsNullOrEmpty(request.Scope) && !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)) { Logger.LogError("SerializeAccessTokenAsync returned no access token."); await SendNativeErrorPageAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "no valid access token was issued" }); return; } // 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)) { Logger.LogError("SerializeIdentityTokenAsync returned no identity token."); await SendNativeErrorPageAsync(new OpenIdConnectMessage { Error = OpenIdConnectConstants.Errors.ServerError, ErrorDescription = "no valid identity token was issued", }); return; } } // Remove the OpenID Connect request from the distributed cache. var identifier = request.GetUniqueIdentifier(); if (!string.IsNullOrEmpty(identifier)) { await Options.Cache.RemoveAsync(identifier); } var ticket = new AuthenticationTicket(context.Principal, new AuthenticationProperties(context.Properties), context.AuthenticationScheme); var notification = new AuthorizationEndpointResponseContext(Context, Options, ticket, request, response); await Options.Provider.AuthorizationEndpointResponse(notification); if (notification.HandledResponse) { return; } await ApplyAuthorizationResponseAsync(request, response); }