diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index 83772ba4..e9e74af4 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1939,6 +1939,12 @@ Consider registering a certificate using 'services.AddOpenIddict().AddClient().A
The number of written bytes ({0}) doesn't match the expected value ({1}).
+
+ The token identifier shouldn't be null or empty at this point.
+
+
+ The authorization identifier shouldn't be null or empty at this point.
+
An error occurred while validating the token '{Token}'.
@@ -1979,14 +1985,21 @@ Consider registering a certificate using 'services.AddOpenIddict().AddClient().A
The token entry for '{Type}' token '{Identifier}' was successfully created.
- A new '{Type}' token was successfully created: {Payload}.
+ A new '{Type}' JSON Web Token was successfully created: {Payload}.
The principal used to create the token contained the following claims: {Claims}.
- The token entry for '{Type}' token '{Identifier}' was successfully converted to a reference token with the identifier '{ReferenceId}'.
+ The token payload ({Payload}) was successfully attached to the token entry '{Identifier}' of type '{Type}'.
+
+
+ The reference identifier ({ReferenceId}) was successfully attached to the token entry '{Identifier}' of type '{Type}'.
+
+
+ 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}.
- The reference token entry for device code '{Identifier}' was successfully updated'.
+ The token entry for device code '{Identifier}' was successfully updated with the new payload.
The authorization request was successfully extracted: {Request}.
diff --git a/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs b/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs
index 629661a6..9bb5b160 100644
--- a/src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs
+++ b/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.
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 },
- (TokenTypeHints.StateToken, null or { Length: 0 })
+ (TokenTypeHints.StateToken, false)
=> new[] { Handlers.Client, Formats.StateToken, Schemes.Server },
_ => 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.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
- (context.TokenType, context.PersistTokenPayload) switch
+ (context.TokenType, context.IsReferenceToken) switch
{
(TokenTypeHints.StateToken, true)
=> 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.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType,
+ context.Logger.LogTrace(SR.GetResourceString(SR.ID6016), context.TokenType,
context.Token, context.Principal.Claims);
return default;
diff --git a/src/OpenIddict.Client/OpenIddictClientBuilder.cs b/src/OpenIddict.Client/OpenIddictClientBuilder.cs
index bd7e4574..df2a9fb8 100644
--- a/src/OpenIddict.Client/OpenIddictClientBuilder.cs
+++ b/src/OpenIddict.Client/OpenIddictClientBuilder.cs
@@ -881,8 +881,7 @@ public sealed class OpenIddictClientBuilder
///
/// Disables token storage, so that no database entry is created
/// for the tokens and codes returned by the OpenIddict client.
- /// Using this option is generally NOT recommended as it prevents
- /// the tokens from being revoked (if needed).
+ /// Using this option is generally NOT recommended.
///
/// The instance.
public OpenIddictClientBuilder DisableTokenStorage()
diff --git a/src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs b/src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs
index b820d29c..9754af17 100644
--- a/src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs
+++ b/src/OpenIddict.Client/OpenIddictClientEvents.Protection.cs
@@ -40,6 +40,12 @@ public static partial class OpenIddictClientEvents
///
public bool CreateTokenEntry { get; set; }
+ ///
+ /// 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.
+ ///
+ public bool IsReferenceToken { get; set; }
+
///
/// Gets or sets a boolean indicating whether the token payload
/// should be persisted alongside the token metadata in the database.
@@ -129,6 +135,16 @@ public static partial class OpenIddictClientEvents
///
public string? TokenTypeHint { get; set; } = default!;
+ ///
+ /// Gets or sets a boolean indicating whether the validated token is a reference token.
+ ///
+ public bool IsReferenceToken { get; set; }
+
+ ///
+ /// Gets or sets the authorization entry identifier associated with the token, if applicable.
+ ///
+ public string? AuthorizationId { get; set; }
+
///
/// Gets or sets the token entry identifier associated with the token, if applicable.
///
diff --git a/src/OpenIddict.Client/OpenIddictClientExtensions.cs b/src/OpenIddict.Client/OpenIddictClientExtensions.cs
index d34db533..348e4e08 100644
--- a/src/OpenIddict.Client/OpenIddictClientExtensions.cs
+++ b/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.
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
@@ -54,6 +55,7 @@ public static class OpenIddictClientExtensions
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs b/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
index b627d36b..0a1825f6 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
@@ -27,6 +27,22 @@ public static class OpenIddictClientHandlerFilters
}
}
+ ///
+ /// Represents a filter that excludes the associated handlers if no authorization identifier is resolved from the token.
+ ///
+ public sealed class RequireAuthorizationIdResolved : IOpenIddictClientHandlerFilter
+ {
+ public ValueTask IsActiveAsync(ValidateTokenContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new(!string.IsNullOrEmpty(context.AuthorizationId));
+ }
+ }
+
///
/// Represents a filter that excludes the associated handlers if no backchannel access token is validated.
///
@@ -305,6 +321,22 @@ public static class OpenIddictClientHandlerFilters
}
}
+ ///
+ /// Represents a filter that excludes the associated handlers if no token identifier is resolved from the token.
+ ///
+ public sealed class RequireTokenIdResolved : IOpenIddictClientHandlerFilter
+ {
+ public ValueTask IsActiveAsync(ValidateTokenContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new(!string.IsNullOrEmpty(context.TokenId));
+ }
+ }
+
///
/// Represents a filter that excludes the associated handlers if the token payload is not persisted in the database.
///
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
index 4b822767..e13c7304 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
@@ -28,7 +28,7 @@ public static partial class OpenIddictClientHandlers
ValidateReferenceTokenIdentifier.Descriptor,
ValidateIdentityModelToken.Descriptor,
MapInternalClaims.Descriptor,
- RestoreReferenceTokenProperties.Descriptor,
+ RestoreTokenEntryProperties.Descriptor,
ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor,
ValidateTokenEntry.Descriptor,
@@ -39,7 +39,7 @@ public static partial class OpenIddictClientHandlers
AttachSecurityCredentials.Descriptor,
CreateTokenEntry.Descriptor,
GenerateIdentityModelToken.Descriptor,
- ConvertReferenceToken.Descriptor);
+ AttachTokenPayload.Descriptor);
///
/// 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
// and store the identifier of the reference token so it can be later
// used to restore the properties associated with the token.
+ context.IsReferenceToken = true;
context.Token = payload;
context.TokenId = await _tokenManager.GetIdAsync(token);
}
@@ -432,16 +433,16 @@ public static partial class OpenIddictClientHandlers
}
///
- /// 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.
///
- public sealed class RestoreReferenceTokenProperties : IOpenIddictClientHandler
+ public sealed class RestoreTokenEntryProperties : IOpenIddictClientHandler
{
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));
///
@@ -450,7 +451,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.AddFilter()
- .UseScopedHandler()
+ .UseScopedHandler()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -462,20 +463,40 @@ public static partial class OpenIddictClientHandlers
throw new ArgumentNullException(nameof(context));
}
- if (context.Principal is null || string.IsNullOrEmpty(context.TokenId))
+ if (context.Principal is null)
{
return;
}
- var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
- throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
+ // Extract the token identifier from the authentication principal.
+ //
+ // 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.
- 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));
+ context.Principal
+ .SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
+ .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
+ .SetAuthorizationId(context.AuthorizationId = await _tokenManager.GetAuthorizationIdAsync(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; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000)
+ .SetOrder(RestoreTokenEntryProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -591,6 +612,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.AddFilter()
+ .AddFilter()
.UseScopedHandler()
.SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000)
.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(!string.IsNullOrEmpty(context.TokenId), SR.GetResourceString(SR.ID4017));
- 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;
- }
+ var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
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(
error: Errors.InvalidToken,
@@ -648,7 +656,7 @@ public static partial class OpenIddictClientHandlers
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(
error: Errors.InvalidToken,
@@ -657,13 +665,6 @@ public static partial class OpenIddictClientHandlers
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
}
///
- /// 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.
///
- public sealed class ConvertReferenceToken : IOpenIddictClientHandler
+ public sealed class AttachTokenPayload : IOpenIddictClientHandler
{
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));
///
@@ -892,7 +893,7 @@ public static partial class OpenIddictClientHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.AddFilter()
.AddFilter()
- .UseScopedHandler()
+ .UseScopedHandler()
.SetOrder(GenerateIdentityModelToken.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -920,14 +921,22 @@ public static partial class OpenIddictClientHandlers
// Attach the generated token to the token entry.
descriptor.Payload = context.Token;
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);
- // Replace the returned token by the reference identifier.
- context.Token = descriptor.ReferenceId;
+ context.Logger.LogTrace(SR.GetResourceString(SR.ID6014), context.Token, identifier, context.TokenType);
- 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);
+ }
}
}
}
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
index 8dabf053..a846ad5a 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
@@ -2258,6 +2258,7 @@ public static partial class OpenIddictClientHandlers
var notification = new GenerateTokenContext(context.Transaction)
{
CreateTokenEntry = false,
+ IsReferenceToken = false,
PersistTokenPayload = false,
Principal = context.ClientAssertionTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
@@ -4624,6 +4625,7 @@ public static partial class OpenIddictClientHandlers
var notification = new GenerateTokenContext(context.Transaction)
{
CreateTokenEntry = !context.Options.DisableTokenStorage,
+ IsReferenceToken = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.StateTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
@@ -5215,6 +5217,7 @@ public static partial class OpenIddictClientHandlers
var notification = new GenerateTokenContext(context.Transaction)
{
CreateTokenEntry = !context.Options.DisableTokenStorage,
+ IsReferenceToken = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.StateTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
diff --git a/src/OpenIddict.Client/OpenIddictClientOptions.cs b/src/OpenIddict.Client/OpenIddictClientOptions.cs
index 9c4cd7c2..b73097f1 100644
--- a/src/OpenIddict.Client/OpenIddictClientOptions.cs
+++ b/src/OpenIddict.Client/OpenIddictClientOptions.cs
@@ -120,8 +120,7 @@ public sealed class OpenIddictClientOptions
///
/// 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
- /// OpenIddict client services. Using this option is generally NOT recommended
- /// as it prevents the tokens from being revoked (if needed).
+ /// OpenIddict client services. Using this option is generally NOT recommended.
///
public bool DisableTokenStorage { get; set; }
diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs
index 98ada848..9d85865f 100644
--- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs
+++ b/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.
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 },
- (TokenTypeHints.AccessToken, null or { Length: 0 })
+ (TokenTypeHints.AccessToken, false)
=> new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
- (TokenTypeHints.AuthorizationCode, { Length: not 0 })
+ (TokenTypeHints.AuthorizationCode, true)
=> 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 },
- (TokenTypeHints.DeviceCode, { Length: not 0 })
+ (TokenTypeHints.DeviceCode, true)
=> 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 },
- (TokenTypeHints.RefreshToken, { Length: not 0 })
+ (TokenTypeHints.RefreshToken, true)
=> 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 },
- (TokenTypeHints.UserCode, { Length: not 0 })
+ (TokenTypeHints.UserCode, true)
=> 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 },
_ => 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.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(
- (context.TokenType, context.PersistTokenPayload) switch
+ (context.TokenType, context.IsReferenceToken) switch
{
(TokenTypeHints.AccessToken, true)
=> 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.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType,
+ context.Logger.LogTrace(SR.GetResourceString(SR.ID6016), context.TokenType,
context.Token, context.Principal.Claims);
return default;
diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs
index 04872f37..ca4b2ed2 100644
--- a/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs
+++ b/src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs
@@ -46,6 +46,12 @@ public static partial class OpenIddictServerEvents
///
public bool CreateTokenEntry { get; set; }
+ ///
+ /// 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.
+ ///
+ public bool IsReferenceToken { get; set; }
+
///
/// Gets or sets a boolean indicating whether the token payload
/// should be persisted alongside the token metadata in the database.
@@ -135,6 +141,16 @@ public static partial class OpenIddictServerEvents
///
public string? TokenTypeHint { get; set; } = default!;
+ ///
+ /// Gets or sets a boolean indicating whether the validated token is a reference token.
+ ///
+ public bool IsReferenceToken { get; set; }
+
+ ///
+ /// Gets or sets the authorization entry identifier associated with the token, if applicable.
+ ///
+ public string? AuthorizationId { get; set; }
+
///
/// Gets or sets the token entry identifier associated with the token, if applicable.
///
diff --git a/src/OpenIddict.Server/OpenIddictServerExtensions.cs b/src/OpenIddict.Server/OpenIddictServerExtensions.cs
index 612389ae..c1c3f2a3 100644
--- a/src/OpenIddict.Server/OpenIddictServerExtensions.cs
+++ b/src/OpenIddict.Server/OpenIddictServerExtensions.cs
@@ -44,6 +44,7 @@ public static class OpenIddictServerExtensions
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
@@ -72,6 +73,7 @@ public static class OpenIddictServerExtensions
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
index 8b221a5c..f905a1c7 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
@@ -75,6 +75,22 @@ public static class OpenIddictServerHandlerFilters
}
}
+ ///
+ /// Represents a filter that excludes the associated handlers if no authorization identifier is resolved from the token.
+ ///
+ public sealed class RequireAuthorizationIdResolved : IOpenIddictServerHandlerFilter
+ {
+ public ValueTask IsActiveAsync(ValidateTokenContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new(!string.IsNullOrEmpty(context.AuthorizationId));
+ }
+ }
+
///
/// Represents a filter that excludes the associated handlers if the request is not an authorization request.
///
@@ -507,6 +523,22 @@ public static class OpenIddictServerHandlerFilters
}
}
+ ///
+ /// Represents a filter that excludes the associated handlers if no token identifier is resolved from the token.
+ ///
+ public sealed class RequireTokenIdResolved : IOpenIddictServerHandlerFilter
+ {
+ public ValueTask IsActiveAsync(ValidateTokenContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new(!string.IsNullOrEmpty(context.TokenId));
+ }
+ }
+
///
/// Represents a filter that excludes the associated handlers if no token entry is created in the database.
///
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
index dc509b82..298f9c99 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
@@ -29,7 +29,7 @@ public static partial class OpenIddictServerHandlers
ValidateIdentityModelToken.Descriptor,
NormalizeScopeClaims.Descriptor,
MapInternalClaims.Descriptor,
- RestoreReferenceTokenProperties.Descriptor,
+ RestoreTokenEntryProperties.Descriptor,
ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor,
ValidateTokenEntry.Descriptor,
@@ -41,7 +41,7 @@ public static partial class OpenIddictServerHandlers
AttachSecurityCredentials.Descriptor,
CreateTokenEntry.Descriptor,
GenerateIdentityModelToken.Descriptor,
- ConvertReferenceToken.Descriptor,
+ AttachTokenPayload.Descriptor,
BeautifyToken.Descriptor);
///
@@ -241,6 +241,7 @@ public static partial class OpenIddictServerHandlers
// 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
// used to restore the properties associated with the token.
+ context.IsReferenceToken = true;
context.Token = payload;
context.TokenId = await _tokenManager.GetIdAsync(token);
@@ -565,16 +566,16 @@ public static partial class OpenIddictServerHandlers
}
///
- /// 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.
///
- public sealed class RestoreReferenceTokenProperties : IOpenIddictServerHandler
+ public sealed class RestoreTokenEntryProperties : IOpenIddictServerHandler
{
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));
///
@@ -584,7 +585,7 @@ public static partial class OpenIddictServerHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
.AddFilter()
- .UseScopedHandler()
+ .UseScopedHandler()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@@ -596,20 +597,54 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
- if (context.Principal is null || string.IsNullOrEmpty(context.TokenId))
+ if (context.Principal is null)
{
return;
}
- var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
- throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
+ // Extract the token identifier from the authentication principal.
+ //
+ // 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.
- 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));
+ context.Principal
+ .SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
+ .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
+ .SetAuthorizationId(context.AuthorizationId = await _tokenManager.GetAuthorizationIdAsync(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; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000)
+ .SetOrder(RestoreTokenEntryProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@@ -770,6 +805,7 @@ public static partial class OpenIddictServerHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
.AddFilter()
+ .AddFilter()
.UseScopedHandler()
.SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000)
.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(!string.IsNullOrEmpty(context.TokenId), SR.GetResourceString(SR.ID4017));
- // Extract the token identifier from the authentication principal.
- // If no token identifier can be found, this indicates that the token
- // 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;
- }
+ var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
// 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.
@@ -828,7 +833,7 @@ public static partial class OpenIddictServerHandlers
{
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(
error: Errors.InvalidToken,
@@ -850,7 +855,7 @@ public static partial class OpenIddictServerHandlers
});
// Revoke all the token entries associated with the authorization.
- await TryRevokeChainAsync(await _tokenManager.GetAuthorizationIdAsync(token));
+ await TryRevokeChainAsync(context.AuthorizationId);
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 (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(
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 (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(
error: Errors.AccessDenied,
@@ -886,7 +891,7 @@ public static partial class OpenIddictServerHandlers
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(
error: Errors.InvalidToken,
@@ -910,13 +915,6 @@ public static partial class OpenIddictServerHandlers
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 IsReusableAsync(object token)
{
// If the reuse leeway was set to null, return false to indicate
@@ -973,6 +971,7 @@ public static partial class OpenIddictServerHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder()
.AddFilter()
.AddFilter()
+ .AddFilter()
.UseScopedHandler()
.SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
.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(!string.IsNullOrEmpty(context.AuthorizationId), SR.GetResourceString(SR.ID4018));
- var identifier = context.Principal.GetAuthorizationId();
- if (string.IsNullOrEmpty(identifier))
- {
- return;
- }
-
- var authorization = await _authorizationManager.FindByIdAsync(identifier);
+ var authorization = await _authorizationManager.FindByIdAsync(context.AuthorizationId);
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(
error: Errors.InvalidToken,
@@ -1154,7 +1148,7 @@ public static partial class OpenIddictServerHandlers
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.Logger.LogTrace(SR.GetResourceString(SR.ID6012), context.TokenType, identifier);
@@ -1295,16 +1289,16 @@ public static partial class OpenIddictServerHandlers
}
///
- /// 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.
///
- public sealed class ConvertReferenceToken : IOpenIddictServerHandler
+ public sealed class AttachTokenPayload : IOpenIddictServerHandler
{
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));
///
@@ -1315,7 +1309,7 @@ public static partial class OpenIddictServerHandlers
.AddFilter()
.AddFilter()
.AddFilter()
- .UseScopedHandler()
+ .UseScopedHandler()
.SetOrder(GenerateIdentityModelToken.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@@ -1344,40 +1338,47 @@ public static partial class OpenIddictServerHandlers
descriptor.Payload = context.Token;
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,
- // 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
- // 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.
+ do
+ {
+ // Note: unlike other reference tokens, user codes are meant to be used by humans,
+ // 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
+ // 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(
- charset: stackalloc[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
- length: length);
+ static string CreateRandomNumericCode(int length) => OpenIddictHelpers.CreateRandomString(
+ charset: stackalloc[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
+ 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
- // existing entries, a database check is performed here before updating the entry.
- while (await _tokenManager.FindByReferenceIdAsync(descriptor.ReferenceId) is not null);
- }
+ // 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.
+ while (await _tokenManager.FindByReferenceIdAsync(descriptor.ReferenceId) is not null);
+ }
- else
- {
- // For other tokens, generate a base64url-encoded 256-bit random identifier.
- descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
+ else
+ {
+ // For other tokens, generate a base64url-encoded 256-bit random identifier.
+ descriptor.ReferenceId = Base64UrlEncoder.Encode(OpenIddictHelpers.CreateRandomArray(size: 256));
+ }
}
await _tokenManager.UpdateAsync(token, descriptor);
- // Replace the returned token by the reference identifier.
- context.Token = descriptor.ReferenceId;
+ context.Logger.LogTrace(SR.GetResourceString(SR.ID6014), context.Token, identifier, context.TokenType);
- 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.
.AddFilter()
.UseSingletonHandler()
- .SetOrder(ConvertReferenceToken.Descriptor.Order + 1_000)
+ .SetOrder(AttachTokenPayload.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.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
// 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.
- if (context.TokenType is TokenTypeHints.UserCode)
+ if (context.IsReferenceToken && context.TokenType is TokenTypeHints.UserCode)
{
var builder = new StringBuilder(context.Token);
if (builder.Length % 4 != 0)
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index 9432d019..492b381c 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
@@ -2412,6 +2412,7 @@ public static partial class OpenIddictServerHandlers
CreateTokenEntry = !context.Options.DisableTokenStorage,
// Access tokens can be converted to reference tokens if the
// corresponding option was enabled in the server options.
+ IsReferenceToken = context.Options.UseReferenceAccessTokens,
PersistTokenPayload = context.Options.UseReferenceAccessTokens,
Principal = context.AccessTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
@@ -2478,6 +2479,7 @@ public static partial class OpenIddictServerHandlers
{
ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage,
+ IsReferenceToken = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage,
Principal = context.AuthorizationCodePrincipal!,
TokenFormat = TokenFormats.Jwt,
@@ -2543,8 +2545,16 @@ public static partial class OpenIddictServerHandlers
var notification = new GenerateTokenContext(context.Transaction)
{
ClientId = context.ClientId,
- CreateTokenEntry = !context.Options.DisableTokenStorage,
- // Device codes can be converted to reference tokens if they are not generated
+ // Don't create a new entry if the device code is generated as part
+ // 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.
PersistTokenPayload = context.EndpointType switch
{
@@ -2554,7 +2564,7 @@ public static partial class OpenIddictServerHandlers
},
Principal = context.DeviceCodePrincipal!,
TokenFormat = TokenFormats.Jwt,
- TokenType = TokenTypeHints.DeviceCode
+ TokenType = TokenTypeHints.DeviceCode,
};
await _dispatcher.DispatchAsync(notification);
@@ -2619,6 +2629,7 @@ public static partial class OpenIddictServerHandlers
CreateTokenEntry = !context.Options.DisableTokenStorage,
// Refresh tokens can be converted to reference tokens if the
// corresponding option was enabled in the server options.
+ IsReferenceToken = context.Options.UseReferenceRefreshTokens,
PersistTokenPayload = context.Options.UseReferenceRefreshTokens,
Principal = context.RefreshTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
@@ -2738,7 +2749,7 @@ public static partial class OpenIddictServerHandlers
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);
if (string.IsNullOrEmpty(identifier))
{
@@ -2890,6 +2901,7 @@ public static partial class OpenIddictServerHandlers
ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage,
PersistTokenPayload = !context.Options.DisableTokenStorage,
+ IsReferenceToken = !context.Options.DisableTokenStorage,
Principal = context.UserCodePrincipal!,
TokenFormat = TokenFormats.Jwt,
TokenType = TokenTypeHints.UserCode
@@ -2955,7 +2967,8 @@ public static partial class OpenIddictServerHandlers
{
ClientId = context.ClientId,
CreateTokenEntry = !context.Options.DisableTokenStorage,
- // Identity tokens cannot never be reference tokens.
+ // Identity tokens cannot be reference tokens.
+ IsReferenceToken = false,
PersistTokenPayload = false,
Principal = context.IdentityTokenPrincipal!,
TokenFormat = TokenFormats.Jwt,
diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs
index e09ad676..9fbb13cf 100644
--- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs
+++ b/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.
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 },
- (TokenTypeHints.AccessToken, null or { Length: 0 })
+ (TokenTypeHints.AccessToken, false)
=> new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
diff --git a/src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs b/src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs
index 6d7f4edc..217504cf 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs
@@ -49,6 +49,16 @@ public static partial class OpenIddictValidationEvents
///
public string Token { get; set; } = default!;
+ ///
+ /// Gets or sets a boolean indicating whether the validated token is a reference token.
+ ///
+ public bool IsReferenceToken { get; set; }
+
+ ///
+ /// Gets or sets the authorization entry identifier associated with the token, if applicable.
+ ///
+ public string? AuthorizationId { get; set; }
+
///
/// Gets or sets the token entry identifier associated with the token, if applicable.
///
diff --git a/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs b/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs
index ab9ff035..0107debe 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs
@@ -44,8 +44,10 @@ public static class OpenIddictValidationExtensions
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs
index ea74b34f..6ab2580c 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs
@@ -59,6 +59,22 @@ public static class OpenIddictValidationHandlerFilters
}
}
+ ///
+ /// Represents a filter that excludes the associated handlers if no authorization identifier is resolved from the token.
+ ///
+ public sealed class RequireAuthorizationIdResolved : IOpenIddictValidationHandlerFilter
+ {
+ public ValueTask IsActiveAsync(ValidateTokenContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new(!string.IsNullOrEmpty(context.AuthorizationId));
+ }
+ }
+
///
/// Represents a filter that excludes the associated handlers if local validation is not used.
///
@@ -91,6 +107,22 @@ public static class OpenIddictValidationHandlerFilters
}
}
+ ///
+ /// Represents a filter that excludes the associated handlers if no token identifier is resolved from the token.
+ ///
+ public sealed class RequireTokenIdResolved : IOpenIddictValidationHandlerFilter
+ {
+ public ValueTask IsActiveAsync(ValidateTokenContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new(!string.IsNullOrEmpty(context.TokenId));
+ }
+ }
+
///
/// Represents a filter that excludes the associated handlers if token validation was not enabled.
///
diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
index ddd0d47f..ae518a45 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
@@ -28,7 +28,7 @@ public static partial class OpenIddictValidationHandlers
IntrospectToken.Descriptor,
NormalizeScopeClaims.Descriptor,
MapInternalClaims.Descriptor,
- RestoreReferenceTokenProperties.Descriptor,
+ RestoreTokenEntryProperties.Descriptor,
ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor,
ValidateAudience.Descriptor,
@@ -199,6 +199,7 @@ public static partial class OpenIddictValidationHandlers
// 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
// used to restore the properties associated with the token.
+ context.IsReferenceToken = true;
context.Token = payload;
context.TokenId = await _tokenManager.GetIdAsync(token);
}
@@ -543,16 +544,16 @@ public static partial class OpenIddictValidationHandlers
}
///
- /// 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.
///
- public sealed class RestoreReferenceTokenProperties : IOpenIddictValidationHandler
+ public sealed class RestoreTokenEntryProperties : IOpenIddictValidationHandler
{
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));
///
@@ -562,7 +563,7 @@ public static partial class OpenIddictValidationHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.AddFilter()
.AddFilter()
- .UseScopedHandler()
+ .UseScopedHandler()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@@ -575,20 +576,40 @@ public static partial class OpenIddictValidationHandlers
throw new ArgumentNullException(nameof(context));
}
- if (context.Principal is null || string.IsNullOrEmpty(context.TokenId))
+ if (context.Principal is null)
{
return;
}
- var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
- throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
+ // Extract the token identifier from the authentication principal.
+ //
+ // 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.
- 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));
+ context.Principal
+ .SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
+ .SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
+ .SetAuthorizationId(context.AuthorizationId = await _tokenManager.GetAuthorizationIdAsync(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; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000)
+ .SetOrder(RestoreTokenEntryProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@@ -771,6 +792,7 @@ public static partial class OpenIddictValidationHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.AddFilter()
.AddFilter()
+ .AddFilter()
.UseScopedHandler()
.SetOrder(ValidateAudience.Descriptor.Order + 1_000)
.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(!string.IsNullOrEmpty(context.TokenId), SR.GetResourceString(SR.ID4017));
- var identifier = context.Principal.GetTokenId();
- if (string.IsNullOrEmpty(identifier))
- {
- return;
- }
+ var token = await _tokenManager.FindByIdAsync(context.TokenId) ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
- var token = await _tokenManager.FindByIdAsync(identifier);
- if (token is null || !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(
error: Errors.InvalidToken,
@@ -804,13 +823,6 @@ public static partial class OpenIddictValidationHandlers
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()
.AddFilter()
.AddFilter()
+ .AddFilter()
.UseScopedHandler()
.SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
.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(!string.IsNullOrEmpty(context.AuthorizationId), SR.GetResourceString(SR.ID4018));
- var identifier = context.Principal.GetAuthorizationId();
- if (string.IsNullOrEmpty(identifier))
- {
- return;
- }
-
- var authorization = await _authorizationManager.FindByIdAsync(identifier);
+ var authorization = await _authorizationManager.FindByIdAsync(context.AuthorizationId);
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(
error: Errors.InvalidToken,
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
index 976d7ec4..0f34537c 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
@@ -530,6 +530,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.FindByReferenceIdAsync("g43LaWCUrz2RaLILz2L1bg1bOpMSv1hGrH12IIkB9H4", It.IsAny()))
.ReturnsAsync(token);
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.DeviceCode);
+
mock.Setup(manager => manager.HasTypeAsync(token, TokenTypeHints.DeviceCode, It.IsAny()))
.ReturnsAsync(true);
@@ -559,7 +562,9 @@ public abstract partial class OpenIddictServerIntegrationTests
Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes);
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;
});
@@ -2365,6 +2370,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.AuthorizationCode);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(true);
});
@@ -2448,6 +2456,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(true);
@@ -2522,6 +2533,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(true);
@@ -2596,6 +2610,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(true);
@@ -2683,6 +2700,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny()))
.ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ mock.Setup(manager => manager.GetTypeAsync(tokens[0], It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.AuthorizationCode);
+
mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(true);
@@ -2784,6 +2804,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny()))
.ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ mock.Setup(manager => manager.GetTypeAsync(tokens[0], It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(true);
@@ -2877,6 +2900,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny()))
.ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ mock.Setup(manager => manager.GetTypeAsync(tokens[0], It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(true);
@@ -2970,6 +2996,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetAuthorizationIdAsync(tokens[0], It.IsAny()))
.ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
+ mock.Setup(manager => manager.GetTypeAsync(tokens[0], It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(tokens[0], Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(true);
@@ -3062,6 +3091,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.AuthorizationCode);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(false);
@@ -3149,6 +3181,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(false);
@@ -3270,6 +3305,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.AuthorizationCode);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(false);
@@ -3362,6 +3400,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(false);
@@ -3881,6 +3922,11 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(flow is GrantTypes.AuthorizationCode ?
+ TokenTypeHints.AuthorizationCode :
+ TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
.ReturnsAsync(true);
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
index bb09a511..01e2cb4a 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
@@ -3057,6 +3057,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.AuthorizationCode);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny()))
.ReturnsAsync(true);
@@ -3132,6 +3135,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.GetIdAsync(token, It.IsAny()))
.ReturnsAsync("60FFF7EA-F98E-437B-937E-5073CC313103");
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(false);
@@ -3198,6 +3204,9 @@ public abstract partial class OpenIddictServerIntegrationTests
mock.Setup(manager => manager.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny()))
.ReturnsAsync(token);
+ mock.Setup(manager => manager.GetTypeAsync(token, It.IsAny()))
+ .ReturnsAsync(TokenTypeHints.RefreshToken);
+
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Redeemed, It.IsAny()))
.ReturnsAsync(false);