Browse Source

Unify the token entry properties restoration logic and fix an issue affecting device codes and ASP.NET Core Data Protection

pull/1621/head
Kévin Chalet 3 years ago
parent
commit
21ccfc5e9b
  1. 19
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  2. 10
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs
  3. 3
      src/OpenIddict.Client/OpenIddictClientBuilder.cs
  4. 16
      src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs
  5. 2
      src/OpenIddict.Client/OpenIddictClientExtensions.cs
  6. 32
      src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
  7. 111
      src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
  8. 3
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  9. 3
      src/OpenIddict.Client/OpenIddictClientOptions.cs
  10. 26
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs
  11. 16
      src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs
  12. 2
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  13. 32
      src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
  14. 203
      src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
  15. 23
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  16. 6
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs
  17. 10
      src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs
  18. 2
      src/OpenIddict.Validation/OpenIddictValidationExtensions.cs
  19. 32
      src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs
  20. 84
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
  21. 48
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
  22. 9
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

19
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -1939,6 +1939,12 @@ Consider registering a certificate using 'services.AddOpenIddict().AddClient().A
<data name="ID4016" xml:space="preserve"> <data name="ID4016" xml:space="preserve">
<value>The number of written bytes ({0}) doesn't match the expected value ({1}).</value> <value>The number of written bytes ({0}) doesn't match the expected value ({1}).</value>
</data> </data>
<data name="ID4017" xml:space="preserve">
<value>The token identifier shouldn't be null or empty at this point.</value>
</data>
<data name="ID4018" xml:space="preserve">
<value>The authorization identifier shouldn't be null or empty at this point.</value>
</data>
<data name="ID6000" xml:space="preserve"> <data name="ID6000" xml:space="preserve">
<value>An error occurred while validating the token '{Token}'.</value> <value>An error occurred while validating the token '{Token}'.</value>
</data> </data>
@ -1979,14 +1985,21 @@ Consider registering a certificate using 'services.AddOpenIddict().AddClient().A
<value>The token entry for '{Type}' token '{Identifier}' was successfully created.</value> <value>The token entry for '{Type}' token '{Identifier}' was successfully created.</value>
</data> </data>
<data name="ID6013" xml:space="preserve"> <data name="ID6013" xml:space="preserve">
<value>A new '{Type}' token was successfully created: {Payload}. <value>A new '{Type}' JSON Web Token was successfully created: {Payload}.
The principal used to create the token contained the following claims: {Claims}.</value> The principal used to create the token contained the following claims: {Claims}.</value>
</data> </data>
<data name="ID6014" xml:space="preserve"> <data name="ID6014" xml:space="preserve">
<value>The token entry for '{Type}' token '{Identifier}' was successfully converted to a reference token with the identifier '{ReferenceId}'.</value> <value>The token payload ({Payload}) was successfully attached to the token entry '{Identifier}' of type '{Type}'.</value>
</data>
<data name="ID6015" xml:space="preserve">
<value>The reference identifier ({ReferenceId}) was successfully attached to the token entry '{Identifier}' of type '{Type}'.</value>
</data>
<data name="ID6016" xml:space="preserve">
<value>A new '{Type}' ASP.NET Core Data Protection token was successfully created: {Payload}.
The principal used to create the token contained the following claims: {Claims}.</value>
</data> </data>
<data name="ID6021" xml:space="preserve"> <data name="ID6021" xml:space="preserve">
<value>The reference token entry for device code '{Identifier}' was successfully updated'.</value> <value>The token entry for device code '{Identifier}' was successfully updated with the new payload.</value>
</data> </data>
<data name="ID6030" xml:space="preserve"> <data name="ID6030" xml:space="preserve">
<value>The authorization request was successfully extracted: {Request}.</value> <value>The authorization request was successfully extracted: {Request}.</value>

10
src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs

@ -104,11 +104,11 @@ public static partial class OpenIddictClientDataProtectionHandlers
// //
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector( var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
(type, context.TokenId) switch (type, context.IsReferenceToken) switch
{ {
(TokenTypeHints.StateToken, { Length: not 0 }) (TokenTypeHints.StateToken, true)
=> new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.StateToken, null or { Length: 0 }) (TokenTypeHints.StateToken, false)
=> new[] { Handlers.Client, Formats.StateToken, Schemes.Server }, => new[] { Handlers.Client, Formats.StateToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
@ -220,7 +220,7 @@ public static partial class OpenIddictClientDataProtectionHandlers
// //
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector( var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
(context.TokenType, context.PersistTokenPayload) switch (context.TokenType, context.IsReferenceToken) switch
{ {
(TokenTypeHints.StateToken, true) (TokenTypeHints.StateToken, true)
=> new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Client, Formats.StateToken, Features.ReferenceTokens, Schemes.Server },
@ -237,7 +237,7 @@ public static partial class OpenIddictClientDataProtectionHandlers
context.Token = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray())); context.Token = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray()));
context.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType, context.Logger.LogTrace(SR.GetResourceString(SR.ID6016), context.TokenType,
context.Token, context.Principal.Claims); context.Token, context.Principal.Claims);
return default; return default;

3
src/OpenIddict.Client/OpenIddictClientBuilder.cs

@ -881,8 +881,7 @@ public sealed class OpenIddictClientBuilder
/// <summary> /// <summary>
/// Disables token storage, so that no database entry is created /// Disables token storage, so that no database entry is created
/// for the tokens and codes returned by the OpenIddict client. /// for the tokens and codes returned by the OpenIddict client.
/// Using this option is generally NOT recommended as it prevents /// Using this option is generally NOT recommended.
/// the tokens from being revoked (if needed).
/// </summary> /// </summary>
/// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns> /// <returns>The <see cref="OpenIddictClientBuilder"/> instance.</returns>
public OpenIddictClientBuilder DisableTokenStorage() public OpenIddictClientBuilder DisableTokenStorage()

16
src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs

@ -40,6 +40,12 @@ public static partial class OpenIddictClientEvents
/// </summary> /// </summary>
public bool CreateTokenEntry { get; set; } public bool CreateTokenEntry { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a reference token should be used
/// and, if applicable, returned to the caller instead of the actual token payload.
/// </summary>
public bool IsReferenceToken { get; set; }
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether the token payload /// Gets or sets a boolean indicating whether the token payload
/// should be persisted alongside the token metadata in the database. /// should be persisted alongside the token metadata in the database.
@ -129,6 +135,16 @@ public static partial class OpenIddictClientEvents
/// </summary> /// </summary>
public string? TokenTypeHint { get; set; } = default!; public string? TokenTypeHint { get; set; } = default!;
/// <summary>
/// Gets or sets a boolean indicating whether the validated token is a reference token.
/// </summary>
public bool IsReferenceToken { get; set; }
/// <summary>
/// Gets or sets the authorization entry identifier associated with the token, if applicable.
/// </summary>
public string? AuthorizationId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the token entry identifier associated with the token, if applicable. /// Gets or sets the token entry identifier associated with the token, if applicable.
/// </summary> /// </summary>

2
src/OpenIddict.Client/OpenIddictClientExtensions.cs

@ -37,6 +37,7 @@ public static class OpenIddictClientExtensions
// Register the built-in filters used by the default OpenIddict client event handlers. // Register the built-in filters used by the default OpenIddict client event handlers.
builder.Services.TryAddSingleton<RequireAuthorizationCodeValidated>(); builder.Services.TryAddSingleton<RequireAuthorizationCodeValidated>();
builder.Services.TryAddSingleton<RequireAuthorizationIdResolved>();
builder.Services.TryAddSingleton<RequireBackchannelAccessTokenValidated>(); builder.Services.TryAddSingleton<RequireBackchannelAccessTokenValidated>();
builder.Services.TryAddSingleton<RequireBackchannelIdentityTokenValidated>(); builder.Services.TryAddSingleton<RequireBackchannelIdentityTokenValidated>();
builder.Services.TryAddSingleton<RequireBackchannelIdentityTokenPrincipal>(); builder.Services.TryAddSingleton<RequireBackchannelIdentityTokenPrincipal>();
@ -54,6 +55,7 @@ public static class OpenIddictClientExtensions
builder.Services.TryAddSingleton<RequireStateTokenPrincipal>(); builder.Services.TryAddSingleton<RequireStateTokenPrincipal>();
builder.Services.TryAddSingleton<RequireStateTokenValidated>(); builder.Services.TryAddSingleton<RequireStateTokenValidated>();
builder.Services.TryAddSingleton<RequireTokenEntryCreated>(); builder.Services.TryAddSingleton<RequireTokenEntryCreated>();
builder.Services.TryAddSingleton<RequireTokenIdResolved>();
builder.Services.TryAddSingleton<RequireTokenPayloadPersisted>(); builder.Services.TryAddSingleton<RequireTokenPayloadPersisted>();
builder.Services.TryAddSingleton<RequireTokenRequest>(); builder.Services.TryAddSingleton<RequireTokenRequest>();
builder.Services.TryAddSingleton<RequireTokenStorageEnabled>(); builder.Services.TryAddSingleton<RequireTokenStorageEnabled>();

32
src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs

@ -27,6 +27,22 @@ public static class OpenIddictClientHandlerFilters
} }
} }
/// <summary>
/// Represents a filter that excludes the associated handlers if no authorization identifier is resolved from the token.
/// </summary>
public sealed class RequireAuthorizationIdResolved : IOpenIddictClientHandlerFilter<ValidateTokenContext>
{
public ValueTask<bool> IsActiveAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!string.IsNullOrEmpty(context.AuthorizationId));
}
}
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if no backchannel access token is validated. /// Represents a filter that excludes the associated handlers if no backchannel access token is validated.
/// </summary> /// </summary>
@ -305,6 +321,22 @@ public static class OpenIddictClientHandlerFilters
} }
} }
/// <summary>
/// Represents a filter that excludes the associated handlers if no token identifier is resolved from the token.
/// </summary>
public sealed class RequireTokenIdResolved : IOpenIddictClientHandlerFilter<ValidateTokenContext>
{
public ValueTask<bool> IsActiveAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!string.IsNullOrEmpty(context.TokenId));
}
}
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if the token payload is not persisted in the database. /// Represents a filter that excludes the associated handlers if the token payload is not persisted in the database.
/// </summary> /// </summary>

111
src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs

@ -28,7 +28,7 @@ public static partial class OpenIddictClientHandlers
ValidateReferenceTokenIdentifier.Descriptor, ValidateReferenceTokenIdentifier.Descriptor,
ValidateIdentityModelToken.Descriptor, ValidateIdentityModelToken.Descriptor,
MapInternalClaims.Descriptor, MapInternalClaims.Descriptor,
RestoreReferenceTokenProperties.Descriptor, RestoreTokenEntryProperties.Descriptor,
ValidatePrincipal.Descriptor, ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor, ValidateExpirationDate.Descriptor,
ValidateTokenEntry.Descriptor, ValidateTokenEntry.Descriptor,
@ -39,7 +39,7 @@ public static partial class OpenIddictClientHandlers
AttachSecurityCredentials.Descriptor, AttachSecurityCredentials.Descriptor,
CreateTokenEntry.Descriptor, CreateTokenEntry.Descriptor,
GenerateIdentityModelToken.Descriptor, GenerateIdentityModelToken.Descriptor,
ConvertReferenceToken.Descriptor); AttachTokenPayload.Descriptor);
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the validation parameters used to validate tokens. /// Contains the logic responsible for resolving the validation parameters used to validate tokens.
@ -245,6 +245,7 @@ public static partial class OpenIddictClientHandlers
// Replace the token parameter by the payload resolved from the token entry // Replace the token parameter by the payload resolved from the token entry
// and store the identifier of the reference token so it can be later // and store the identifier of the reference token so it can be later
// used to restore the properties associated with the token. // used to restore the properties associated with the token.
context.IsReferenceToken = true;
context.Token = payload; context.Token = payload;
context.TokenId = await _tokenManager.GetIdAsync(token); context.TokenId = await _tokenManager.GetIdAsync(token);
} }
@ -432,16 +433,16 @@ public static partial class OpenIddictClientHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for restoring the properties associated with a reference token entry. /// Contains the logic responsible for restoring the properties associated with a token entry.
/// Note: this handler is not used when token storage is disabled. /// Note: this handler is not used when token storage is disabled.
/// </summary> /// </summary>
public sealed class RestoreReferenceTokenProperties : IOpenIddictClientHandler<ValidateTokenContext> public sealed class RestoreTokenEntryProperties : IOpenIddictClientHandler<ValidateTokenContext>
{ {
private readonly IOpenIddictTokenManager _tokenManager; private readonly IOpenIddictTokenManager _tokenManager;
public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0318)); public RestoreTokenEntryProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0318));
public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager) public RestoreTokenEntryProperties(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager)); => _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary> /// <summary>
@ -450,7 +451,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireTokenStorageEnabled>() .AddFilter<RequireTokenStorageEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>() .UseScopedHandler<RestoreTokenEntryProperties>()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000) .SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
.Build(); .Build();
@ -462,20 +463,40 @@ public static partial class OpenIddictClientHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (context.Principal is null || string.IsNullOrEmpty(context.TokenId)) if (context.Principal is null)
{ {
return; return;
} }
var token = await _tokenManager.FindByIdAsync(context.TokenId) ?? // Extract the token identifier from the authentication principal.
throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); //
// If no token identifier can be found, this indicates that the token
// has no backing database entry (e.g if token storage was disabled).
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
}
// If the token entry cannot be found, return a generic error.
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is null)
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2019),
uri: SR.FormatID8000(SR.ID2019));
return;
}
// Restore the creation/expiration dates/identifiers from the token entry metadata. // Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) context.Principal
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) .SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token)) .SetAuthorizationId(context.AuthorizationId = await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token)); .SetTokenId(context.TokenId = await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
} }
} }
@ -490,7 +511,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidatePrincipal>() .UseSingletonHandler<ValidatePrincipal>()
.SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000) .SetOrder(RestoreTokenEntryProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
.Build(); .Build();
@ -591,6 +612,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; } public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireTokenStorageEnabled>() .AddFilter<RequireTokenStorageEnabled>()
.AddFilter<RequireTokenIdResolved>()
.UseScopedHandler<ValidateTokenEntry>() .UseScopedHandler<ValidateTokenEntry>()
.SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000) .SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
@ -605,28 +627,14 @@ public static partial class OpenIddictClientHandlers
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
Debug.Assert(!string.IsNullOrEmpty(context.TokenId), SR.GetResourceString(SR.ID4017));
var identifier = context.Principal.GetTokenId(); var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
if (string.IsNullOrEmpty(identifier)) throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
{
return;
}
// If the token entry cannot be found, return a generic error.
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is null)
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2019),
uri: SR.FormatID8000(SR.ID2019));
return;
}
if (await _tokenManager.HasStatusAsync(token, Statuses.Redeemed)) if (await _tokenManager.HasStatusAsync(token, Statuses.Redeemed))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), context.TokenId);
context.Reject( context.Reject(
error: Errors.InvalidToken, error: Errors.InvalidToken,
@ -648,7 +656,7 @@ public static partial class OpenIddictClientHandlers
if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid)) if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), context.TokenId);
context.Reject( context.Reject(
error: Errors.InvalidToken, error: Errors.InvalidToken,
@ -657,13 +665,6 @@ public static partial class OpenIddictClientHandlers
return; return;
} }
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
} }
} }
@ -873,16 +874,16 @@ public static partial class OpenIddictClientHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for converting the token to a reference token. /// Contains the logic responsible for attaching the token payload to the token entry.
/// Note: this handler is not used when token storage is disabled. /// Note: this handler is not used when token storage is disabled.
/// </summary> /// </summary>
public sealed class ConvertReferenceToken : IOpenIddictClientHandler<GenerateTokenContext> public sealed class AttachTokenPayload : IOpenIddictClientHandler<GenerateTokenContext>
{ {
private readonly IOpenIddictTokenManager _tokenManager; private readonly IOpenIddictTokenManager _tokenManager;
public ConvertReferenceToken() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0318)); public AttachTokenPayload() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0318));
public ConvertReferenceToken(IOpenIddictTokenManager tokenManager) public AttachTokenPayload(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager)); => _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary> /// <summary>
@ -892,7 +893,7 @@ public static partial class OpenIddictClientHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.AddFilter<RequireTokenStorageEnabled>() .AddFilter<RequireTokenStorageEnabled>()
.AddFilter<RequireTokenPayloadPersisted>() .AddFilter<RequireTokenPayloadPersisted>()
.UseScopedHandler<ConvertReferenceToken>() .UseScopedHandler<AttachTokenPayload>()
.SetOrder(GenerateIdentityModelToken.Descriptor.Order + 1_000) .SetOrder(GenerateIdentityModelToken.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
.Build(); .Build();
@ -920,14 +921,22 @@ public static partial class OpenIddictClientHandlers
// Attach the generated token to the token entry. // Attach the generated token to the token entry.
descriptor.Payload = context.Token; descriptor.Payload = context.Token;
descriptor.Principal = context.Principal; descriptor.Principal = context.Principal;
descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
if (context.IsReferenceToken)
{
descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
}
await _tokenManager.UpdateAsync(token, descriptor); await _tokenManager.UpdateAsync(token, descriptor);
// Replace the returned token by the reference identifier. context.Logger.LogTrace(SR.GetResourceString(SR.ID6014), context.Token, identifier, context.TokenType);
context.Token = descriptor.ReferenceId;
context.Logger.LogTrace(SR.GetResourceString(SR.ID6014), context.TokenType, identifier, descriptor.ReferenceId); // Replace the returned token by the reference identifier, if applicable.
if (context.IsReferenceToken)
{
context.Token = descriptor.ReferenceId;
context.Logger.LogTrace(SR.GetResourceString(SR.ID6015), descriptor.ReferenceId, identifier, context.TokenType);
}
} }
} }
} }

3
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -2258,6 +2258,7 @@ public static partial class OpenIddictClientHandlers
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
CreateTokenEntry = false, CreateTokenEntry = false,
IsReferenceToken = false,
PersistTokenPayload = false, PersistTokenPayload = false,
Principal = context.ClientAssertionTokenPrincipal!, Principal = context.ClientAssertionTokenPrincipal!,
TokenFormat = TokenFormats.Jwt, TokenFormat = TokenFormats.Jwt,
@ -4624,6 +4625,7 @@ public static partial class OpenIddictClientHandlers
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
IsReferenceToken = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage, PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.StateTokenPrincipal!, Principal = context.StateTokenPrincipal!,
TokenFormat = TokenFormats.Jwt, TokenFormat = TokenFormats.Jwt,
@ -5215,6 +5217,7 @@ public static partial class OpenIddictClientHandlers
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
IsReferenceToken = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage, PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.StateTokenPrincipal!, Principal = context.StateTokenPrincipal!,
TokenFormat = TokenFormats.Jwt, TokenFormat = TokenFormats.Jwt,

3
src/OpenIddict.Client/OpenIddictClientOptions.cs

@ -120,8 +120,7 @@ public sealed class OpenIddictClientOptions
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether token storage should be disabled. /// Gets or sets a boolean indicating whether token storage should be disabled.
/// When disabled, no database entry is created for the tokens created by the /// When disabled, no database entry is created for the tokens created by the
/// OpenIddict client services. Using this option is generally NOT recommended /// OpenIddict client services. Using this option is generally NOT recommended.
/// as it prevents the tokens from being revoked (if needed).
/// </summary> /// </summary>
public bool DisableTokenStorage { get; set; } public bool DisableTokenStorage { get; set; }

26
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs

@ -189,31 +189,31 @@ public static partial class OpenIddictServerDataProtectionHandlers
// //
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector( var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
(type, context.TokenId) switch (type, context.IsReferenceToken) switch
{ {
(TokenTypeHints.AccessToken, { Length: not 0 }) (TokenTypeHints.AccessToken, true)
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.AccessToken, null or { Length: 0 }) (TokenTypeHints.AccessToken, false)
=> new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
(TokenTypeHints.AuthorizationCode, { Length: not 0 }) (TokenTypeHints.AuthorizationCode, true)
=> new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.AuthorizationCode, null or { Length: 0 }) (TokenTypeHints.AuthorizationCode, false)
=> new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server }, => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server },
(TokenTypeHints.DeviceCode, { Length: not 0 }) (TokenTypeHints.DeviceCode, true)
=> new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.DeviceCode, null or { Length: 0 }) (TokenTypeHints.DeviceCode, false)
=> new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server }, => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server },
(TokenTypeHints.RefreshToken, { Length: not 0 }) (TokenTypeHints.RefreshToken, true)
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.RefreshToken, null or { Length: 0 }) (TokenTypeHints.RefreshToken, false)
=> new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server }, => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server },
(TokenTypeHints.UserCode, { Length: not 0 }) (TokenTypeHints.UserCode, true)
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.UserCode, null or { Length: 0 }) (TokenTypeHints.UserCode, false)
=> new[] { Handlers.Server, Formats.UserCode, Schemes.Server }, => new[] { Handlers.Server, Formats.UserCode, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
@ -337,7 +337,7 @@ public static partial class OpenIddictServerDataProtectionHandlers
// //
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector( var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
(context.TokenType, context.PersistTokenPayload) switch (context.TokenType, context.IsReferenceToken) switch
{ {
(TokenTypeHints.AccessToken, true) (TokenTypeHints.AccessToken, true)
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
@ -374,7 +374,7 @@ public static partial class OpenIddictServerDataProtectionHandlers
context.Token = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray())); context.Token = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray()));
context.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType, context.Logger.LogTrace(SR.GetResourceString(SR.ID6016), context.TokenType,
context.Token, context.Principal.Claims); context.Token, context.Principal.Claims);
return default; return default;

16
src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs

@ -46,6 +46,12 @@ public static partial class OpenIddictServerEvents
/// </summary> /// </summary>
public bool CreateTokenEntry { get; set; } public bool CreateTokenEntry { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a reference token should be used
/// and, if applicable, returned to the caller instead of the actual token payload.
/// </summary>
public bool IsReferenceToken { get; set; }
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether the token payload /// Gets or sets a boolean indicating whether the token payload
/// should be persisted alongside the token metadata in the database. /// should be persisted alongside the token metadata in the database.
@ -135,6 +141,16 @@ public static partial class OpenIddictServerEvents
/// </summary> /// </summary>
public string? TokenTypeHint { get; set; } = default!; public string? TokenTypeHint { get; set; } = default!;
/// <summary>
/// Gets or sets a boolean indicating whether the validated token is a reference token.
/// </summary>
public bool IsReferenceToken { get; set; }
/// <summary>
/// Gets or sets the authorization entry identifier associated with the token, if applicable.
/// </summary>
public string? AuthorizationId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the token entry identifier associated with the token, if applicable. /// Gets or sets the token entry identifier associated with the token, if applicable.
/// </summary> /// </summary>

2
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -44,6 +44,7 @@ public static class OpenIddictServerExtensions
builder.Services.TryAddSingleton<RequireAccessTokenValidated>(); builder.Services.TryAddSingleton<RequireAccessTokenValidated>();
builder.Services.TryAddSingleton<RequireAuthorizationCodeGenerated>(); builder.Services.TryAddSingleton<RequireAuthorizationCodeGenerated>();
builder.Services.TryAddSingleton<RequireAuthorizationCodeValidated>(); builder.Services.TryAddSingleton<RequireAuthorizationCodeValidated>();
builder.Services.TryAddSingleton<RequireAuthorizationIdResolved>();
builder.Services.TryAddSingleton<RequireAuthorizationStorageEnabled>(); builder.Services.TryAddSingleton<RequireAuthorizationStorageEnabled>();
builder.Services.TryAddSingleton<RequireAuthorizationRequest>(); builder.Services.TryAddSingleton<RequireAuthorizationRequest>();
builder.Services.TryAddSingleton<RequireClientIdParameter>(); builder.Services.TryAddSingleton<RequireClientIdParameter>();
@ -72,6 +73,7 @@ public static class OpenIddictServerExtensions
builder.Services.TryAddSingleton<RequireScopePermissionsEnabled>(); builder.Services.TryAddSingleton<RequireScopePermissionsEnabled>();
builder.Services.TryAddSingleton<RequireScopeValidationEnabled>(); builder.Services.TryAddSingleton<RequireScopeValidationEnabled>();
builder.Services.TryAddSingleton<RequireTokenEntryCreated>(); builder.Services.TryAddSingleton<RequireTokenEntryCreated>();
builder.Services.TryAddSingleton<RequireTokenIdResolved>();
builder.Services.TryAddSingleton<RequireTokenLifetimeValidationEnabled>(); builder.Services.TryAddSingleton<RequireTokenLifetimeValidationEnabled>();
builder.Services.TryAddSingleton<RequireTokenPayloadPersisted>(); builder.Services.TryAddSingleton<RequireTokenPayloadPersisted>();
builder.Services.TryAddSingleton<RequireTokenRequest>(); builder.Services.TryAddSingleton<RequireTokenRequest>();

32
src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs

@ -75,6 +75,22 @@ public static class OpenIddictServerHandlerFilters
} }
} }
/// <summary>
/// Represents a filter that excludes the associated handlers if no authorization identifier is resolved from the token.
/// </summary>
public sealed class RequireAuthorizationIdResolved : IOpenIddictServerHandlerFilter<ValidateTokenContext>
{
public ValueTask<bool> IsActiveAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!string.IsNullOrEmpty(context.AuthorizationId));
}
}
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if the request is not an authorization request. /// Represents a filter that excludes the associated handlers if the request is not an authorization request.
/// </summary> /// </summary>
@ -507,6 +523,22 @@ public static class OpenIddictServerHandlerFilters
} }
} }
/// <summary>
/// Represents a filter that excludes the associated handlers if no token identifier is resolved from the token.
/// </summary>
public sealed class RequireTokenIdResolved : IOpenIddictServerHandlerFilter<ValidateTokenContext>
{
public ValueTask<bool> IsActiveAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!string.IsNullOrEmpty(context.TokenId));
}
}
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if no token entry is created in the database. /// Represents a filter that excludes the associated handlers if no token entry is created in the database.
/// </summary> /// </summary>

203
src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs

@ -29,7 +29,7 @@ public static partial class OpenIddictServerHandlers
ValidateIdentityModelToken.Descriptor, ValidateIdentityModelToken.Descriptor,
NormalizeScopeClaims.Descriptor, NormalizeScopeClaims.Descriptor,
MapInternalClaims.Descriptor, MapInternalClaims.Descriptor,
RestoreReferenceTokenProperties.Descriptor, RestoreTokenEntryProperties.Descriptor,
ValidatePrincipal.Descriptor, ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor, ValidateExpirationDate.Descriptor,
ValidateTokenEntry.Descriptor, ValidateTokenEntry.Descriptor,
@ -41,7 +41,7 @@ public static partial class OpenIddictServerHandlers
AttachSecurityCredentials.Descriptor, AttachSecurityCredentials.Descriptor,
CreateTokenEntry.Descriptor, CreateTokenEntry.Descriptor,
GenerateIdentityModelToken.Descriptor, GenerateIdentityModelToken.Descriptor,
ConvertReferenceToken.Descriptor, AttachTokenPayload.Descriptor,
BeautifyToken.Descriptor); BeautifyToken.Descriptor);
/// <summary> /// <summary>
@ -241,6 +241,7 @@ public static partial class OpenIddictServerHandlers
// Replace the token parameter by the payload resolved from the token entry // Replace the token parameter by the payload resolved from the token entry
// and store the identifier of the reference token so it can be later // and store the identifier of the reference token so it can be later
// used to restore the properties associated with the token. // used to restore the properties associated with the token.
context.IsReferenceToken = true;
context.Token = payload; context.Token = payload;
context.TokenId = await _tokenManager.GetIdAsync(token); context.TokenId = await _tokenManager.GetIdAsync(token);
@ -565,16 +566,16 @@ public static partial class OpenIddictServerHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for restoring the properties associated with a reference token entry. /// Contains the logic responsible for restoring the properties associated with a token entry.
/// Note: this handler is not used when the degraded mode is enabled. /// Note: this handler is not used when the degraded mode is enabled.
/// </summary> /// </summary>
public sealed class RestoreReferenceTokenProperties : IOpenIddictServerHandler<ValidateTokenContext> public sealed class RestoreTokenEntryProperties : IOpenIddictServerHandler<ValidateTokenContext>
{ {
private readonly IOpenIddictTokenManager _tokenManager; private readonly IOpenIddictTokenManager _tokenManager;
public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); public RestoreTokenEntryProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager) public RestoreTokenEntryProperties(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager)); => _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary> /// <summary>
@ -584,7 +585,7 @@ public static partial class OpenIddictServerHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireDegradedModeDisabled>() .AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireTokenStorageEnabled>() .AddFilter<RequireTokenStorageEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>() .UseScopedHandler<RestoreTokenEntryProperties>()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000) .SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -596,20 +597,54 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (context.Principal is null || string.IsNullOrEmpty(context.TokenId)) if (context.Principal is null)
{ {
return; return;
} }
var token = await _tokenManager.FindByIdAsync(context.TokenId) ?? // Extract the token identifier from the authentication principal.
throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); //
// If no token identifier can be found, this indicates that the token
// has no backing database entry (e.g if token storage was disabled).
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
}
// If the token entry cannot be found, return a generic error.
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is null)
{
context.Reject(
error: Errors.InvalidToken,
description: context.Principal.GetTokenType() switch
{
TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2001),
TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2002),
TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2003),
_ => SR.GetResourceString(SR.ID2004)
},
uri: context.Principal.GetTokenType() switch
{
TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2001),
TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2002),
TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2003),
_ => SR.FormatID8000(SR.ID2004)
});
return;
}
// Restore the creation/expiration dates/identifiers from the token entry metadata. // Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) context.Principal
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) .SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token)) .SetAuthorizationId(context.AuthorizationId = await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token)); .SetTokenId(context.TokenId = await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
} }
} }
@ -624,7 +659,7 @@ public static partial class OpenIddictServerHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidatePrincipal>() .UseSingletonHandler<ValidatePrincipal>()
.SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000) .SetOrder(RestoreTokenEntryProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -770,6 +805,7 @@ public static partial class OpenIddictServerHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireDegradedModeDisabled>() .AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireTokenStorageEnabled>() .AddFilter<RequireTokenStorageEnabled>()
.AddFilter<RequireTokenIdResolved>()
.UseScopedHandler<ValidateTokenEntry>() .UseScopedHandler<ValidateTokenEntry>()
.SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000) .SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
@ -783,41 +819,10 @@ public static partial class OpenIddictServerHandlers
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
Debug.Assert(!string.IsNullOrEmpty(context.TokenId), SR.GetResourceString(SR.ID4017));
// Extract the token identifier from the authentication principal. var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
// If no token identifier can be found, this indicates that the token throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
// has no backing database entry (e.g an access token or an identity token).
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
}
// If the token entry cannot be found, return a generic error.
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is null)
{
context.Reject(
error: Errors.InvalidToken,
description: context.Principal.GetTokenType() switch
{
TokenTypeHints.AuthorizationCode => SR.GetResourceString(SR.ID2001),
TokenTypeHints.DeviceCode => SR.GetResourceString(SR.ID2002),
TokenTypeHints.RefreshToken => SR.GetResourceString(SR.ID2003),
_ => SR.GetResourceString(SR.ID2004)
},
uri: context.Principal.GetTokenType() switch
{
TokenTypeHints.AuthorizationCode => SR.FormatID8000(SR.ID2001),
TokenTypeHints.DeviceCode => SR.FormatID8000(SR.ID2002),
TokenTypeHints.RefreshToken => SR.FormatID8000(SR.ID2003),
_ => SR.FormatID8000(SR.ID2004)
});
return;
}
// If the token is already marked as redeemed, this may indicate that it was compromised. // 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. // In this case, revoke the entire chain of tokens associated with the authorization.
@ -828,7 +833,7 @@ public static partial class OpenIddictServerHandlers
{ {
if (!context.Principal.HasTokenType(TokenTypeHints.RefreshToken) || !await IsReusableAsync(token)) if (!context.Principal.HasTokenType(TokenTypeHints.RefreshToken) || !await IsReusableAsync(token))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6002), context.TokenId);
context.Reject( context.Reject(
error: Errors.InvalidToken, error: Errors.InvalidToken,
@ -850,7 +855,7 @@ public static partial class OpenIddictServerHandlers
}); });
// Revoke all the token entries associated with the authorization. // Revoke all the token entries associated with the authorization.
await TryRevokeChainAsync(await _tokenManager.GetAuthorizationIdAsync(token)); await TryRevokeChainAsync(context.AuthorizationId);
return; return;
} }
@ -861,7 +866,7 @@ public static partial class OpenIddictServerHandlers
// If the token is not marked as valid yet, return an authorization_pending error. // If the token is not marked as valid yet, return an authorization_pending error.
if (await _tokenManager.HasStatusAsync(token, Statuses.Inactive)) if (await _tokenManager.HasStatusAsync(token, Statuses.Inactive))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6003), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6003), context.TokenId);
context.Reject( context.Reject(
error: Errors.AuthorizationPending, error: Errors.AuthorizationPending,
@ -874,7 +879,7 @@ public static partial class OpenIddictServerHandlers
// If the token is marked as rejected, return an access_denied error. // If the token is marked as rejected, return an access_denied error.
if (await _tokenManager.HasStatusAsync(token, Statuses.Rejected)) if (await _tokenManager.HasStatusAsync(token, Statuses.Rejected))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6004), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6004), context.TokenId);
context.Reject( context.Reject(
error: Errors.AccessDenied, error: Errors.AccessDenied,
@ -886,7 +891,7 @@ public static partial class OpenIddictServerHandlers
if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid)) if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), context.TokenId);
context.Reject( context.Reject(
error: Errors.InvalidToken, error: Errors.InvalidToken,
@ -910,13 +915,6 @@ public static partial class OpenIddictServerHandlers
return; return;
} }
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
async ValueTask<bool> IsReusableAsync(object token) async ValueTask<bool> IsReusableAsync(object token)
{ {
// If the reuse leeway was set to null, return false to indicate // If the reuse leeway was set to null, return false to indicate
@ -973,6 +971,7 @@ public static partial class OpenIddictServerHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireDegradedModeDisabled>() .AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireAuthorizationStorageEnabled>() .AddFilter<RequireAuthorizationStorageEnabled>()
.AddFilter<RequireAuthorizationIdResolved>()
.UseScopedHandler<ValidateAuthorizationEntry>() .UseScopedHandler<ValidateAuthorizationEntry>()
.SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000) .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
@ -986,17 +985,12 @@ public static partial class OpenIddictServerHandlers
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
Debug.Assert(!string.IsNullOrEmpty(context.AuthorizationId), SR.GetResourceString(SR.ID4018));
var identifier = context.Principal.GetAuthorizationId(); var authorization = await _authorizationManager.FindByIdAsync(context.AuthorizationId);
if (string.IsNullOrEmpty(identifier))
{
return;
}
var authorization = await _authorizationManager.FindByIdAsync(identifier);
if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid)) if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), context.AuthorizationId);
context.Reject( context.Reject(
error: Errors.InvalidToken, error: Errors.InvalidToken,
@ -1154,7 +1148,7 @@ public static partial class OpenIddictServerHandlers
var identifier = await _tokenManager.GetIdAsync(token); var identifier = await _tokenManager.GetIdAsync(token);
// Attach the token identifier to the principal so that it can be stored in the token. // Attach the token identifier to the principal so that it can be stored in the token payload.
context.Principal.SetTokenId(identifier); context.Principal.SetTokenId(identifier);
context.Logger.LogTrace(SR.GetResourceString(SR.ID6012), context.TokenType, identifier); context.Logger.LogTrace(SR.GetResourceString(SR.ID6012), context.TokenType, identifier);
@ -1295,16 +1289,16 @@ public static partial class OpenIddictServerHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for converting the token to a reference token. /// Contains the logic responsible for attaching the token payload to the token entry.
/// Note: this handler is not used when the degraded mode is enabled. /// Note: this handler is not used when the degraded mode is enabled.
/// </summary> /// </summary>
public sealed class ConvertReferenceToken : IOpenIddictServerHandler<GenerateTokenContext> public sealed class AttachTokenPayload : IOpenIddictServerHandler<GenerateTokenContext>
{ {
private readonly IOpenIddictTokenManager _tokenManager; private readonly IOpenIddictTokenManager _tokenManager;
public ConvertReferenceToken() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); public AttachTokenPayload() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ConvertReferenceToken(IOpenIddictTokenManager tokenManager) public AttachTokenPayload(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager)); => _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary> /// <summary>
@ -1315,7 +1309,7 @@ public static partial class OpenIddictServerHandlers
.AddFilter<RequireDegradedModeDisabled>() .AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireTokenStorageEnabled>() .AddFilter<RequireTokenStorageEnabled>()
.AddFilter<RequireTokenPayloadPersisted>() .AddFilter<RequireTokenPayloadPersisted>()
.UseScopedHandler<ConvertReferenceToken>() .UseScopedHandler<AttachTokenPayload>()
.SetOrder(GenerateIdentityModelToken.Descriptor.Order + 1_000) .SetOrder(GenerateIdentityModelToken.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -1344,40 +1338,47 @@ public static partial class OpenIddictServerHandlers
descriptor.Payload = context.Token; descriptor.Payload = context.Token;
descriptor.Principal = context.Principal; descriptor.Principal = context.Principal;
if (context.TokenType is TokenTypeHints.UserCode) if (context.IsReferenceToken)
{ {
do if (context.TokenType is TokenTypeHints.UserCode)
{ {
// Note: unlike other reference tokens, user codes are meant to be used by humans, do
// who may have to enter it in a web form. To ensure they remain easy enough to type {
// even by users with non-Latin keyboards, user codes generated by OpenIddict are // Note: unlike other reference tokens, user codes are meant to be used by humans,
// only compound of 12 digits, generated using a crypto-secure random number generator. // who may have to enter it in a web form. To ensure they remain easy enough to type
// In this case, the resulting user code is estimated to have at most ~40 bits of entropy. // even by users with non-Latin keyboards, user codes generated by OpenIddict are
// only compound of 12 digits, generated using a crypto-secure random number generator.
// In this case, the resulting user code is estimated to have at most ~40 bits of entropy.
static string CreateRandomNumericCode(int length) => OpenIddictHelpers.CreateRandomString( static string CreateRandomNumericCode(int length) => OpenIddictHelpers.CreateRandomString(
charset: stackalloc[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }, charset: stackalloc[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
length: length); length: length);
descriptor.ReferenceId = CreateRandomNumericCode(length: 12); descriptor.ReferenceId = CreateRandomNumericCode(length: 12);
} }
// User codes are relatively short. To help reduce the risks of collisions with // User codes are relatively short. To help reduce the risks of collisions with
// existing entries, a database check is performed here before updating the entry. // existing entries, a database check is performed here before updating the entry.
while (await _tokenManager.FindByReferenceIdAsync(descriptor.ReferenceId) is not null); while (await _tokenManager.FindByReferenceIdAsync(descriptor.ReferenceId) is not null);
} }
else else
{ {
// For other tokens, generate a base64url-encoded 256-bit random identifier. // For other tokens, generate a base64url-encoded 256-bit random identifier.
descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256)); descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
}
} }
await _tokenManager.UpdateAsync(token, descriptor); await _tokenManager.UpdateAsync(token, descriptor);
// Replace the returned token by the reference identifier. context.Logger.LogTrace(SR.GetResourceString(SR.ID6014), context.Token, identifier, context.TokenType);
context.Token = descriptor.ReferenceId;
context.Logger.LogTrace(SR.GetResourceString(SR.ID6014), context.TokenType, identifier, descriptor.ReferenceId); // Replace the returned token by the reference identifier, if applicable.
if (context.IsReferenceToken)
{
context.Token = descriptor.ReferenceId;
context.Logger.LogTrace(SR.GetResourceString(SR.ID6015), descriptor.ReferenceId, identifier, context.TokenType);
}
} }
} }
@ -1397,7 +1398,7 @@ public static partial class OpenIddictServerHandlers
// reference identifiers only works when the degraded mode is disabled. // reference identifiers only works when the degraded mode is disabled.
.AddFilter<RequireDegradedModeDisabled>() .AddFilter<RequireDegradedModeDisabled>()
.UseSingletonHandler<BeautifyToken>() .UseSingletonHandler<BeautifyToken>()
.SetOrder(ConvertReferenceToken.Descriptor.Order + 1_000) .SetOrder(AttachTokenPayload.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -1412,7 +1413,7 @@ public static partial class OpenIddictServerHandlers
// To make user codes easier to read and type by humans, a dash is automatically // To make user codes easier to read and type by humans, a dash is automatically
// appended before each new block of 4 integers. These dashes are expected to be // appended before each new block of 4 integers. These dashes are expected to be
// stripped from the user codes when receiving them at the verification endpoint. // stripped from the user codes when receiving them at the verification endpoint.
if (context.TokenType is TokenTypeHints.UserCode) if (context.IsReferenceToken && context.TokenType is TokenTypeHints.UserCode)
{ {
var builder = new StringBuilder(context.Token); var builder = new StringBuilder(context.Token);
if (builder.Length % 4 != 0) if (builder.Length % 4 != 0)

23
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -2412,6 +2412,7 @@ public static partial class OpenIddictServerHandlers
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
// Access tokens can be converted to reference tokens if the // Access tokens can be converted to reference tokens if the
// corresponding option was enabled in the server options. // corresponding option was enabled in the server options.
IsReferenceToken = context.Options.UseReferenceAccessTokens,
PersistTokenPayload = context.Options.UseReferenceAccessTokens, PersistTokenPayload = context.Options.UseReferenceAccessTokens,
Principal = context.AccessTokenPrincipal!, Principal = context.AccessTokenPrincipal!,
TokenFormat = TokenFormats.Jwt, TokenFormat = TokenFormats.Jwt,
@ -2478,6 +2479,7 @@ public static partial class OpenIddictServerHandlers
{ {
ClientId = context.ClientId, ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
IsReferenceToken = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage, PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.AuthorizationCodePrincipal!, Principal = context.AuthorizationCodePrincipal!,
TokenFormat = TokenFormats.Jwt, TokenFormat = TokenFormats.Jwt,
@ -2543,8 +2545,16 @@ public static partial class OpenIddictServerHandlers
var notification = new GenerateTokenContext(context.Transaction) var notification = new GenerateTokenContext(context.Transaction)
{ {
ClientId = context.ClientId, ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage, // Don't create a new entry if the device code is generated as part
// Device codes can be converted to reference tokens if they are not generated // of a device code swap made by the user code verification endpoint.
CreateTokenEntry = context.EndpointType switch
{
OpenIddictServerEndpointType.Verification => false,
_ => !context.Options.DisableTokenStorage
},
IsReferenceToken = !context.Options.DisableTokenStorage,
// Device codes are not persisted using the generic logic if they are generated
// as part of a device code swap made by the user code verification endpoint. // as part of a device code swap made by the user code verification endpoint.
PersistTokenPayload = context.EndpointType switch PersistTokenPayload = context.EndpointType switch
{ {
@ -2554,7 +2564,7 @@ public static partial class OpenIddictServerHandlers
}, },
Principal = context.DeviceCodePrincipal!, Principal = context.DeviceCodePrincipal!,
TokenFormat = TokenFormats.Jwt, TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.DeviceCode TokenType = TokenTypeHints.DeviceCode,
}; };
await _dispatcher.DispatchAsync(notification); await _dispatcher.DispatchAsync(notification);
@ -2619,6 +2629,7 @@ public static partial class OpenIddictServerHandlers
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
// Refresh tokens can be converted to reference tokens if the // Refresh tokens can be converted to reference tokens if the
// corresponding option was enabled in the server options. // corresponding option was enabled in the server options.
IsReferenceToken = context.Options.UseReferenceRefreshTokens,
PersistTokenPayload = context.Options.UseReferenceRefreshTokens, PersistTokenPayload = context.Options.UseReferenceRefreshTokens,
Principal = context.RefreshTokenPrincipal!, Principal = context.RefreshTokenPrincipal!,
TokenFormat = TokenFormats.Jwt, TokenFormat = TokenFormats.Jwt,
@ -2738,7 +2749,7 @@ public static partial class OpenIddictServerHandlers
throw new InvalidOperationException(SR.GetResourceString(SR.ID0020)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0020));
} }
// Extract the token identifier from the authentication principal. // Extract the device code identifier from the user code principal.
var identifier = context.Principal.GetClaim(Claims.Private.DeviceCodeId); var identifier = context.Principal.GetClaim(Claims.Private.DeviceCodeId);
if (string.IsNullOrEmpty(identifier)) if (string.IsNullOrEmpty(identifier))
{ {
@ -2890,6 +2901,7 @@ public static partial class OpenIddictServerHandlers
ClientId = context.ClientId, ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage, PersistTokenPayload = !context.Options.DisableTokenStorage,
IsReferenceToken = !context.Options.DisableTokenStorage,
Principal = context.UserCodePrincipal!, Principal = context.UserCodePrincipal!,
TokenFormat = TokenFormats.Jwt, TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.UserCode TokenType = TokenTypeHints.UserCode
@ -2955,7 +2967,8 @@ public static partial class OpenIddictServerHandlers
{ {
ClientId = context.ClientId, ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage, CreateTokenEntry = !context.Options.DisableTokenStorage,
// Identity tokens cannot never be reference tokens. // Identity tokens cannot be reference tokens.
IsReferenceToken = false,
PersistTokenPayload = false, PersistTokenPayload = false,
Principal = context.IdentityTokenPrincipal!, Principal = context.IdentityTokenPrincipal!,
TokenFormat = TokenFormats.Jwt, TokenFormat = TokenFormats.Jwt,

6
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs

@ -98,11 +98,11 @@ public static partial class OpenIddictValidationDataProtectionHandlers
// //
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens. // Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector( var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
(type, context.TokenId) switch (type, context.IsReferenceToken) switch
{ {
(TokenTypeHints.AccessToken, { Length: not 0 }) (TokenTypeHints.AccessToken, true)
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
(TokenTypeHints.AccessToken, null or { Length: 0 }) (TokenTypeHints.AccessToken, false)
=> new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))

10
src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs

@ -49,6 +49,16 @@ public static partial class OpenIddictValidationEvents
/// </summary> /// </summary>
public string Token { get; set; } = default!; public string Token { get; set; } = default!;
/// <summary>
/// Gets or sets a boolean indicating whether the validated token is a reference token.
/// </summary>
public bool IsReferenceToken { get; set; }
/// <summary>
/// Gets or sets the authorization entry identifier associated with the token, if applicable.
/// </summary>
public string? AuthorizationId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the token entry identifier associated with the token, if applicable. /// Gets or sets the token entry identifier associated with the token, if applicable.
/// </summary> /// </summary>

2
src/OpenIddict.Validation/OpenIddictValidationExtensions.cs

@ -44,8 +44,10 @@ public static class OpenIddictValidationExtensions
builder.Services.TryAddSingleton<RequireAccessTokenExtracted>(); builder.Services.TryAddSingleton<RequireAccessTokenExtracted>();
builder.Services.TryAddSingleton<RequireAccessTokenValidated>(); builder.Services.TryAddSingleton<RequireAccessTokenValidated>();
builder.Services.TryAddSingleton<RequireAuthorizationEntryValidationEnabled>(); builder.Services.TryAddSingleton<RequireAuthorizationEntryValidationEnabled>();
builder.Services.TryAddSingleton<RequireAuthorizationIdResolved>();
builder.Services.TryAddSingleton<RequireLocalValidation>(); builder.Services.TryAddSingleton<RequireLocalValidation>();
builder.Services.TryAddSingleton<RequireTokenEntryValidationEnabled>(); builder.Services.TryAddSingleton<RequireTokenEntryValidationEnabled>();
builder.Services.TryAddSingleton<RequireTokenIdResolved>();
builder.Services.TryAddSingleton<RequireIntrospectionValidation>(); builder.Services.TryAddSingleton<RequireIntrospectionValidation>();
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.

32
src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs

@ -59,6 +59,22 @@ public static class OpenIddictValidationHandlerFilters
} }
} }
/// <summary>
/// Represents a filter that excludes the associated handlers if no authorization identifier is resolved from the token.
/// </summary>
public sealed class RequireAuthorizationIdResolved : IOpenIddictValidationHandlerFilter<ValidateTokenContext>
{
public ValueTask<bool> IsActiveAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!string.IsNullOrEmpty(context.AuthorizationId));
}
}
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if local validation is not used. /// Represents a filter that excludes the associated handlers if local validation is not used.
/// </summary> /// </summary>
@ -91,6 +107,22 @@ public static class OpenIddictValidationHandlerFilters
} }
} }
/// <summary>
/// Represents a filter that excludes the associated handlers if no token identifier is resolved from the token.
/// </summary>
public sealed class RequireTokenIdResolved : IOpenIddictValidationHandlerFilter<ValidateTokenContext>
{
public ValueTask<bool> IsActiveAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!string.IsNullOrEmpty(context.TokenId));
}
}
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if token validation was not enabled. /// Represents a filter that excludes the associated handlers if token validation was not enabled.
/// </summary> /// </summary>

84
src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs

@ -28,7 +28,7 @@ public static partial class OpenIddictValidationHandlers
IntrospectToken.Descriptor, IntrospectToken.Descriptor,
NormalizeScopeClaims.Descriptor, NormalizeScopeClaims.Descriptor,
MapInternalClaims.Descriptor, MapInternalClaims.Descriptor,
RestoreReferenceTokenProperties.Descriptor, RestoreTokenEntryProperties.Descriptor,
ValidatePrincipal.Descriptor, ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor, ValidateExpirationDate.Descriptor,
ValidateAudience.Descriptor, ValidateAudience.Descriptor,
@ -199,6 +199,7 @@ public static partial class OpenIddictValidationHandlers
// Replace the token parameter by the payload resolved from the token entry // Replace the token parameter by the payload resolved from the token entry
// and store the identifier of the reference token so it can be later // and store the identifier of the reference token so it can be later
// used to restore the properties associated with the token. // used to restore the properties associated with the token.
context.IsReferenceToken = true;
context.Token = payload; context.Token = payload;
context.TokenId = await _tokenManager.GetIdAsync(token); context.TokenId = await _tokenManager.GetIdAsync(token);
} }
@ -543,16 +544,16 @@ public static partial class OpenIddictValidationHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for restoring the properties associated with a reference token entry. /// Contains the logic responsible for restoring the properties associated with a token entry.
/// Note: this handler is not used when the degraded mode is enabled. /// Note: this handler is not used when the degraded mode is enabled.
/// </summary> /// </summary>
public sealed class RestoreReferenceTokenProperties : IOpenIddictValidationHandler<ValidateTokenContext> public sealed class RestoreTokenEntryProperties : IOpenIddictValidationHandler<ValidateTokenContext>
{ {
private readonly IOpenIddictTokenManager _tokenManager; private readonly IOpenIddictTokenManager _tokenManager;
public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139)); public RestoreTokenEntryProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139));
public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager) public RestoreTokenEntryProperties(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager)); => _tokenManager = tokenManager ?? throw new ArgumentNullException(nameof(tokenManager));
/// <summary> /// <summary>
@ -562,7 +563,7 @@ public static partial class OpenIddictValidationHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>() .AddFilter<RequireLocalValidation>()
.AddFilter<RequireTokenEntryValidationEnabled>() .AddFilter<RequireTokenEntryValidationEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>() .UseScopedHandler<RestoreTokenEntryProperties>()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000) .SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build(); .Build();
@ -575,20 +576,40 @@ public static partial class OpenIddictValidationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (context.Principal is null || string.IsNullOrEmpty(context.TokenId)) if (context.Principal is null)
{ {
return; return;
} }
var token = await _tokenManager.FindByIdAsync(context.TokenId) ?? // Extract the token identifier from the authentication principal.
throw new InvalidOperationException(SR.GetResourceString(SR.ID0021)); //
// If no token identifier can be found, this indicates that the token
// has no backing database entry (e.g if token storage was disabled).
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
}
// If the token entry cannot be found, return a generic error.
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is null)
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2019),
uri: SR.FormatID8000(SR.ID2019));
return;
}
// Restore the creation/expiration dates/identifiers from the token entry metadata. // Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) context.Principal
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) .SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token)) .SetAuthorizationId(context.AuthorizationId = await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token)); .SetTokenId(context.TokenId = await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
} }
} }
@ -603,7 +624,7 @@ public static partial class OpenIddictValidationHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidatePrincipal>() .UseSingletonHandler<ValidatePrincipal>()
.SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000) .SetOrder(RestoreTokenEntryProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build(); .Build();
@ -771,6 +792,7 @@ public static partial class OpenIddictValidationHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>() .AddFilter<RequireLocalValidation>()
.AddFilter<RequireTokenEntryValidationEnabled>() .AddFilter<RequireTokenEntryValidationEnabled>()
.AddFilter<RequireTokenIdResolved>()
.UseScopedHandler<ValidateTokenEntry>() .UseScopedHandler<ValidateTokenEntry>()
.SetOrder(ValidateAudience.Descriptor.Order + 1_000) .SetOrder(ValidateAudience.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
@ -785,17 +807,14 @@ public static partial class OpenIddictValidationHandlers
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
Debug.Assert(!string.IsNullOrEmpty(context.TokenId), SR.GetResourceString(SR.ID4017));
var identifier = context.Principal.GetTokenId(); var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
if (string.IsNullOrEmpty(identifier)) throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
{
return;
}
var token = await _tokenManager.FindByIdAsync(identifier); if (!await _tokenManager.HasStatusAsync(token, Statuses.Valid))
if (token is null || !await _tokenManager.HasStatusAsync(token, Statuses.Valid))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), context.TokenId);
context.Reject( context.Reject(
error: Errors.InvalidToken, error: Errors.InvalidToken,
@ -804,13 +823,6 @@ public static partial class OpenIddictValidationHandlers
return; return;
} }
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
} }
} }
@ -835,6 +847,7 @@ public static partial class OpenIddictValidationHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>() .AddFilter<RequireLocalValidation>()
.AddFilter<RequireAuthorizationEntryValidationEnabled>() .AddFilter<RequireAuthorizationEntryValidationEnabled>()
.AddFilter<RequireAuthorizationIdResolved>()
.UseScopedHandler<ValidateAuthorizationEntry>() .UseScopedHandler<ValidateAuthorizationEntry>()
.SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000) .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
@ -849,17 +862,12 @@ public static partial class OpenIddictValidationHandlers
} }
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
Debug.Assert(!string.IsNullOrEmpty(context.AuthorizationId), SR.GetResourceString(SR.ID4018));
var identifier = context.Principal.GetAuthorizationId(); var authorization = await _authorizationManager.FindByIdAsync(context.AuthorizationId);
if (string.IsNullOrEmpty(identifier))
{
return;
}
var authorization = await _authorizationManager.FindByIdAsync(identifier);
if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid)) if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid))
{ {
context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier); context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), context.AuthorizationId);
context.Reject( context.Reject(
error: Errors.InvalidToken, error: Errors.InvalidToken,

48
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs

@ -530,6 +530,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.FindByReferenceIdAsync("g43LaWCUrz2RaLILz2L1bg1bOpMSv1hGrH12IIkB9H4", It.IsAny<CancellationToken>())) mock.Setup(manager => manager.FindByReferenceIdAsync("g43LaWCUrz2RaLILz2L1bg1bOpMSv1hGrH12IIkB9H4", It.IsAny<CancellationToken>()))
.ReturnsAsync(token); .ReturnsAsync(token);
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.DeviceCode);
mock.Setup(manager => manager.HasTypeAsync(token, TokenTypeHints.DeviceCode, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasTypeAsync(token, TokenTypeHints.DeviceCode, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
@ -559,7 +562,9 @@ public abstract partial class OpenIddictServerIntegrationTests
Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes); Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer")) context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetClaim(Claims.Subject, "Bob le Bricoleur"); .SetClaim(Claims.Subject, "Bob le Bricoleur")
.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103")
.SetTokenType(TokenTypeHints.DeviceCode);
return default; return default;
}); });
@ -2365,6 +2370,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.AuthorizationCode);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
}); });
@ -2448,6 +2456,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
@ -2522,6 +2533,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
@ -2596,6 +2610,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
@ -2683,6 +2700,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
mock.Setup(manager => manager.GetTypeAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.AuthorizationCode);
mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
@ -2784,6 +2804,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
mock.Setup(manager => manager.GetTypeAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
@ -2877,6 +2900,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
mock.Setup(manager => manager.GetTypeAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
@ -2970,6 +2996,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
mock.Setup(manager => manager.GetTypeAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
@ -3062,6 +3091,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.AuthorizationCode);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
@ -3149,6 +3181,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
@ -3270,6 +3305,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.AuthorizationCode);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
@ -3362,6 +3400,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
@ -3881,6 +3922,11 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC"); .ReturnsAsync("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(flow is GrantTypes.AuthorizationCode ?
TokenTypeHints.AuthorizationCode :
TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);

9
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

@ -3057,6 +3057,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56"); .ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.AuthorizationCode);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
@ -3132,6 +3135,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103"); .ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
@ -3198,6 +3204,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>())) mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token); .ReturnsAsync(token);
mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(TokenTypeHints.RefreshToken);
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);

Loading…
Cancel
Save