/// <summary> /// Represents an event called when processing a sign-in response. /// </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 ProcessSigninResponse(ProcessSigninResponseContext context) => OnProcessSigninResponse(context);
private async Task <bool> HandleSignInAsync(AuthenticationTicket ticket) { // Extract the OpenID Connect request from the OWIN context. // If it cannot be found or doesn't correspond to an authorization // or a token request, throw an InvalidOperationException. var request = Context.GetOpenIdConnectRequest(); if (request == null || (!request.IsAuthorizationRequest() && !request.IsTokenRequest())) { throw new InvalidOperationException("An authorization or token response cannot be returned from this endpoint."); } // Note: if a response was already generated, throw an exception. var response = Context.GetOpenIdConnectResponse(); if (response != null) { throw new InvalidOperationException("A response has already been sent."); } if (string.IsNullOrEmpty(ticket.Identity.GetClaim(OpenIdConnectConstants.Claims.Subject))) { throw new InvalidOperationException("The authentication ticket was rejected because " + "the mandatory subject claim was missing."); } Logger.LogTrace("A sign-in operation was triggered: {Claims} ; {Properties}.", ticket.Identity.Claims, ticket.Properties.Dictionary); // Prepare a new OpenID Connect response. response = new OpenIdConnectResponse(); // Copy the confidentiality level associated with the request to the authentication ticket. if (!ticket.HasProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel)) { ticket.SetConfidentialityLevel(request.GetProperty <string>(OpenIdConnectConstants.Properties.ConfidentialityLevel)); } // Always include the "openid" scope when the developer doesn't explicitly call SetScopes. // Note: the application is allowed to specify a different "scopes": in this case, // don't replace the "scopes" property stored in the authentication ticket. if (request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && !ticket.HasScope()) { ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId); } // When a "resources" property cannot be found in the ticket, // infer it from the "audiences" property. if (ticket.HasAudience() && !ticket.HasResource()) { ticket.SetResources(ticket.GetAudiences()); } // Add the validated client_id to the list of authorized presenters, // unless the presenters were explicitly set by the developer. var presenter = request.GetProperty <string>(OpenIdConnectConstants.Properties.ValidatedClientId); if (!string.IsNullOrEmpty(presenter) && !ticket.HasPresenter()) { ticket.SetPresenters(presenter); } var notification = new ProcessSigninResponseContext(Context, Options, ticket, request, response); if (request.IsAuthorizationRequest()) { // By default, return an authorization code if a response type containing code was specified. notification.IncludeAuthorizationCode = request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Code); // By default, return an access token if a response type containing token was specified. notification.IncludeAccessToken = request.HasResponseType(OpenIdConnectConstants.ResponseTypes.Token); // By default, prevent a refresh token from being returned as the OAuth2 specification // explicitly disallows returning a refresh token from the authorization endpoint. // See https://tools.ietf.org/html/rfc6749#section-4.2.2 for more information. notification.IncludeRefreshToken = false; // By default, return an identity token if a response type containing code // was specified and if the openid scope was explicitly or implicitly granted. notification.IncludeIdentityToken = request.HasResponseType(OpenIdConnectConstants.ResponseTypes.IdToken) && ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId); } else { // By default, prevent an authorization code from being returned as this type of token // cannot be issued from the token endpoint in the standard OAuth2/OpenID Connect flows. notification.IncludeAuthorizationCode = false; // By default, always return an access token. notification.IncludeAccessToken = true; // By default, only return a refresh token is the offline_access scope was granted and if // sliding expiration is disabled or if the request is not a grant_type=refresh_token request. notification.IncludeRefreshToken = ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess) && (Options.UseSlidingExpiration || !request.IsRefreshTokenGrantType()); // By default, only return an identity token if the openid scope was granted. notification.IncludeIdentityToken = ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId); } await Options.Provider.ProcessSigninResponse(notification); if (notification.HandledResponse) { Logger.LogDebug("The sign-in response was handled in user code."); return(true); } else if (notification.Skipped) { Logger.LogDebug("The default sign-in handling was skipped from user code."); return(false); } else if (notification.IsRejected) { Logger.LogError("The request was rejected with the following error: {Error} ; {Description}", /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, /* Description: */ notification.ErrorDescription); if (request.IsAuthorizationRequest()) { return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidRequest, ErrorDescription = notification.ErrorDescription, ErrorUri = notification.ErrorUri })); } // Flow the changes made to the ticket. ticket = notification.Ticket; // Ensure an authentication ticket has been provided or return // an error code indicating that the request was rejected. if (ticket == null) { Logger.LogError("The request was rejected because no authentication ticket was provided."); if (request.IsAuthorizationRequest()) { return(await SendAuthorizationResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.AccessDenied, ErrorDescription = "The authorization was denied by the resource owner." })); } return(await SendTokenResponseAsync(new OpenIdConnectResponse { Error = OpenIdConnectConstants.Errors.InvalidGrant, ErrorDescription = "The token request was rejected by the authorization server." })); } if (notification.IncludeAuthorizationCode) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.Code = await SerializeAuthorizationCodeAsync(ticket.Identity, properties, request, response); } if (notification.IncludeAccessToken) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); // When receiving a grant_type=refresh_token request, determine whether the client application // requests a limited set of scopes and replace the corresponding properties if necessary. if (!string.IsNullOrEmpty(request.Scope) && request.IsTokenRequest() && request.IsRefreshTokenGrantType()) { Logger.LogDebug("The access token scopes will be limited to the scopes requested " + "by the client application: {Scopes}.", request.GetScopes()); // Replace the scopes initially granted by the scopes listed by the client // application in the token request. Note: request.GetScopes() automatically // removes duplicate entries, so additional filtering is not necessary. properties.SetProperty(OpenIdConnectConstants.Properties.Scopes, new JArray(request.GetScopes()).ToString(Formatting.None)); } var scopes = ticket.GetScopes(); if ((request.IsTokenRequest() && request.IsAuthorizationCodeGrantType()) || !new HashSet <string>(scopes).SetEquals(request.GetScopes())) { response.Scope = string.Join(" ", scopes); } response.TokenType = OpenIdConnectConstants.TokenTypes.Bearer; response.AccessToken = await SerializeAccessTokenAsync(ticket.Identity, properties, request, response); // 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; response.ExpiresIn = (long)(lifetime.TotalSeconds + .5); } } if (notification.IncludeRefreshToken) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.RefreshToken = await SerializeRefreshTokenAsync(ticket.Identity, properties, request, response); } if (notification.IncludeIdentityToken) { // Make sure to create a copy of the authentication properties // to avoid modifying the properties set on the original ticket. var properties = ticket.Properties.Copy(); response.IdToken = await SerializeIdentityTokenAsync(ticket.Identity, properties, request, response); } if (request.IsAuthorizationRequest()) { return(await SendAuthorizationResponseAsync(response, ticket)); } return(await SendTokenResponseAsync(response, ticket)); }