diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs index 0781c1aa..4348dfcd 100644 --- a/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs +++ b/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs @@ -29,14 +29,32 @@ namespace OpenIddict.Server } /// - /// Gets or sets the request. + /// Gets or sets the request, or null if it is not available. /// - public OpenIddictRequest Request + public OpenIddictRequest? Request { - get => Transaction.Request!; + get => Transaction.Request; set => Transaction.Request = value; } + /// + /// Gets or sets the client identifier of the application + /// the resulting token will be issued to, if applicable. + /// + public string? ClientId { get; set; } + + /// + /// Gets or sets a boolean indicating whether a token entry + /// should be created to persist token metadata in a database. + /// + public bool CreateTokenEntry { get; set; } + + /// + /// Gets or sets a boolean indicating whether the token payload + /// should be persisted alongside the token metadata in the database. + /// + public bool PersistTokenPayload { get; set; } + /// /// Gets or sets the security principal used to create the token. /// @@ -82,14 +100,19 @@ namespace OpenIddict.Server } /// - /// Gets or sets the request. + /// Gets or sets the request, or null if it is not available. /// - public OpenIddictRequest Request + public OpenIddictRequest? Request { - get => Transaction.Request!; + get => Transaction.Request; set => Transaction.Request = value; } + /// + /// Gets or sets a boolean indicating whether lifetime validation is disabled. + /// + public bool DisableLifetimeValidation { get; set; } + /// /// Gets or sets the security token handler used to validate the token. /// diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs index 326448df..e1b4935a 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs @@ -72,7 +72,12 @@ namespace OpenIddict.Server /* * Token request handling: */ - AttachPrincipal.Descriptor); + AttachPrincipal.Descriptor, + + /* + * Token response handling: + */ + NormalizeErrorResponse.Descriptor); /// /// Contains the logic responsible of extracting token requests and invoking the corresponding event handlers. @@ -1716,6 +1721,56 @@ namespace OpenIddict.Server return default; } } + + /// + /// Contains the logic responsible of converting token errors to standard invalid_grant responses. + /// + public class NormalizeErrorResponse : IOpenIddictServerHandler + { + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .UseSingletonHandler() + .SetOrder(int.MinValue + 100_000) + .SetType(OpenIddictServerHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ApplyTokenResponseContext context) + { + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (string.IsNullOrEmpty(context.Error)) + { + return default; + } + + // If the error indicates an invalid token caused by an invalid authorization, + // device code or refresh token, return a standard invalid_grant. + + if (context.Request is null || !(context.Request.IsAuthorizationCodeGrantType() || + context.Request.IsDeviceCodeGrantType() || + context.Request.IsRefreshTokenGrantType())) + { + return default; + } + + + context.Response.Error = context.Error switch + { + Errors.InvalidToken or Errors.ExpiredToken => Errors.InvalidGrant, + + _ => context.Error // Otherwise, keep the error as-is. + }; + + return default; + } + } } } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs index 65dbff23..31267684 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs @@ -184,29 +184,25 @@ namespace OpenIddict.Server !await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray())) { context.Reject( - error: context.EndpointType switch + error: Errors.InvalidToken, + description: context.ValidTokenTypes.Count switch { - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - _ => Errors.InvalidToken - }, - description: context.EndpointType switch - { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) => SR.GetResourceString(SR.ID2001), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) => SR.GetResourceString(SR.ID2002), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) => SR.GetResourceString(SR.ID2003), _ => SR.GetResourceString(SR.ID2004) }, - uri: context.EndpointType switch + uri: context.ValidTokenTypes.Count switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) => SR.FormatID8000(SR.ID2001), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) => SR.FormatID8000(SR.ID2002), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) => SR.FormatID8000(SR.ID2003), _ => SR.FormatID8000(SR.ID2004), @@ -305,25 +301,22 @@ namespace OpenIddict.Server context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token); context.Reject( - error: context.EndpointType switch - { - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - _ => Errors.InvalidToken - }, + error: Errors.InvalidToken, description: result.Exception switch { - SecurityTokenInvalidTypeException => context.EndpointType switch + SecurityTokenInvalidTypeException => context.ValidTokenTypes.Count switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) => SR.GetResourceString(SR.ID2005), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) => SR.GetResourceString(SR.ID2006), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) => SR.GetResourceString(SR.ID2007), - OpenIddictServerEndpointType.Userinfo => SR.GetResourceString(SR.ID2008), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) + => SR.GetResourceString(SR.ID2008), _ => SR.GetResourceString(SR.ID2089) }, @@ -336,18 +329,19 @@ namespace OpenIddict.Server }, uri: result.Exception switch { - SecurityTokenInvalidTypeException => context.EndpointType switch + SecurityTokenInvalidTypeException => context.ValidTokenTypes.Count switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) => SR.FormatID8000(SR.ID2005), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) => SR.FormatID8000(SR.ID2006), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) => SR.FormatID8000(SR.ID2007), - OpenIddictServerEndpointType.Userinfo => SR.FormatID8000(SR.ID2008), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) + => SR.FormatID8000(SR.ID2008), _ => SR.FormatID8000(SR.ID2089) }, @@ -629,36 +623,30 @@ namespace OpenIddict.Server if (context.Principal is null) { context.Reject( - error: context.EndpointType switch + error: Errors.InvalidToken, + description: context.ValidTokenTypes.Count switch { - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - _ => Errors.InvalidToken - }, - description: context.EndpointType switch - { - OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout - => SR.GetResourceString(SR.ID2009), - - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) => SR.GetResourceString(SR.ID2001), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) => SR.GetResourceString(SR.ID2002), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) => SR.GetResourceString(SR.ID2003), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.IdToken) + => SR.GetResourceString(SR.ID2009), _ => SR.GetResourceString(SR.ID2004) }, - uri: context.EndpointType switch + uri: context.ValidTokenTypes.Count switch { - OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout - => SR.FormatID8000(SR.ID2009), - - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.AuthorizationCode) => SR.FormatID8000(SR.ID2001), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.DeviceCode) => SR.FormatID8000(SR.ID2002), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.RefreshToken) => SR.FormatID8000(SR.ID2003), + 1 when context.ValidTokenTypes.Contains(TokenTypeHints.IdToken) + => SR.FormatID8000(SR.ID2009), _ => SR.FormatID8000(SR.ID2004) }); @@ -735,30 +723,20 @@ namespace OpenIddict.Server if (token is null) { context.Reject( - error: context.EndpointType switch + error: Errors.InvalidToken, + description: context.Principal.GetTokenType() switch { - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - _ => Errors.InvalidToken - }, - description: context.EndpointType switch - { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.GetResourceString(SR.ID2001), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.GetResourceString(SR.ID2002), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.GetResourceString(SR.ID2003), + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2001), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2002), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2003), _ => SR.GetResourceString(SR.ID2004) }, - uri: context.EndpointType switch + uri: context.Principal.GetTokenType() switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.FormatID8000(SR.ID2001), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.FormatID8000(SR.ID2002), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.FormatID8000(SR.ID2003), + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2001), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2002), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2003), _ => SR.FormatID8000(SR.ID2004) }); @@ -766,88 +744,69 @@ namespace OpenIddict.Server return; } - if (context.EndpointType == OpenIddictServerEndpointType.Token && (context.Request.IsAuthorizationCodeGrantType() || - context.Request.IsDeviceCodeGrantType() || - context.Request.IsRefreshTokenGrantType())) + // If the token is already marked as redeemed, this may indicate that it was compromised. + // In this case, revoke the entire chain of tokens associated with the authorization. + // Special logic is used to avoid revoking refresh tokens already marked as redeemed to allow for a small leeway. + // Note: the authorization itself is not revoked to allow the legitimate client to start a new flow. + // See https://tools.ietf.org/html/rfc6749#section-10.5 for more information. + if (await _tokenManager.HasStatusAsync(token, Statuses.Redeemed)) { - // If the authorization code/device code/refresh token is already marked as redeemed, this may indicate - // that it was compromised. In this case, revoke the entire chain of tokens associated with the authorization. - // Special logic is used to avoid revoking refresh tokens already marked as redeemed to allow for a small leeway. - // Note: the authorization itself is not revoked to allow the legitimate client to start a new flow. - // See https://tools.ietf.org/html/rfc6749#section-10.5 for more information. - if (await _tokenManager.HasStatusAsync(token, Statuses.Redeemed)) + if (!context.Principal.HasTokenType(TokenTypeHints.RefreshToken) || !await IsReusableAsync(token)) { - if (!context.Request.IsRefreshTokenGrantType() || !await IsReusableAsync(token)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), identifier); - - context.Reject( - error: context.EndpointType switch - { - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - - _ => Errors.InvalidToken - }, - description: context.EndpointType switch - { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.GetResourceString(SR.ID2010), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.GetResourceString(SR.ID2011), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.GetResourceString(SR.ID2012), - - _ => SR.GetResourceString(SR.ID2013) - }, - uri: context.EndpointType switch - { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.FormatID8000(SR.ID2010), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.FormatID8000(SR.ID2011), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.FormatID8000(SR.ID2012), - - _ => SR.FormatID8000(SR.ID2013) - }); - - // Revoke all the token entries associated with the authorization. - await TryRevokeChainAsync(await _tokenManager.GetAuthorizationIdAsync(token)); - - return; - } + context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), identifier); + + context.Reject( + error: Errors.InvalidToken, + description: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2010), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2011), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2012), + + _ => SR.GetResourceString(SR.ID2013) + }, + uri: context.Principal.GetTokenType() switch + { + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2010), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2011), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2012), + + _ => SR.FormatID8000(SR.ID2013) + }); + + // Revoke all the token entries associated with the authorization. + await TryRevokeChainAsync(await _tokenManager.GetAuthorizationIdAsync(token)); return; } - if (context.Request.IsDeviceCodeGrantType()) - { - // If the device code is not marked as valid yet, return an authorization_pending error. - if (await _tokenManager.HasStatusAsync(token, Statuses.Inactive)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6003), identifier); + return; + } - context.Reject( - error: Errors.AuthorizationPending, - description: SR.GetResourceString(SR.ID2014), - uri: SR.FormatID8000(SR.ID2014)); + // If the token is not marked as valid yet, return an authorization_pending error. + if (await _tokenManager.HasStatusAsync(token, Statuses.Inactive)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6003), identifier); - return; - } + context.Reject( + error: Errors.AuthorizationPending, + description: SR.GetResourceString(SR.ID2014), + uri: SR.FormatID8000(SR.ID2014)); - // If the device code is marked as rejected, return an access_denied error. - if (await _tokenManager.HasStatusAsync(token, Statuses.Rejected)) - { - context.Logger.LogInformation(SR.GetResourceString(SR.ID6004), identifier); + return; + } - context.Reject( - error: Errors.AccessDenied, - description: SR.GetResourceString(SR.ID2015), - uri: SR.FormatID8000(SR.ID2015)); + // If the token is marked as rejected, return an access_denied error. + if (await _tokenManager.HasStatusAsync(token, Statuses.Rejected)) + { + context.Logger.LogInformation(SR.GetResourceString(SR.ID6004), identifier); - return; - } - } + context.Reject( + error: Errors.AccessDenied, + description: SR.GetResourceString(SR.ID2015), + uri: SR.FormatID8000(SR.ID2015)); + + return; } if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid)) @@ -855,30 +814,20 @@ namespace OpenIddict.Server context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); context.Reject( - error: context.EndpointType switch - { - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - _ => Errors.InvalidToken - }, - description: context.EndpointType switch + error: Errors.InvalidToken, + description: context.Principal.GetTokenType() switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.GetResourceString(SR.ID2016), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.GetResourceString(SR.ID2017), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.GetResourceString(SR.ID2018), + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2016), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2017), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2018), _ => SR.GetResourceString(SR.ID2019) }, - uri: context.EndpointType switch + uri: context.Principal.GetTokenType() switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.FormatID8000(SR.ID2016), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.FormatID8000(SR.ID2017), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.FormatID8000(SR.ID2018), + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2016), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2017), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2018), _ => SR.FormatID8000(SR.ID2019) }); @@ -975,30 +924,20 @@ namespace OpenIddict.Server context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier); context.Reject( - error: context.EndpointType switch - { - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - _ => Errors.InvalidToken - }, - description: context.EndpointType switch + error: Errors.InvalidToken, + description: context.Principal.GetTokenType() switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.GetResourceString(SR.ID2020), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.GetResourceString(SR.ID2021), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.GetResourceString(SR.ID2022), + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2020), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2021), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2022), _ => SR.GetResourceString(SR.ID2023) }, - uri: context.EndpointType switch + uri: context.Principal.GetTokenType() switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.FormatID8000(SR.ID2020), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.FormatID8000(SR.ID2021), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.FormatID8000(SR.ID2022), + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2020), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2021), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2022), _ => SR.FormatID8000(SR.ID2023) }); @@ -1033,9 +972,7 @@ namespace OpenIddict.Server Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); - // Don't validate the lifetime of id_tokens used as id_token_hints. - if (context.ValidTokenTypes.Count is 1 && context.ValidTokenTypes.ElementAt(0) is TokenTypeHints.IdToken && - context.EndpointType is OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout) + if (context.DisableLifetimeValidation) { return default; } @@ -1044,34 +981,24 @@ namespace OpenIddict.Server if (date.HasValue && date.Value < DateTimeOffset.UtcNow) { context.Reject( - error: context.EndpointType switch + error: context.Principal.GetTokenType() switch { - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => Errors.ExpiredToken, - - OpenIddictServerEndpointType.Token => Errors.InvalidGrant, - - _ => Errors.InvalidToken + TokenTypeHints.DeviceCode => Errors.ExpiredToken, + _ => Errors.InvalidToken }, - description: context.EndpointType switch + description: context.Principal.GetTokenType() switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.GetResourceString(SR.ID2016), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.GetResourceString(SR.ID2017), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.GetResourceString(SR.ID2018), + TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2016), + TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2017), + TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2018), _ => SR.GetResourceString(SR.ID2019) }, - uri: context.EndpointType switch + uri: context.Principal.GetTokenType() switch { - OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() - => SR.FormatID8000(SR.ID2016), - OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() - => SR.FormatID8000(SR.ID2017), - OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() - => SR.FormatID8000(SR.ID2018), + TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2016), + TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2017), + TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2018), _ => SR.FormatID8000(SR.ID2019) }); @@ -1170,6 +1097,11 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } + if (!context.CreateTokenEntry) + { + return; + } + var descriptor = new OpenIddictTokenDescriptor { AuthorizationId = context.Principal.GetAuthorizationId(), @@ -1201,9 +1133,9 @@ namespace OpenIddict.Server }; // If the client application is known, associate it with the token. - if (!string.IsNullOrEmpty(context.Request.ClientId)) + if (!string.IsNullOrEmpty(context.ClientId)) { - var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); + var application = await _applicationManager.FindByClientIdAsync(context.ClientId); if (application is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0017)); @@ -1386,25 +1318,7 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - if (!(context.TokenType switch - { - // Access and refresh tokens can be converted to reference tokens - // if the corresponding option was enabled in the server options. - TokenTypeHints.AccessToken => context.Options.UseReferenceAccessTokens, - TokenTypeHints.RefreshToken => context.Options.UseReferenceRefreshTokens, - - // By default, authorization/user codes are always converted to reference tokens. - TokenTypeHints.AuthorizationCode or TokenTypeHints.UserCode => true, - - // Device codes are only converted to reference tokens if they are not generated - // as part of a device code swap made by the user code verification endpoint. - TokenTypeHints.DeviceCode => context.EndpointType is not OpenIddictServerEndpointType.Verification, - - // Identity tokens cannot be converted to reference tokens. - TokenTypeHints.IdToken => false, - - _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) - })) + if (!context.PersistTokenPayload) { return; } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 6b425d35..2b9fa38a 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -695,6 +695,9 @@ namespace OpenIddict.Server var notification = new ValidateTokenContext(context.Transaction) { + // Don't validate the lifetime of id_tokens used as id_token_hints. + DisableLifetimeValidation = context.EndpointType is OpenIddictServerEndpointType.Authorization or + OpenIddictServerEndpointType.Logout, Token = context.IdentityToken, ValidTokenTypes = { TokenTypeHints.IdToken } }; @@ -2218,6 +2221,11 @@ namespace OpenIddict.Server var notification = new GenerateTokenContext(context.Transaction) { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + // Access tokens can be converted to reference tokens if the + // corresponding option was enabled in the server options. + PersistTokenPayload = context.Options.UseReferenceAccessTokens, Principal = context.AccessTokenPrincipal!, TokenType = TokenTypeHints.AccessToken }; @@ -2280,6 +2288,9 @@ namespace OpenIddict.Server var notification = new GenerateTokenContext(context.Transaction) { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + PersistTokenPayload = !context.Options.DisableTokenStorage, Principal = context.AuthorizationCodePrincipal!, TokenType = TokenTypeHints.AuthorizationCode }; @@ -2342,6 +2353,16 @@ namespace OpenIddict.Server var notification = new GenerateTokenContext(context.Transaction) { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + // Device codes can be converted to reference tokens if they are not generated + // as part of a device code swap made by the user code verification endpoint. + PersistTokenPayload = context.EndpointType switch + { + OpenIddictServerEndpointType.Verification => false, + + _ => !context.Options.DisableTokenStorage + }, Principal = context.DeviceCodePrincipal!, TokenType = TokenTypeHints.DeviceCode }; @@ -2404,6 +2425,11 @@ namespace OpenIddict.Server var notification = new GenerateTokenContext(context.Transaction) { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + // Refresh tokens can be converted to reference tokens if the + // corresponding option was enabled in the server options. + PersistTokenPayload = context.Options.UseReferenceRefreshTokens, Principal = context.RefreshTokenPrincipal!, TokenType = TokenTypeHints.RefreshToken }; @@ -2711,6 +2737,9 @@ namespace OpenIddict.Server var notification = new GenerateTokenContext(context.Transaction) { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + PersistTokenPayload = !context.Options.DisableTokenStorage, Principal = context.UserCodePrincipal!, TokenType = TokenTypeHints.UserCode }; @@ -2773,6 +2802,10 @@ namespace OpenIddict.Server var notification = new GenerateTokenContext(context.Transaction) { + ClientId = context.ClientId, + CreateTokenEntry = !context.Options.DisableTokenStorage, + // Identity tokens cannot never be reference tokens. + PersistTokenPayload = false, Principal = context.IdentityTokenPrincipal!, TokenType = TokenTypeHints.IdToken }; diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs index b0d82312..bdc56f97 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs @@ -3436,6 +3436,9 @@ namespace OpenIddict.Server.IntegrationTests mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.AuthorizationCode); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) .ReturnsAsync(false); @@ -3535,6 +3538,9 @@ namespace OpenIddict.Server.IntegrationTests mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.AuthorizationCode); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) .ReturnsAsync(false); @@ -3618,6 +3624,9 @@ namespace OpenIddict.Server.IntegrationTests mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.RefreshToken); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) .ReturnsAsync(false); @@ -3703,6 +3712,9 @@ namespace OpenIddict.Server.IntegrationTests mock.Setup(manager => manager.GetIdAsync(token, It.IsAny())) .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); + mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny())) + .ReturnsAsync(TokenTypeHints.RefreshToken); + mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny())) .ReturnsAsync(false); @@ -3775,18 +3787,13 @@ namespace OpenIddict.Server.IntegrationTests builder.UseInlineHandler(context => { context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) - .SetTokenType(context.Request.IsAuthorizationCodeGrantType() ? + .SetTokenType(context.Request!.IsAuthorizationCodeGrantType() ? TokenTypeHints.AuthorizationCode : TokenTypeHints.RefreshToken) .SetPresenters("Fabrikam") .SetTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC") .SetClaim(Claims.Subject, "Bob le Bricoleur"); - if (context.Request.IsAuthorizationCodeGrantType()) - { - context.Principal.SetPresenters("Fabrikam"); - } - return default; }); diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs index 1c0cebf4..48fd0628 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs @@ -221,8 +221,8 @@ namespace OpenIddict.Server.IntegrationTests // Assert Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2019), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2019), response.ErrorUri); + Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); } [Theory] diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs index 95560956..00b3c483 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs @@ -705,8 +705,8 @@ namespace OpenIddict.Server.IntegrationTests // Assert Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri); + Assert.Equal(SR.GetResourceString(SR.ID2003), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2003), response.ErrorUri); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny(), It.IsAny()), Times.Never()); @@ -766,8 +766,8 @@ namespace OpenIddict.Server.IntegrationTests // Assert Assert.Equal(Errors.InvalidToken, response.Error); - Assert.Equal(SR.GetResourceString(SR.ID2019), response.ErrorDescription); - Assert.Equal(SR.FormatID8000(SR.ID2019), response.ErrorUri); + Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription); + Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri); Mock.Get(manager).Verify(manager => manager.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny()), Times.AtLeastOnce()); Mock.Get(manager).Verify(manager => manager.TryRevokeAsync(It.IsAny(), It.IsAny()), Times.Never());