diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
index 66439e25..46656cc9 100644
--- a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
+++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
@@ -304,6 +304,15 @@ namespace OpenIddict.Abstractions
/// true if the token has the specified status, false otherwise.
ValueTask HasStatusAsync([NotNull] object token, [NotNull] string status, CancellationToken cancellationToken = default);
+ ///
+ /// Determines whether a given token has the specified type.
+ ///
+ /// The token.
+ /// The expected type.
+ /// The that can be used to abort the operation.
+ /// true if the token has the specified type, false otherwise.
+ ValueTask HasTypeAsync([NotNull] object token, [NotNull] string type, CancellationToken cancellationToken = default);
+
///
/// Executes the specified query and returns all the corresponding elements.
///
diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
index e53595fb..5b1b731e 100644
--- a/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
+++ b/src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
@@ -1299,8 +1299,7 @@ namespace OpenIddict.Abstractions
=> principal.GetClaim(Claims.Private.TokenUsage);
///
- /// Gets a boolean value indicating whether the
- /// claims principal corresponds to an access token.
+ /// Gets a boolean value indicating whether the claims principal corresponds to an access token.
///
/// The claims principal.
/// true if the principal corresponds to an access token.
@@ -1315,8 +1314,7 @@ namespace OpenIddict.Abstractions
}
///
- /// Gets a boolean value indicating whether the
- /// claims principal corresponds to an access token.
+ /// Gets a boolean value indicating whether the claims principal corresponds to an access token.
///
/// The claims principal.
/// true if the principal corresponds to an authorization code.
@@ -1331,8 +1329,22 @@ namespace OpenIddict.Abstractions
}
///
- /// Gets a boolean value indicating whether the
- /// claims principal corresponds to an identity token.
+ /// Gets a boolean value indicating whether the claims principal corresponds to a device code.
+ ///
+ /// The claims principal.
+ /// true if the principal corresponds to a device code.
+ public static bool IsDeviceCode([NotNull] this ClaimsPrincipal principal)
+ {
+ if (principal == null)
+ {
+ throw new ArgumentNullException(nameof(principal));
+ }
+
+ return string.Equals(principal.GetTokenUsage(), TokenUsages.DeviceCode, StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ /// Gets a boolean value indicating whether the claims principal corresponds to an identity token.
///
/// The claims principal.
/// true if the principal corresponds to an identity token.
@@ -1347,8 +1359,7 @@ namespace OpenIddict.Abstractions
}
///
- /// Gets a boolean value indicating whether the
- /// claims principal corresponds to a refresh token.
+ /// Gets a boolean value indicating whether the claims principal corresponds to a refresh token.
///
/// The claims principal.
/// true if the principal corresponds to a refresh token.
@@ -1362,6 +1373,21 @@ namespace OpenIddict.Abstractions
return string.Equals(principal.GetTokenUsage(), TokenUsages.RefreshToken, StringComparison.OrdinalIgnoreCase);
}
+ ///
+ /// Gets a boolean value indicating whether the claims principal corresponds to a user code.
+ ///
+ /// The claims principal.
+ /// true if the principal corresponds to a user code.
+ public static bool IsUserCode([NotNull] this ClaimsPrincipal principal)
+ {
+ if (principal == null)
+ {
+ throw new ArgumentNullException(nameof(principal));
+ }
+
+ return string.Equals(principal.GetTokenUsage(), TokenUsages.UserCode, StringComparison.OrdinalIgnoreCase);
+ }
+
///
/// Determines whether the claims principal contains at least one audience.
///
diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
index 60217fa5..2b177700 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
@@ -757,6 +757,28 @@ namespace OpenIddict.Core
return string.Equals(await Store.GetStatusAsync(token, cancellationToken), status, StringComparison.OrdinalIgnoreCase);
}
+ ///
+ /// Determines whether a given token has the specified type.
+ ///
+ /// The token.
+ /// The expected type.
+ /// The that can be used to abort the operation.
+ /// true if the token has the specified type, false otherwise.
+ public virtual async ValueTask HasTypeAsync([NotNull] TToken token, [NotNull] string type, CancellationToken cancellationToken = default)
+ {
+ if (token == null)
+ {
+ throw new ArgumentNullException(nameof(token));
+ }
+
+ if (string.IsNullOrEmpty(type))
+ {
+ throw new ArgumentException("The type cannot be null or empty.", nameof(type));
+ }
+
+ return string.Equals(await Store.GetTypeAsync(token, cancellationToken), type, StringComparison.OrdinalIgnoreCase);
+ }
+
///
/// Executes the specified query and returns all the corresponding elements.
///
@@ -1366,6 +1388,9 @@ namespace OpenIddict.Core
ValueTask IOpenIddictTokenManager.HasStatusAsync(object token, string status, CancellationToken cancellationToken)
=> HasStatusAsync((TToken) token, status, cancellationToken);
+ ValueTask IOpenIddictTokenManager.HasTypeAsync(object token, string type, CancellationToken cancellationToken)
+ => HasTypeAsync((TToken) token, type, cancellationToken);
+
IAsyncEnumerable IOpenIddictTokenManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken)
=> ListAsync(count, offset, cancellationToken).OfType();
diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs
index 2caa348a..cbbf2d30 100644
--- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs
+++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs
@@ -17,7 +17,6 @@ using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
-using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants;
using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes;
using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHandlerFilters;
using static OpenIddict.Server.OpenIddictServerEvents;
@@ -87,9 +86,11 @@ namespace OpenIddict.Server.DataProtection
// If the token cannot be validated, don't return an error to allow another handle to validate it.
var principal = !string.IsNullOrEmpty(context.TokenType) ?
ValidateToken(context.Token, context.TokenType) :
- ValidateToken(context.Token, TokenUsages.AccessToken) ??
- ValidateToken(context.Token, TokenUsages.RefreshToken) ??
- ValidateToken(context.Token, TokenUsages.AuthorizationCode);
+ ValidateToken(context.Token, TokenUsages.AccessToken) ??
+ ValidateToken(context.Token, TokenUsages.RefreshToken) ??
+ ValidateToken(context.Token, TokenUsages.AuthorizationCode) ??
+ ValidateToken(context.Token, TokenUsages.DeviceCode) ??
+ ValidateToken(context.Token, TokenUsages.UserCode);
if (principal == null)
{
return default;
@@ -120,7 +121,7 @@ namespace OpenIddict.Server.DataProtection
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server },
TokenUsages.UserCode when !context.Options.DisableTokenStorage
- => new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server, },
+ => new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server },
TokenUsages.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
TokenUsages.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server },
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
index b97b9a09..afa2902f 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
@@ -48,6 +48,7 @@ namespace OpenIddict.Server
ValidateClientSecret.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateToken.Descriptor,
+ ValidateTokenType.Descriptor,
ValidateAuthorizedParty.Descriptor,
/*
@@ -808,6 +809,51 @@ namespace OpenIddict.Server
}
}
+ ///
+ /// Contains the logic responsible of rejecting introspection requests that specify an unsupported token.
+ ///
+ public class ValidateTokenType : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateToken.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ValidateIntrospectionRequestContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (!context.Principal.IsAccessToken() && !context.Principal.IsAuthorizationCode() &&
+ !context.Principal.IsIdentityToken() && !context.Principal.IsRefreshToken())
+ {
+ context.Logger.LogError("The introspection request was rejected because " +
+ "the received token was of an unsupported type.");
+
+ context.Reject(
+ error: Errors.UnsupportedTokenType,
+ description: "The specified token cannot be introspected.");
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible of rejecting introspection requests that specify a token
/// that cannot be introspected by the client application sending the introspection requests.
@@ -824,7 +870,7 @@ namespace OpenIddict.Server
// In this case, the returned claims are limited by AttachApplicationClaims to limit exposure.
.AddFilter()
.UseSingletonHandler()
- .SetOrder(ValidateToken.Descriptor.Order + 1_000)
+ .SetOrder(ValidateTokenType.Descriptor.Order + 1_000)
.Build();
///
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
index ce0e4e1f..16894b5b 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
@@ -41,6 +41,7 @@ namespace OpenIddict.Server
ValidateClientSecret.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateToken.Descriptor,
+ ValidateTokenType.Descriptor,
ValidateAuthorizedParty.Descriptor,
/*
@@ -754,6 +755,64 @@ namespace OpenIddict.Server
}
}
+ ///
+ /// Contains the logic responsible of rejecting revocation requests that specify an unsupported token.
+ ///
+ public class ValidateTokenType : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateToken.Descriptor.Order + 1_000)
+ .Build();
+
+ ///
+ /// Processes the event.
+ ///
+ /// The context associated with the event to process.
+ ///
+ /// A that can be used to monitor the asynchronous operation.
+ ///
+ public ValueTask HandleAsync([NotNull] ValidateRevocationRequestContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (!context.Principal.IsAccessToken() &&
+ !context.Principal.IsAuthorizationCode() &&
+ !context.Principal.IsRefreshToken())
+ {
+ context.Logger.LogError("The revocation request was rejected because " +
+ "the received token was of an unsupported type.");
+
+ context.Reject(
+ error: Errors.UnsupportedTokenType,
+ description: "This token cannot be revoked.");
+
+ return default;
+ }
+
+ // If the received token is an access token, return an error if reference tokens are not enabled.
+ if (context.Principal.IsAccessToken() && !context.Options.UseReferenceAccessTokens)
+ {
+ context.Logger.LogError("The revocation request was rejected because the access token was not revocable.");
+
+ context.Reject(
+ error: Errors.UnsupportedTokenType,
+ description: "The specified token cannot be revoked.");
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible of rejecting revocation requests that specify a token
/// that cannot be revoked by the client application sending the revocation requests.
@@ -770,7 +829,7 @@ namespace OpenIddict.Server
// In this case, the risk is quite limited as claims are never returned by this endpoint.
.AddFilter()
.UseSingletonHandler()
- .SetOrder(ValidateToken.Descriptor.Order + 1_000)
+ .SetOrder(ValidateTokenType.Descriptor.Order + 1_000)
.Build();
///
@@ -949,31 +1008,6 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- // If the received token is not an authorization code or a refresh token,
- // return an error to indicate that the token cannot be revoked.
- if (context.Principal.IsIdentityToken())
- {
- context.Logger.LogError("The revocation request was rejected because identity tokens are not revocable.");
-
- context.Reject(
- error: Errors.UnsupportedTokenType,
- description: "The specified token cannot be revoked.");
-
- return;
- }
-
- // If the received token is an access token, return an error if reference tokens are not enabled.
- if (context.Principal.IsAccessToken() && !context.Options.UseReferenceAccessTokens)
- {
- context.Logger.LogError("The revocation request was rejected because the access token was not revocable.");
-
- context.Reject(
- error: Errors.UnsupportedTokenType,
- description: "The specified token cannot be revoked.");
-
- return;
- }
-
// Extract the token identifier from the authentication principal.
var identifier = context.Principal.GetInternalTokenId();
if (string.IsNullOrEmpty(identifier))
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index 50809be8..e0385d36 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
@@ -188,6 +188,8 @@ namespace OpenIddict.Server
OpenIddictServerEndpointType.Authorization => (context.Request.IdTokenHint, TokenUsages.IdToken),
OpenIddictServerEndpointType.Logout => (context.Request.IdTokenHint, TokenUsages.IdToken),
+ // Generic tokens received by the introspection and revocation can be of any type.
+ // Additional token type filtering is made by the endpoint themselves, if needed.
OpenIddictServerEndpointType.Introspection => (context.Request.Token, null),
OpenIddictServerEndpointType.Revocation => (context.Request.Token, null),
@@ -334,6 +336,7 @@ namespace OpenIddict.Server
return;
}
+ // If the type associated with the token entry doesn't match the expected type, return an error.
if (!string.IsNullOrEmpty(context.TokenType) &&
!string.Equals(context.TokenType, await _tokenManager.GetTypeAsync(token)))
{
@@ -416,41 +419,18 @@ namespace OpenIddict.Server
return;
}
- // If the token cannot be validated, don't return an error to allow another handle to validate it.
- var result = !string.IsNullOrEmpty(context.TokenType) ?
- await ValidateTokenAsync(context.Token, context.TokenType) :
- await ValidateAnyTokenAsync(context.Token);
- if (result.ClaimsIdentity == null)
- {
- return;
- }
+ var parameters = context.Options.TokenValidationParameters.Clone();
+ parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
+ parameters.IssuerSigningKeys = context.Options.SigningCredentials.Select(credentials => credentials.Key);
+ parameters.TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key);
- // Attach the principal extracted from the token to the parent event context.
- context.Principal = new ClaimsPrincipal(result.ClaimsIdentity);
-
- // Store the token type as a special private claim.
- context.Principal.SetClaim(Claims.Private.TokenUsage, ((JsonWebToken) result.SecurityToken).Typ switch
+ // If a specific token type is expected, override the default valid types to reject
+ // security tokens whose "typ" header doesn't match the expected token type.
+ if (!string.IsNullOrEmpty(context.TokenType))
{
- JsonWebTokenTypes.AccessToken => TokenUsages.AccessToken,
- JsonWebTokenTypes.IdentityToken => TokenUsages.IdToken,
-
- JsonWebTokenTypes.Private.AuthorizationCode => TokenUsages.AuthorizationCode,
- JsonWebTokenTypes.Private.DeviceCode => TokenUsages.DeviceCode,
- JsonWebTokenTypes.Private.RefreshToken => TokenUsages.RefreshToken,
- JsonWebTokenTypes.Private.UserCode => TokenUsages.UserCode,
-
- _ => throw new InvalidOperationException("The token type is not supported.")
- });
-
- context.Logger.LogTrace("The token '{Token}' was successfully validated and the following claims " +
- "could be extracted: {Claims}.", context.Token, context.Principal.Claims);
-
- async ValueTask ValidateTokenAsync(string token, string type)
- {
- var parameters = context.Options.TokenValidationParameters.Clone();
parameters.ValidTypes = new[]
{
- type switch
+ context.TokenType switch
{
TokenUsages.AccessToken => JsonWebTokenTypes.AccessToken,
TokenUsages.IdToken => JsonWebTokenTypes.IdentityToken,
@@ -463,74 +443,36 @@ namespace OpenIddict.Server
_ => throw new InvalidOperationException("The token type is not supported.")
}
};
- parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
-
- parameters.IssuerSigningKeys = type switch
- {
- TokenUsages.AccessToken => context.Options.SigningCredentials.Select(credentials => credentials.Key),
- TokenUsages.AuthorizationCode => context.Options.SigningCredentials.Select(credentials => credentials.Key),
- TokenUsages.DeviceCode => context.Options.SigningCredentials.Select(credentials => credentials.Key),
- TokenUsages.RefreshToken => context.Options.SigningCredentials.Select(credentials => credentials.Key),
- TokenUsages.UserCode => context.Options.SigningCredentials.Select(credentials => credentials.Key),
-
- TokenUsages.IdToken => context.Options.SigningCredentials
- .Select(credentials => credentials.Key)
- .OfType(),
-
- _ => Array.Empty()
- };
-
- parameters.TokenDecryptionKeys = type switch
- {
- TokenUsages.AuthorizationCode => context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
- TokenUsages.DeviceCode => context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
- TokenUsages.RefreshToken => context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
- TokenUsages.UserCode => context.Options.EncryptionCredentials.Select(credentials => credentials.Key),
-
- TokenUsages.AccessToken => context.Options.EncryptionCredentials
- .Select(credentials => credentials.Key)
- .Where(key => key is SymmetricSecurityKey),
-
- _ => Array.Empty()
- };
+ }
- var result = await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(token, parameters);
- if (!result.IsValid)
- {
- context.Logger.LogTrace(result.Exception, "An error occurred while validating the token '{Token}'.", token);
- }
+ // If the token cannot be validated, don't return an error to allow another handle to validate it.
+ var result = await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(context.Token, parameters);
+ if (result.ClaimsIdentity == null || !result.IsValid)
+ {
+ context.Logger.LogTrace(result.Exception, "An error occurred while validating the token '{Token}'.", context.Token);
- return result;
+ return;
}
- async ValueTask ValidateAnyTokenAsync(string token)
- {
- var result = await ValidateTokenAsync(token, TokenUsages.AccessToken);
- if (result.IsValid)
- {
- return result;
- }
+ // Attach the principal extracted from the token to the parent event context.
+ context.Principal = new ClaimsPrincipal(result.ClaimsIdentity);
- result = await ValidateTokenAsync(token, TokenUsages.RefreshToken);
- if (result.IsValid)
- {
- return result;
- }
+ // Store the token type as a special private claim.
+ context.Principal.SetClaim(Claims.Private.TokenUsage, ((JsonWebToken) result.SecurityToken).Typ switch
+ {
+ JsonWebTokenTypes.AccessToken => TokenUsages.AccessToken,
+ JsonWebTokenTypes.IdentityToken => TokenUsages.IdToken,
- result = await ValidateTokenAsync(token, TokenUsages.AuthorizationCode);
- if (result.IsValid)
- {
- return result;
- }
+ JsonWebTokenTypes.Private.AuthorizationCode => TokenUsages.AuthorizationCode,
+ JsonWebTokenTypes.Private.DeviceCode => TokenUsages.DeviceCode,
+ JsonWebTokenTypes.Private.RefreshToken => TokenUsages.RefreshToken,
+ JsonWebTokenTypes.Private.UserCode => TokenUsages.UserCode,
- result = await ValidateTokenAsync(token, TokenUsages.IdToken);
- if (result.IsValid)
- {
- return result;
- }
+ _ => throw new InvalidOperationException("The token type is not supported.")
+ });
- return new TokenValidationResult { IsValid = false };
- }
+ context.Logger.LogTrace("The token '{Token}' was successfully validated and the following claims " +
+ "could be extracted: {Claims}.", context.Token, context.Principal.Claims);
}
}
diff --git a/src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs b/src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs
index 7dbeedeb..11efcf82 100644
--- a/src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs
+++ b/src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs
@@ -73,11 +73,6 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(parameters));
}
- if (parameters.ValidTypes == null || !parameters.ValidTypes.Any())
- {
- throw new InvalidOperationException("The valid token types collection cannot be empty.");
- }
-
if (!CanReadToken(token))
{
return new ValueTask(new TokenValidationResult
diff --git a/src/OpenIddict.Server/OpenIddictServerOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs
index c884f19d..8eebd539 100644
--- a/src/OpenIddict.Server/OpenIddictServerOptions.cs
+++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
+using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Server
{
@@ -111,7 +112,17 @@ namespace OpenIddict.Server
RoleClaimType = OpenIddictConstants.Claims.Role,
// Note: audience and lifetime are manually validated by OpenIddict itself.
ValidateAudience = false,
- ValidateLifetime = false
+ ValidateLifetime = false,
+ // Note: valid types can be overriden by OpenIddict depending on the received request.
+ ValidTypes = new[]
+ {
+ JsonWebTokenTypes.AccessToken,
+ JsonWebTokenTypes.IdentityToken,
+ JsonWebTokenTypes.Private.AuthorizationCode,
+ JsonWebTokenTypes.Private.DeviceCode,
+ JsonWebTokenTypes.Private.RefreshToken,
+ JsonWebTokenTypes.Private.UserCode
+ }
};
///
diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
index 628dfa73..80cc6b9a 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
@@ -212,7 +212,7 @@ namespace OpenIddict.Validation
// If the token cannot be validated, don't return an error to allow another handle to validate it.
var result = await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(context.Token, parameters);
- if (result.ClaimsIdentity == null)
+ if (result.ClaimsIdentity == null || !result.IsValid)
{
context.Logger.LogTrace(result.Exception, "An error occurred while validating the token '{Token}'.", context.Token);
diff --git a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
index 601fccbd..d0e71701 100644
--- a/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
+++ b/test/OpenIddict.Abstractions.Tests/Primitives/OpenIddictExtensionsTests.cs
@@ -2106,8 +2106,10 @@ namespace OpenIddict.Abstractions.Tests.Primitives
[InlineData("unknown", false)]
[InlineData(OpenIddictConstants.TokenUsages.AccessToken, true)]
[InlineData(OpenIddictConstants.TokenUsages.AuthorizationCode, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.DeviceCode, false)]
[InlineData(OpenIddictConstants.TokenUsages.IdToken, false)]
[InlineData(OpenIddictConstants.TokenUsages.RefreshToken, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.UserCode, false)]
public void IsAccessToken_ReturnsExpectedResult(string usage, bool result)
{
// Arrange
@@ -2137,8 +2139,10 @@ namespace OpenIddict.Abstractions.Tests.Primitives
[InlineData("unknown", false)]
[InlineData(OpenIddictConstants.TokenUsages.AccessToken, false)]
[InlineData(OpenIddictConstants.TokenUsages.AuthorizationCode, true)]
+ [InlineData(OpenIddictConstants.TokenUsages.DeviceCode, false)]
[InlineData(OpenIddictConstants.TokenUsages.IdToken, false)]
[InlineData(OpenIddictConstants.TokenUsages.RefreshToken, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.UserCode, false)]
public void IsAuthorizationCode_ReturnsExpectedResult(string usage, bool result)
{
// Arrange
@@ -2151,6 +2155,39 @@ namespace OpenIddict.Abstractions.Tests.Primitives
Assert.Equal(result, principal.IsAuthorizationCode());
}
+ [Fact]
+ public void IsDeviceCode_ThrowsAnExceptionForNullPrincipal()
+ {
+ // Arrange
+ var principal = (ClaimsPrincipal) null;
+
+ // Act and assert
+ var exception = Assert.Throws(() => principal.IsDeviceCode());
+
+ Assert.Equal("principal", exception.ParamName);
+ }
+
+ [Theory]
+ [InlineData(null, false)]
+ [InlineData("unknown", false)]
+ [InlineData(OpenIddictConstants.TokenUsages.AccessToken, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.AuthorizationCode, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.DeviceCode, true)]
+ [InlineData(OpenIddictConstants.TokenUsages.IdToken, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.RefreshToken, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.UserCode, false)]
+ public void IsDeviceCode_ReturnsExpectedResult(string usage, bool result)
+ {
+ // Arrange
+ var identity = new ClaimsIdentity();
+ var principal = new ClaimsPrincipal(identity);
+
+ principal.SetClaim(OpenIddictConstants.Claims.Private.TokenUsage, usage);
+
+ // Act and assert
+ Assert.Equal(result, principal.IsDeviceCode());
+ }
+
[Fact]
public void IsIdentityToken_ThrowsAnExceptionForNullPrincipal()
{
@@ -2168,8 +2205,10 @@ namespace OpenIddict.Abstractions.Tests.Primitives
[InlineData("unknown", false)]
[InlineData(OpenIddictConstants.TokenUsages.AccessToken, false)]
[InlineData(OpenIddictConstants.TokenUsages.AuthorizationCode, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.DeviceCode, false)]
[InlineData(OpenIddictConstants.TokenUsages.IdToken, true)]
[InlineData(OpenIddictConstants.TokenUsages.RefreshToken, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.UserCode, false)]
public void IsIdentityToken_ReturnsExpectedResult(string usage, bool result)
{
// Arrange
@@ -2213,6 +2252,39 @@ namespace OpenIddict.Abstractions.Tests.Primitives
Assert.Equal(result, principal.IsRefreshToken());
}
+ [Fact]
+ public void IsUserCode_ThrowsAnExceptionForNullPrincipal()
+ {
+ // Arrange
+ var principal = (ClaimsPrincipal) null;
+
+ // Act and assert
+ var exception = Assert.Throws(() => principal.IsUserCode());
+
+ Assert.Equal("principal", exception.ParamName);
+ }
+
+ [Theory]
+ [InlineData(null, false)]
+ [InlineData("unknown", false)]
+ [InlineData(OpenIddictConstants.TokenUsages.AccessToken, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.AuthorizationCode, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.DeviceCode, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.IdToken, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.RefreshToken, false)]
+ [InlineData(OpenIddictConstants.TokenUsages.UserCode, true)]
+ public void IsUserCode_ReturnsExpectedResult(string usage, bool result)
+ {
+ // Arrange
+ var identity = new ClaimsIdentity();
+ var principal = new ClaimsPrincipal(identity);
+
+ principal.SetClaim(OpenIddictConstants.Claims.Private.TokenUsage, usage);
+
+ // Act and assert
+ Assert.Equal(result, principal.IsUserCode());
+ }
+
[Theory]
[InlineData(null)]
[InlineData("")]