public override async Task ProcessSigninResponse([NotNull] ProcessSigninResponseContext context) { var options = (OpenIddictOptions)context.Options; if (context.Request.IsTokenRequest() && (context.Request.IsAuthorizationCodeGrantType() || context.Request.IsRefreshTokenGrantType())) { // Note: when handling a grant_type=authorization_code or refresh_token request, // the OpenID Connect server middleware allows creating authentication tickets // that are completely disconnected from the original code or refresh token ticket. // This scenario is deliberately not supported in OpenIddict and all the tickets // must be linked. To ensure the properties are preserved from an authorization code // or a refresh token to the new ticket, they are manually restored if necessary. // Retrieve the original authentication ticket from the request properties. var ticket = context.Request.GetProperty <AuthenticationTicket>( OpenIddictConstants.Properties.AuthenticationTicket); Debug.Assert(ticket != null, "The authentication ticket shouldn't be null."); // If the properties instances of the two authentication tickets differ, // restore the missing properties in the new authentication ticket. if (!ReferenceEquals(ticket.Properties, context.Ticket.Properties)) { foreach (var property in ticket.Properties.Items) { // Don't override the properties that have been // manually set on the new authentication ticket. if (context.Ticket.HasProperty(property.Key)) { continue; } context.Ticket.AddProperty(property.Key, property.Value); } // 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 (context.Request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && !context.Ticket.HasScope()) { context.Ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId); } context.IncludeIdentityToken = context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId); } // Always include a refresh token for grant_type=refresh_token requests if // rolling tokens are enabled and if the offline_access scope was specified. context.IncludeRefreshToken = context.Request.IsRefreshTokenGrantType() && options.UseRollingTokens && context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess); // If token revocation was explicitly disabled, // none of the following security routines apply. if (options.DisableTokenRevocation) { return; } // If rolling tokens are enabled or if the request is a grant_type=authorization_code request, // mark the authorization code or the refresh token as redeemed to prevent future reuses. // See https://tools.ietf.org/html/rfc6749#section-6 for more information. if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType()) { if (!await TryRedeemTokenAsync(context.Ticket, context.HttpContext)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, description: "The specified authorization code is no longer valid."); return; } } // When rolling tokens are enabled, revoke all the previously issued tokens associated // with the authorization if the request is a grant_type=refresh_token request. if (options.UseRollingTokens && context.Request.IsRefreshTokenGrantType()) { if (!await TryRevokeTokensAsync(context.Ticket, context.HttpContext)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, description: "The specified refresh token is no longer valid."); return; } } // When rolling tokens are disabled, extend the expiration date // of the existing token instead of returning a new refresh token // with a new expiration date if sliding expiration was not disabled. else if (options.UseSlidingExpiration && context.Request.IsRefreshTokenGrantType()) { if (!await TryExtendTokenAsync(context.Ticket, context.HttpContext, options)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, description: "The specified refresh token is no longer valid."); return; } // Prevent the OpenID Connect server from returning a new refresh token. context.IncludeRefreshToken = false; } } // If no authorization was explicitly attached to the authentication ticket, // create an ad hoc authorization if an authorization code or a refresh token // is going to be returned to the client application as part of the response. if (!context.Ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId) && (context.IncludeAuthorizationCode || context.IncludeRefreshToken)) { await CreateAuthorizationAsync(context.Ticket, options, context.HttpContext, context.Request); } }
public override async Task ProcessSigninResponse([NotNull] ProcessSigninResponseContext context) { var options = (OpenIddictServerOptions)context.Options; Debug.Assert(context.Request.IsAuthorizationRequest() || context.Request.IsTokenRequest(), "The request should be an authorization or token request."); if (context.Request.IsTokenRequest() && (context.Request.IsAuthorizationCodeGrantType() || context.Request.IsRefreshTokenGrantType())) { // Note: when handling a grant_type=authorization_code or refresh_token request, // the OpenID Connect server middleware allows creating authentication tickets // that are completely disconnected from the original code or refresh token ticket. // This scenario is deliberately not supported in OpenIddict and all the tickets // must be linked. To ensure the properties are flowed from the authorization code // or the refresh token to the new ticket, they are manually restored if necessary. if (!context.Ticket.Properties.HasProperty(OpenIdConnectConstants.Properties.TokenId)) { // Retrieve the original authentication ticket from the request properties. var ticket = context.Request.GetProperty <AuthenticationTicket>( OpenIddictConstants.Properties.AuthenticationTicket); Debug.Assert(ticket != null, "The authentication ticket shouldn't be null."); foreach (var property in ticket.Properties.Items) { // Don't override the properties that have been // manually set on the new authentication ticket. if (context.Ticket.HasProperty(property.Key)) { continue; } context.Ticket.AddProperty(property.Key, property.Value); } // 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 (context.Request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && !context.Ticket.HasScope()) { context.Ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId); } context.IncludeIdentityToken = context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId); } context.IncludeRefreshToken = context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess); // Always include a refresh token for grant_type=refresh_token requests if // rolling tokens are enabled and if the offline_access scope was specified. if (context.Request.IsRefreshTokenGrantType()) { context.IncludeRefreshToken &= options.UseRollingTokens; } // If token revocation was explicitly disabled, // none of the following security routines apply. if (options.DisableTokenRevocation) { await base.ProcessSigninResponse(context); return; } var token = context.Request.GetProperty($"{OpenIddictConstants.Properties.Token}:{context.Ticket.GetTokenId()}"); Debug.Assert(token != null, "The token shouldn't be null."); // If rolling tokens are enabled or if the request is a grant_type=authorization_code request, // mark the authorization code or the refresh token as redeemed to prevent future reuses. // If the operation fails, return an error indicating the code/token is no longer valid. // See https://tools.ietf.org/html/rfc6749#section-6 for more information. if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType()) { if (!await TryRedeemTokenAsync(token)) { context.Reject( error: OpenIdConnectConstants.Errors.InvalidGrant, description: context.Request.IsAuthorizationCodeGrantType() ? "The specified authorization code is no longer valid." : "The specified refresh token is no longer valid."); return; } } if (context.Request.IsRefreshTokenGrantType()) { // When rolling tokens are enabled, try to revoke all the previously issued tokens // associated with the authorization if the request is a refresh_token request. // If the operation fails, silently ignore the error and keep processing the request: // this may indicate that one of the revoked tokens was modified by a concurrent request. if (options.UseRollingTokens) { await TryRevokeTokensAsync(context.Ticket); } // When rolling tokens are disabled, try to extend the expiration date // of the existing token instead of returning a new refresh token // with a new expiration date if sliding expiration was not disabled. // If the operation fails, silently ignore the error and keep processing // the request: this may indicate that a concurrent refresh token request // already updated the expiration date associated with the refresh token. if (!options.UseRollingTokens && options.UseSlidingExpiration) { await TryExtendTokenAsync(token, context.Ticket, options); } } } // If no authorization was explicitly attached to the authentication ticket, // create an ad hoc authorization if an authorization code or a refresh token // is going to be returned to the client application as part of the response. if (!context.Ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId) && (context.IncludeAuthorizationCode || context.IncludeRefreshToken)) { await CreateAuthorizationAsync(context.Ticket, options, context.Request); } // Add the custom properties that are marked as public as authorization or // token response properties and remove them from the authentication ticket // so they are not persisted in the authorization code/access/refresh token. // Note: make sure the foreach statement iterates on a copy of the ticket // as the property collection is modified when the property is removed. var parameters = GetParameters(context.Request, context.Ticket.Properties); foreach (var(property, parameter, value) in parameters.ToArray()) { context.Response.AddParameter(parameter, value); context.Ticket.RemoveProperty(property); } await base.ProcessSigninResponse(context); }
public override async Task ProcessSigninResponse([NotNull] ProcessSigninResponseContext context) { var options = (KeystoneServerOptions)context.Options; Debug.Assert(context.Request.IsAuthorizationRequest() || context.Request.IsTokenRequest(), "The request should be an authorization or token request."); // While null/unauthenticated identities can be validly represented and are allowed by // the OpenID Connect server handler, this most likely indicates that the developer // has not correctly set the authentication type associated with the claims identity, // which may later cause issues when validating opaque access tokens, as the resulting // principal would be considered unauthenticated by the ASP.NET Core authorization stack. if (context.Ticket.Principal.Identity == null || !context.Ticket.Principal.Identity.IsAuthenticated) { throw new InvalidOperationException(new StringBuilder() .AppendLine("The specified principal doesn't contain a valid or authenticated identity.") .Append("Make sure that both 'ClaimsPrincipal.Identity' and 'ClaimsPrincipal.Identity.AuthenticationType' ") .Append("are not null and that 'ClaimsPrincipal.Identity.IsAuthenticated' returns 'true'.") .ToString()); } if (context.Request.IsTokenRequest() && (context.Request.IsAuthorizationCodeGrantType() || context.Request.IsRefreshTokenGrantType())) { // Note: when handling a grant_type=authorization_code or refresh_token request, // the OpenID Connect server middleware allows creating authentication tickets // that are completely disconnected from the original code or refresh token ticket. // This scenario is deliberately not supported in Keystone and all the tickets // must be linked. To ensure the properties are flowed from the authorization code // or the refresh token to the new ticket, they are manually restored if necessary. if (string.IsNullOrEmpty(context.Ticket.GetInternalTokenId())) { // Retrieve the original authentication ticket from the request properties. var ticket = context.Request.GetProperty <AuthenticationTicket>( KeystoneConstants.Properties.AuthenticationTicket); Debug.Assert(ticket != null, "The authentication ticket shouldn't be null."); foreach (var property in ticket.Properties.Items) { // Don't override the properties that have been // manually set on the new authentication ticket. if (context.Ticket.HasProperty(property.Key)) { continue; } context.Ticket.AddProperty(property.Key, property.Value); } // 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 (context.Request.HasScope(KeystoneConstants.Scopes.OpenId) && !context.Ticket.HasScope()) { context.Ticket.SetScopes(KeystoneConstants.Scopes.OpenId); } context.IncludeIdentityToken = context.Ticket.HasScope(KeystoneConstants.Scopes.OpenId); } context.IncludeRefreshToken = context.Ticket.HasScope(KeystoneConstants.Scopes.OfflineAccess); // Always include a refresh token for grant_type=refresh_token requests if // rolling tokens are enabled and if the offline_access scope was specified. if (context.Request.IsRefreshTokenGrantType()) { context.IncludeRefreshToken &= options.UseRollingTokens; } // If token revocation was explicitly disabled, none of the following security routines apply. if (!options.DisableTokenStorage) { var token = await _tokenManager.FindByIdAsync(context.Ticket.GetInternalTokenId()); if (token == null) { context.Reject( error: KeystoneConstants.Errors.InvalidGrant, description: context.Request.IsAuthorizationCodeGrantType() ? "The specified authorization code is no longer valid." : "The specified refresh token is no longer valid."); return; } // If rolling tokens are enabled or if the request is a grant_type=authorization_code request, // mark the authorization code or the refresh token as redeemed to prevent future reuses. // If the operation fails, return an error indicating the code/token is no longer valid. // See https://tools.ietf.org/html/rfc6749#section-6 for more information. if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType()) { if (!await TryRedeemTokenAsync(token)) { context.Reject( error: KeystoneConstants.Errors.InvalidGrant, description: context.Request.IsAuthorizationCodeGrantType() ? "The specified authorization code is no longer valid." : "The specified refresh token is no longer valid."); return; } } if (context.Request.IsRefreshTokenGrantType()) { // When rolling tokens are enabled, try to revoke all the previously issued tokens // associated with the authorization if the request is a refresh_token request. // If the operation fails, silently ignore the error and keep processing the request: // this may indicate that one of the revoked tokens was modified by a concurrent request. if (options.UseRollingTokens) { await TryRevokeTokensAsync(context.Ticket); } // When rolling tokens are disabled, try to extend the expiration date // of the existing token instead of returning a new refresh token // with a new expiration date if sliding expiration was not disabled. // If the operation fails, silently ignore the error and keep processing // the request: this may indicate that a concurrent refresh token request // already updated the expiration date associated with the refresh token. if (!options.UseRollingTokens && options.UseSlidingExpiration) { await TryExtendRefreshTokenAsync(token, context.Ticket, options); } } } } // If no authorization was explicitly attached to the authentication ticket, // create an ad hoc authorization if an authorization code or a refresh token // is going to be returned to the client application as part of the response. if (!options.DisableAuthorizationStorage && string.IsNullOrEmpty(context.Ticket.GetInternalAuthorizationId()) && (context.IncludeAuthorizationCode || context.IncludeRefreshToken)) { await CreateAuthorizationAsync(context.Ticket, options, context.Request); } // Add the custom properties that are marked as public as authorization or // token response properties and remove them from the authentication ticket // so they are not persisted in the authorization code/access/refresh token. // Note: make sure the foreach statement iterates on a copy of the ticket // as the property collection is modified when the property is removed. var parameters = GetParameters(context.Request, context.Ticket.Properties); foreach (var(property, parameter, value) in parameters.ToList()) { context.Response.AddParameter(parameter, value); context.Ticket.RemoveProperty(property); } await _eventDispatcher.DispatchAsync(new KeystoneServerEvents.ProcessSigninResponse(context)); }