diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
index e9bcb2f1..f3b020da 100644
--- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs
+++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs
@@ -1682,6 +1682,21 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => options.Issuer = address);
}
+ ///
+ /// Updates the token validation parameters using the specified delegate.
+ ///
+ /// The configuration delegate.
+ /// The .
+ public OpenIddictServerBuilder SetTokenValidationParameters([NotNull] Action configuration)
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ return Configure(options => configuration(options.TokenValidationParameters));
+ }
+
///
/// Configures OpenIddict to use reference tokens, so that authorization codes,
/// access tokens and refresh tokens are stored as ciphertext in the database
diff --git a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs
index d83e339a..ca3c465e 100644
--- a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs
+++ b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs
@@ -35,7 +35,7 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(options));
}
- if (options.SecurityTokenHandler == null)
+ if (options.JsonWebTokenHandler == null)
{
throw new InvalidOperationException("The security token handler cannot be null.");
}
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index 9acc60a4..79746325 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
@@ -77,7 +77,9 @@ namespace OpenIddict.Server
AttachSelfContainedRefreshToken.Descriptor,
AttachTokenDigests.Descriptor,
- AttachSelfContainedIdentityToken.Descriptor)
+ AttachSelfContainedIdentityToken.Descriptor,
+
+ AttachAdditionalProperties.Descriptor)
.AddRange(Authentication.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers)
@@ -229,7 +231,7 @@ namespace OpenIddict.Server
}
// If the token cannot be validated, don't return an error to allow another handle to validate it.
- if (!context.Options.SecurityTokenHandler.CanReadToken(payload))
+ if (!context.Options.JsonWebTokenHandler.CanReadToken(payload))
{
return;
}
@@ -267,15 +269,9 @@ namespace OpenIddict.Server
async ValueTask ValidateTokenAsync(string token, string type)
{
- var parameters = new TokenValidationParameters
- {
- NameClaimType = Claims.Name,
- PropertyBag = new Dictionary { [Claims.Private.TokenUsage] = type },
- RoleClaimType = Claims.Role,
- ValidIssuer = context.Issuer?.AbsoluteUri,
- ValidateAudience = false,
- ValidateLifetime = false
- };
+ var parameters = context.Options.TokenValidationParameters.Clone();
+ parameters.PropertyBag = new Dictionary { [Claims.Private.TokenUsage] = type };
+ parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
parameters.IssuerSigningKeys = type switch
{
@@ -298,7 +294,7 @@ namespace OpenIddict.Server
_ => Array.Empty()
};
- return await context.Options.SecurityTokenHandler.ValidateTokenStringAsync(token, parameters);
+ return await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(token, parameters);
}
async ValueTask ValidateAnyTokenAsync(string token)
@@ -406,7 +402,7 @@ namespace OpenIddict.Server
};
// If the token cannot be validated, don't return an error to allow another handle to validate it.
- if (string.IsNullOrEmpty(token) || !context.Options.SecurityTokenHandler.CanReadToken(token))
+ if (string.IsNullOrEmpty(token) || !context.Options.JsonWebTokenHandler.CanReadToken(token))
{
return;
}
@@ -450,15 +446,9 @@ namespace OpenIddict.Server
async ValueTask ValidateTokenAsync(string token, string type)
{
- var parameters = new TokenValidationParameters
- {
- NameClaimType = Claims.Name,
- PropertyBag = new Dictionary { [Claims.Private.TokenUsage] = type },
- RoleClaimType = Claims.Role,
- ValidIssuer = context.Issuer?.AbsoluteUri,
- ValidateAudience = false,
- ValidateLifetime = false
- };
+ var parameters = context.Options.TokenValidationParameters.Clone();
+ parameters.PropertyBag = new Dictionary { [Claims.Private.TokenUsage] = type };
+ parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
parameters.IssuerSigningKeys = type switch
{
@@ -485,7 +475,7 @@ namespace OpenIddict.Server
_ => Array.Empty()
};
- return await context.Options.SecurityTokenHandler.ValidateTokenStringAsync(token, parameters);
+ return await context.Options.JsonWebTokenHandler.ValidateTokenStringAsync(token, parameters);
}
async ValueTask ValidateAnyTokenAsync(string token)
@@ -1413,6 +1403,14 @@ namespace OpenIddict.Server
return true;
}
+ // Never include the public or internal token identifiers to ensure the identifiers
+ // that are automatically inherited from the parent token are not reused for the new token.
+ if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
// Always exclude private claims, whose values must generally be kept secret.
if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase))
{
@@ -1437,11 +1435,8 @@ namespace OpenIddict.Server
claim.Properties.Remove(OpenIddictConstants.Properties.Destinations);
}
- // Note: the internal token identifier is automatically reset to ensure
- // the identifier inherited from the parent token is not automatically reused.
- principal.SetClaim(Claims.JwtId, Guid.NewGuid().ToString())
- .SetCreationDate(DateTimeOffset.UtcNow)
- .SetInternalTokenId(identifier: null);
+ principal.SetClaim(Claims.JwtId, Guid.NewGuid().ToString());
+ principal.SetCreationDate(DateTimeOffset.UtcNow);
var lifetime = context.Principal.GetAccessTokenLifetime() ?? context.Options.AccessTokenLifetime;
if (lifetime.HasValue)
@@ -1512,12 +1507,24 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- // Note: the internal token identifier is automatically reset to ensure
- // the identifier inherited from the parent token is not automatically reused.
- var principal = context.Principal.Clone(_ => true)
- .SetClaim(Claims.JwtId, Guid.NewGuid().ToString())
- .SetCreationDate(DateTimeOffset.UtcNow)
- .SetInternalTokenId(identifier: null);
+ // Create a new principal containing only the filtered claims.
+ // Actors identities are also filtered (delegation scenarios).
+ var principal = context.Principal.Clone(claim =>
+ {
+ // Never include the public or internal token identifiers to ensure the identifiers
+ // that are automatically inherited from the parent token are not reused for the new token.
+ if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // Other claims are always included in the authorization code, even private claims.
+ return true;
+ });
+
+ principal.SetClaim(Claims.JwtId, Guid.NewGuid().ToString());
+ principal.SetCreationDate(DateTimeOffset.UtcNow);
var lifetime = context.Principal.GetAuthorizationCodeLifetime() ?? context.Options.AuthorizationCodeLifetime;
if (lifetime.HasValue)
@@ -1587,12 +1594,24 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
- // Note: the internal token identifier is automatically reset to ensure
- // the identifier inherited from the parent token is not automatically reused.
- var principal = context.Principal.Clone(_ => true)
- .SetClaim(Claims.JwtId, Guid.NewGuid().ToString())
- .SetCreationDate(DateTimeOffset.UtcNow)
- .SetInternalTokenId(identifier: null);
+ // Create a new principal containing only the filtered claims.
+ // Actors identities are also filtered (delegation scenarios).
+ var principal = context.Principal.Clone(claim =>
+ {
+ // Never include the public or internal token identifiers to ensure the identifiers
+ // that are automatically inherited from the parent token are not reused for the new token.
+ if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ // Other claims are always included in the refresh token, even private claims.
+ return true;
+ });
+
+ principal.SetClaim(Claims.JwtId, Guid.NewGuid().ToString());
+ principal.SetCreationDate(DateTimeOffset.UtcNow);
// When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed
// and must exactly match the expiration date of the refresh token used in the token request.
@@ -1663,6 +1682,14 @@ namespace OpenIddict.Server
return true;
}
+ // Never include the public or internal token identifiers to ensure the identifiers
+ // that are automatically inherited from the parent token are not reused for the new token.
+ if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
// Always exclude private claims by default, whose values must generally be kept secret.
if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase))
{
@@ -1687,11 +1714,8 @@ namespace OpenIddict.Server
claim.Properties.Remove(OpenIddictConstants.Properties.Destinations);
}
- // Note: the internal token identifier is automatically reset to ensure
- // the identifier inherited from the parent token is not automatically reused.
- principal.SetClaim(Claims.JwtId, Guid.NewGuid().ToString())
- .SetCreationDate(DateTimeOffset.UtcNow)
- .SetInternalTokenId(identifier: null);
+ principal.SetClaim(Claims.JwtId, Guid.NewGuid().ToString());
+ principal.SetCreationDate(DateTimeOffset.UtcNow);
var lifetime = context.Principal.GetIdentityTokenLifetime() ?? context.Options.IdentityTokenLifetime;
if (lifetime.HasValue)
@@ -2058,7 +2082,7 @@ namespace OpenIddict.Server
descriptor.ApplicationId = await _applicationManager.GetIdAsync(application);
}
- descriptor.Payload = await context.Options.SecurityTokenHandler.CreateTokenFromDescriptorAsync(
+ descriptor.Payload = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.AccessToken },
@@ -2169,7 +2193,7 @@ namespace OpenIddict.Server
descriptor.ApplicationId = await _applicationManager.GetIdAsync(application);
}
- descriptor.Payload = await context.Options.SecurityTokenHandler.CreateTokenFromDescriptorAsync(
+ descriptor.Payload = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.AuthorizationCode },
@@ -2280,7 +2304,7 @@ namespace OpenIddict.Server
descriptor.ApplicationId = await _applicationManager.GetIdAsync(application);
}
- descriptor.Payload = await context.Options.SecurityTokenHandler.CreateTokenFromDescriptorAsync(
+ descriptor.Payload = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.RefreshToken },
@@ -2510,7 +2534,7 @@ namespace OpenIddict.Server
return;
}
- context.Response.AccessToken = await context.Options.SecurityTokenHandler.CreateTokenFromDescriptorAsync(
+ context.Response.AccessToken = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.AccessToken },
@@ -2561,7 +2585,7 @@ namespace OpenIddict.Server
return;
}
- context.Response.Code = await context.Options.SecurityTokenHandler.CreateTokenFromDescriptorAsync(
+ context.Response.Code = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.AuthorizationCode },
@@ -2612,7 +2636,7 @@ namespace OpenIddict.Server
return;
}
- context.Response.RefreshToken = await context.Options.SecurityTokenHandler.CreateTokenFromDescriptorAsync(
+ context.Response.RefreshToken = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.RefreshToken },
@@ -2793,7 +2817,7 @@ namespace OpenIddict.Server
return;
}
- context.Response.IdToken = await context.Options.SecurityTokenHandler.CreateTokenFromDescriptorAsync(
+ context.Response.IdToken = await context.Options.JsonWebTokenHandler.CreateTokenFromDescriptorAsync(
new SecurityTokenDescriptor
{
Claims = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.IdToken },
@@ -2850,7 +2874,7 @@ namespace OpenIddict.Server
// If the granted access token scopes differ from the requested scopes, return the granted scopes
// list as a parameter to inform the client application of the fact the scopes set will be reduced.
- if (context.Request.IsAuthorizationCodeGrantType() ||
+ if ((context.EndpointType == OpenIddictServerEndpointType.Token && context.Request.IsAuthorizationCodeGrantType()) ||
!context.AccessTokenPrincipal.GetScopes().SetEquals(context.Request.GetScopes()))
{
context.Response.Scope = string.Join(" ", context.AccessTokenPrincipal.GetScopes());
diff --git a/src/OpenIddict.Server/OpenIddictServerTokenHandler.cs b/src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs
similarity index 98%
rename from src/OpenIddict.Server/OpenIddictServerTokenHandler.cs
rename to src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs
index c549a282..1b9c1dd2 100644
--- a/src/OpenIddict.Server/OpenIddictServerTokenHandler.cs
+++ b/src/OpenIddict.Server/OpenIddictServerJsonWebTokenHandler.cs
@@ -15,7 +15,7 @@ using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Server
{
- public class OpenIddictServerTokenHandler : JsonWebTokenHandler
+ public class OpenIddictServerJsonWebTokenHandler : JsonWebTokenHandler
{
public ValueTask CreateTokenFromDescriptorAsync(SecurityTokenDescriptor descriptor)
{
diff --git a/src/OpenIddict.Server/OpenIddictServerOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs
index 3a8c5709..05055cf7 100644
--- a/src/OpenIddict.Server/OpenIddictServerOptions.cs
+++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs
@@ -84,13 +84,26 @@ namespace OpenIddict.Server
public IList UserinfoEndpointUris { get; } = new List();
///
- /// Gets or sets the security token handler used to protect and unprotect tokens.
+ /// Gets or sets the JWT handler used to protect and unprotect tokens.
///
- public OpenIddictServerTokenHandler SecurityTokenHandler { get; set; } = new OpenIddictServerTokenHandler
+ public OpenIddictServerJsonWebTokenHandler JsonWebTokenHandler { get; set; } = new OpenIddictServerJsonWebTokenHandler
{
SetDefaultTimesOnTokenCreation = false
};
+ ///
+ /// Gets the token validation parameters used by the OpenIddict server services.
+ ///
+ public TokenValidationParameters TokenValidationParameters { get; } = new TokenValidationParameters
+ {
+ ClockSkew = TimeSpan.Zero,
+ NameClaimType = OpenIddictConstants.Claims.Name,
+ RoleClaimType = OpenIddictConstants.Claims.Role,
+ // Note: audience and lifetime are manually validated by OpenIddict itself.
+ ValidateAudience = false,
+ ValidateLifetime = false
+ };
+
///
/// Gets or sets the period of time the authorization codes remain valid after being issued.
/// While not recommended, this property can be set to null to issue codes that never expire.
diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs
index 3a48f608..815b8013 100644
--- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs
+++ b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs
@@ -34,10 +34,6 @@ namespace OpenIddict.Validation.DataProtection
throw new ArgumentNullException(nameof(options));
}
- // Use empty token validation parameters to ensure the core OpenIddict validation components
- // don't throw an exception stating that an issuer or a metadata address was not set.
- options.TokenValidationParameters = new TokenValidationParameters();
-
// Register the built-in event handlers used by the OpenIddict Data Protection validation components.
foreach (var handler in OpenIddictValidationDataProtectionHandlers.DefaultHandlers)
{
diff --git a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs
index b71bd62d..4f91b2e7 100644
--- a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs
+++ b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs
@@ -44,11 +44,9 @@ namespace OpenIddict.Validation.ServerIntegration
options.Issuer = _options.CurrentValue.Issuer;
// Import the token validation parameters from the server configuration.
- options.TokenValidationParameters = new TokenValidationParameters
- {
- IssuerSigningKeys = (from credentials in _options.CurrentValue.SigningCredentials
- select credentials.Key).ToList()
- };
+ options.TokenValidationParameters.IssuerSigningKeys =
+ (from credentials in _options.CurrentValue.SigningCredentials
+ select credentials.Key).ToList();
// Import the symmetric encryption keys from the server configuration.
foreach (var credentials in _options.CurrentValue.EncryptionCredentials)
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs
index 5b74fa16..5088151d 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpExtensions.cs
@@ -12,6 +12,7 @@ using Microsoft.Extensions.Http;
using Microsoft.Extensions.Options;
using OpenIddict.Validation;
using OpenIddict.Validation.SystemNetHttp;
+using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlerFilters;
using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlers;
namespace Microsoft.Extensions.DependencyInjection
@@ -41,6 +42,9 @@ namespace Microsoft.Extensions.DependencyInjection
// Note: the order used here is not important, as the actual order is set in the options.
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor));
+ // Register the built-in filters used by the default OpenIddict System.Net.Http event handlers.
+ builder.Services.TryAddSingleton();
+
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(new[]
{
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlerFilters.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlerFilters.cs
new file mode 100644
index 00000000..838b9229
--- /dev/null
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlerFilters.cs
@@ -0,0 +1,41 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ * See https://github.com/openiddict/openiddict-core for more information concerning
+ * the license and the contributors participating to this project.
+ */
+
+using System;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using static OpenIddict.Validation.OpenIddictValidationEvents;
+
+namespace OpenIddict.Validation.SystemNetHttp
+{
+ [EditorBrowsable(EditorBrowsableState.Advanced)]
+ public static class OpenIddictValidationSystemNetHttpHandlerFilters
+ {
+ ///
+ /// Represents a filter that excludes the associated handlers if the metadata address of the issuer is not available.
+ ///
+ public class RequireHttpMetadataAddress : IOpenIddictValidationHandlerFilter
+ {
+ public ValueTask IsActiveAsync([NotNull] BaseContext context)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (context.Options.MetadataAddress == null)
+ {
+ return new ValueTask(false);
+ }
+
+ return new ValueTask(
+ string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+ }
+}
diff --git a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
index b0d41494..c5fd8721 100644
--- a/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
+++ b/src/OpenIddict.Validation.SystemNetHttp/OpenIddictValidationSystemNetHttpHandlers.cs
@@ -23,6 +23,7 @@ using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.OpenIddictValidationEvents;
using static OpenIddict.Validation.OpenIddictValidationHandlers;
using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpConstants;
+using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlerFilters;
namespace OpenIddict.Validation.SystemNetHttp
{
@@ -33,79 +34,33 @@ namespace OpenIddict.Validation.SystemNetHttp
/*
* Authentication processing:
*/
- PopulateTokenValidationParametersFromMemoryCache.Descriptor,
- PopulateTokenValidationParametersFromProviderConfiguration.Descriptor,
- CacheTokenValidationParameters.Descriptor);
-
- ///
- /// Contains the logic responsible of populating the token validation parameters from the memory cache.
- ///
- public class PopulateTokenValidationParametersFromMemoryCache : IOpenIddictValidationHandler
- {
- private readonly IMemoryCache _cache;
-
- public PopulateTokenValidationParametersFromMemoryCache([NotNull] IMemoryCache cache)
- => _cache = cache;
-
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
- = OpenIddictValidationHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(PopulateTokenValidationParametersFromProviderConfiguration.Descriptor.Order - 1_000)
- .Build();
-
- public ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- // If token validation parameters were already attached, don't overwrite them.
- if (context.TokenValidationParameters != null)
- {
- return default;
- }
-
- // If the metadata address is not an HTTP/HTTPS address, let another handler populate the validation parameters.
- if (!string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
- {
- return default;
- }
-
- // Resolve the token validation parameters from the memory cache.
- if (_cache.TryGetValue(
- key: string.Concat("af84c073-c27c-49fd-a54f-584fd60320d3", "\x1e", context.Issuer?.AbsoluteUri),
- value: out TokenValidationParameters parameters))
- {
- context.TokenValidationParameters = parameters;
- }
-
- return default;
- }
- }
+ PopulateTokenValidationParameters.Descriptor);
///
/// Contains the logic responsible of populating the token validation
/// parameters using OAuth 2.0/OpenID Connect discovery.
///
- public class PopulateTokenValidationParametersFromProviderConfiguration : IOpenIddictValidationHandler
+ public class PopulateTokenValidationParameters : IOpenIddictValidationHandler
{
+ private readonly IMemoryCache _cache;
private readonly IHttpClientFactory _factory;
- public PopulateTokenValidationParametersFromProviderConfiguration([NotNull] IHttpClientFactory factory)
- => _factory = factory;
+ public PopulateTokenValidationParameters(
+ [NotNull] IMemoryCache cache,
+ [NotNull] IHttpClientFactory factory)
+ {
+ _cache = cache;
+ _factory = factory;
+ }
///
/// Gets the default descriptor definition assigned to this handler.
///
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(ValidateTokenValidationParameters.Descriptor.Order - 1_000)
+ .AddFilter()
+ .UseSingletonHandler()
+ .SetOrder(ValidateSelfContainedToken.Descriptor.Order - 500)
.Build();
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
@@ -115,42 +70,45 @@ namespace OpenIddict.Validation.SystemNetHttp
throw new ArgumentNullException(nameof(context));
}
- // If token validation parameters were already attached, don't overwrite them.
- if (context.TokenValidationParameters != null)
- {
- return;
- }
+ var parameters = await _cache.GetOrCreateAsync(
+ key: string.Concat("af84c073-c27c-49fd-a54f-584fd60320d3", "\x1e", context.Issuer?.AbsoluteUri),
+ factory: async entry =>
+ {
+ entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(30));
+ entry.SetPriority(CacheItemPriority.NeverRemove);
- // If the metadata address is not an HTTP/HTTPS address, let another handler populate the validation parameters.
- if (!string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
+ return await GetTokenValidationParametersAsync();
+ });
- using var client = _factory.CreateClient(Clients.Discovery);
- var response = await SendHttpRequestMessageAsync(context.Options.MetadataAddress);
+ context.TokenValidationParameters.ValidIssuer = parameters.ValidIssuer;
+ context.TokenValidationParameters.IssuerSigningKeys = parameters.IssuerSigningKeys;
- // Ensure the JWKS endpoint URL is present and valid.
- if (!response.TryGetParameter(Metadata.JwksUri, out var endpoint) || OpenIddictParameter.IsNullOrEmpty(endpoint))
+ async ValueTask GetTokenValidationParametersAsync()
{
- throw new InvalidOperationException("A discovery response containing an empty JWKS endpoint URL was returned.");
- }
+ using var client = _factory.CreateClient(Clients.Discovery);
+ var response = await SendHttpRequestMessageAsync(client, context.Options.MetadataAddress);
- if (!Uri.TryCreate((string) endpoint, UriKind.Absolute, out Uri uri))
- {
- throw new InvalidOperationException("A discovery response containing an invalid JWKS endpoint URL was returned.");
- }
+ // Ensure the JWKS endpoint URL is present and valid.
+ if (!response.TryGetParameter(Metadata.JwksUri, out var endpoint) || OpenIddictParameter.IsNullOrEmpty(endpoint))
+ {
+ throw new InvalidOperationException("A discovery response containing an empty JWKS endpoint URL was returned.");
+ }
- context.TokenValidationParameters = new TokenValidationParameters
- {
- ValidIssuer = (string) response[Metadata.Issuer],
- IssuerSigningKeys = await GetSigningKeysAsync(uri).ToListAsync()
- };
+ if (!Uri.TryCreate((string) endpoint, UriKind.Absolute, out Uri uri))
+ {
+ throw new InvalidOperationException("A discovery response containing an invalid JWKS endpoint URL was returned.");
+ }
+
+ return new TokenValidationParameters
+ {
+ ValidIssuer = (string) response[Metadata.Issuer],
+ IssuerSigningKeys = await GetSigningKeysAsync(client, uri).ToListAsync()
+ };
+ }
- async IAsyncEnumerable GetSigningKeysAsync(Uri address)
+ async IAsyncEnumerable GetSigningKeysAsync(HttpClient client, Uri address)
{
- var response = await SendHttpRequestMessageAsync(address);
+ var response = await SendHttpRequestMessageAsync(client, address);
var keys = response[JsonWebKeySetParameterNames.Keys];
if (keys == null)
@@ -208,7 +166,7 @@ namespace OpenIddict.Validation.SystemNetHttp
}
}
- async ValueTask SendHttpRequestMessageAsync(Uri address)
+ static async ValueTask SendHttpRequestMessageAsync(HttpClient client, Uri address)
{
using var request = new HttpRequestMessage(HttpMethod.Get, address);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
@@ -243,58 +201,5 @@ namespace OpenIddict.Validation.SystemNetHttp
}
}
}
-
- ///
- /// Contains the logic responsible of caching the token validation parameters.
- ///
- public class CacheTokenValidationParameters : IOpenIddictValidationHandler
- {
- private readonly IMemoryCache _cache;
-
- public CacheTokenValidationParameters([NotNull] IMemoryCache cache)
- => _cache = cache;
-
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
- = OpenIddictValidationHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(ValidateTokenValidationParameters.Descriptor.Order + 500)
- .Build();
-
- public ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (context.TokenValidationParameters == null)
- {
- return default;
- }
-
- // If the metadata address is not an HTTP/HTTPS address, let another handler populate the validation parameters.
- if (!string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
- {
- return default;
- }
-
- // Store the token validation parameters in the memory cache.
- _ = _cache.GetOrCreate(
- key: string.Concat("af84c073-c27c-49fd-a54f-584fd60320d3", "\x1e", context.Issuer?.AbsoluteUri),
- factory: entry =>
- {
- entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(30));
- entry.SetPriority(CacheItemPriority.NeverRemove);
-
- return context.TokenValidationParameters;
- });
-
- return default;
- }
- }
}
}
diff --git a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
index 1655e4dc..f6d9cf98 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
@@ -605,18 +605,18 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Sets the static token validation parameters.
+ /// Updates the token validation parameters using the specified delegate.
///
- /// The issuer address.
+ /// The configuration delegate.
/// The .
- public OpenIddictValidationBuilder SetTokenValidationParameters([NotNull] TokenValidationParameters parameters)
+ public OpenIddictValidationBuilder SetTokenValidationParameters([NotNull] Action configuration)
{
- if (parameters == null)
+ if (configuration == null)
{
- throw new ArgumentNullException(nameof(parameters));
+ throw new ArgumentNullException(nameof(configuration));
}
- return Configure(options => options.TokenValidationParameters = parameters);
+ return Configure(options => configuration(options.TokenValidationParameters));
}
///
diff --git a/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs b/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs
index 4518eb77..4c23fe45 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs
@@ -7,7 +7,6 @@
using System;
using System.Diagnostics;
using System.Linq;
-using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
@@ -32,25 +31,13 @@ namespace OpenIddict.Validation
throw new ArgumentNullException(nameof(options));
}
- if (options.SecurityTokenHandler == null)
+ if (options.JsonWebTokenHandler == null)
{
throw new InvalidOperationException("The security token handler cannot be null.");
}
- if (options.TokenValidationParameters == null)
+ if (options.Issuer != null || options.MetadataAddress != null)
{
- if (options.Issuer == null && options.MetadataAddress == null)
- {
- throw new InvalidOperationException(new StringBuilder()
- .AppendLine("The authority or an absolute metadata endpoint address must be provided.")
- .Append("Alternatively, token validation parameters can be manually set by calling ")
- .AppendLine("'services.AddOpenIddict().AddValidation().SetTokenValidationParameters()'.")
- .Append("To use the server configuration of a local OpenIddict server instance, ")
- .Append("reference the 'OpenIddict.Validation.ServerIntegration' package ")
- .Append("and call 'services.AddOpenIddict().AddValidation().UseLocalServer()'.")
- .ToString());
- }
-
if (options.MetadataAddress == null)
{
options.MetadataAddress = new Uri(".well-known/openid-configuration", UriKind.Relative);
diff --git a/src/OpenIddict.Validation/OpenIddictValidationEvents.cs b/src/OpenIddict.Validation/OpenIddictValidationEvents.cs
index f839ddb6..eebaff5d 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationEvents.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationEvents.cs
@@ -240,13 +240,12 @@ namespace OpenIddict.Validation
///
public ProcessAuthenticationContext([NotNull] OpenIddictValidationTransaction transaction)
: base(transaction)
- {
- }
+ => TokenValidationParameters = transaction.Options.TokenValidationParameters.Clone();
///
- /// Gets or sets the token validation parameters used for the current request.
+ /// Gets the token validation parameters used for the current request.
///
- public TokenValidationParameters TokenValidationParameters { get; set; }
+ public TokenValidationParameters TokenValidationParameters { get; }
///
/// Gets or sets the security principal.
diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
index f791780b..5d943aa0 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
@@ -28,7 +28,6 @@ namespace OpenIddict.Validation
/*
* Authentication processing:
*/
- ValidateTokenValidationParameters.Descriptor,
ValidateAccessTokenParameter.Descriptor,
ValidateReferenceToken.Descriptor,
ValidateSelfContainedToken.Descriptor,
@@ -42,64 +41,6 @@ namespace OpenIddict.Validation
*/
AttachDefaultChallengeError.Descriptor);
- ///
- /// Contains the logic responsible of ensuring the token validation parameters are populated.
- ///
- public class ValidateTokenValidationParameters : IOpenIddictValidationHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
- = OpenIddictValidationHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
- .SetOrder(int.MinValue + 100_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] ProcessAuthenticationContext context)
- {
- if (context == null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- // Note: at this stage, throw an exception if the token validation parameters cannot be found.
- var parameters = context.TokenValidationParameters ?? context.Options.TokenValidationParameters;
- if (parameters == null)
- {
- throw new InvalidOperationException(new StringBuilder()
- .AppendLine("The token validation parameters cannot be retrieved.")
- .Append("To register the default client, reference the 'OpenIddict.Validation.SystemNetHttp' package ")
- .AppendLine("and call 'services.AddOpenIddict().AddValidation().UseSystemNetHttp()'.")
- .Append("Alternatively, you can manually provide the token validation parameters ")
- .Append("by calling 'services.AddOpenIddict().AddValidation().SetTokenValidationParameters()'.")
- .ToString());
- }
-
- // Clone the token validation parameters before mutating them to ensure the
- // shared token validation parameters registered as options are not modified.
- parameters = parameters.Clone();
- parameters.NameClaimType = Claims.Name;
- parameters.PropertyBag = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.AccessToken };
- parameters.RoleClaimType = Claims.Role;
- parameters.TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key);
- parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
- parameters.ValidateAudience = false;
- parameters.ValidateLifetime = false;
-
- context.TokenValidationParameters = parameters;
-
- return default;
- }
- }
-
///
/// Contains the logic responsible of validating the access token resolved from the current request.
///
@@ -111,7 +52,7 @@ namespace OpenIddict.Validation
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateTokenValidationParameters.Descriptor.Order + 1_000)
+ .SetOrder(int.MinValue + 100_000)
.Build();
///
@@ -205,15 +146,27 @@ namespace OpenIddict.Validation
}
// If the token cannot be validated, don't return an error to allow another handle to validate it.
- if (!context.Options.SecurityTokenHandler.CanReadToken(payload))
+ if (!context.Options.JsonWebTokenHandler.CanReadToken(payload))
{
return;
}
- // If the token cannot be validated, don't return an error to allow another handle to validate it.
- var result = await context.Options.SecurityTokenHandler.ValidateTokenStringAsync(
- payload, context.TokenValidationParameters);
+ // If no issuer signing key was attached, don't return an error to allow another handle to validate it.
+ var parameters = context.TokenValidationParameters;
+ if (parameters?.IssuerSigningKeys == null)
+ {
+ return;
+ }
+ // Clone the token validation parameters before mutating them to ensure the
+ // shared token validation parameters registered as options are not modified.
+ parameters = parameters.Clone();
+ parameters.PropertyBag = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.AccessToken };
+ parameters.TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key);
+ parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
+
+ // 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(payload, parameters);
if (result.ClaimsIdentity == null)
{
return;
@@ -265,15 +218,26 @@ namespace OpenIddict.Validation
}
// If the token cannot be validated, don't return an error to allow another handle to validate it.
- if (!context.Options.SecurityTokenHandler.CanReadToken(context.Request.AccessToken))
+ if (!context.Options.JsonWebTokenHandler.CanReadToken(context.Request.AccessToken))
{
return;
}
- // If the token cannot be validated, don't return an error to allow another handle to validate it.
- var result = await context.Options.SecurityTokenHandler.ValidateTokenStringAsync(
- context.Request.AccessToken, context.TokenValidationParameters);
+ // If no issuer signing key was attached, don't return an error to allow another handle to validate it.
+ var parameters = context.TokenValidationParameters;
+ if (parameters?.IssuerSigningKeys == null)
+ {
+ return;
+ }
+
+ // Clone the token validation parameters before mutating them.
+ parameters = parameters.Clone();
+ parameters.PropertyBag = new Dictionary { [Claims.Private.TokenUsage] = TokenUsages.AccessToken };
+ parameters.TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key);
+ parameters.ValidIssuer = context.Issuer?.AbsoluteUri;
+ // 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.Request.AccessToken, parameters);
if (result.ClaimsIdentity == null)
{
return;
@@ -295,7 +259,7 @@ namespace OpenIddict.Validation
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateSelfContainedToken.Descriptor.Order + 1_000)
+ .SetOrder(ValidateReferenceToken.Descriptor.Order + 1_000)
.Build();
///
diff --git a/src/OpenIddict.Validation/OpenIddictValidationTokenHandler.cs b/src/OpenIddict.Validation/OpenIddictValidationJsonWebTokenHandler.cs
similarity index 97%
rename from src/OpenIddict.Validation/OpenIddictValidationTokenHandler.cs
rename to src/OpenIddict.Validation/OpenIddictValidationJsonWebTokenHandler.cs
index 93ed4164..fc797f93 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationTokenHandler.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationJsonWebTokenHandler.cs
@@ -15,7 +15,7 @@ using static OpenIddict.Abstractions.OpenIddictConstants;
namespace OpenIddict.Validation
{
- public class OpenIddictValidationTokenHandler : JsonWebTokenHandler
+ public class OpenIddictValidationJsonWebTokenHandler : JsonWebTokenHandler
{
public ValueTask ValidateTokenStringAsync(string token, TokenValidationParameters parameters)
{
diff --git a/src/OpenIddict.Validation/OpenIddictValidationOptions.cs b/src/OpenIddict.Validation/OpenIddictValidationOptions.cs
index e431f1a3..468d054b 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationOptions.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationOptions.cs
@@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
+using OpenIddict.Abstractions;
namespace OpenIddict.Validation
{
@@ -22,9 +23,9 @@ namespace OpenIddict.Validation
public IList EncryptionCredentials { get; } = new List();
///
- /// Gets or sets the security token handler used to protect and unprotect tokens.
+ /// Gets or sets the JWT handler used to protect and unprotect tokens.
///
- public OpenIddictValidationTokenHandler SecurityTokenHandler { get; set; } = new OpenIddictValidationTokenHandler
+ public OpenIddictValidationJsonWebTokenHandler JsonWebTokenHandler { get; set; } = new OpenIddictValidationJsonWebTokenHandler
{
SetDefaultTimesOnTokenCreation = false
};
@@ -79,8 +80,16 @@ namespace OpenIddict.Validation
public ISet Audiences { get; } = new HashSet(StringComparer.Ordinal);
///
- /// Gets or sets the token validation parameters used by the OpenIddict validation services.
+ /// Gets the token validation parameters used by the OpenIddict validation services.
///
- public TokenValidationParameters TokenValidationParameters { get; set; }
+ public TokenValidationParameters TokenValidationParameters { get; } = new TokenValidationParameters
+ {
+ ClockSkew = TimeSpan.Zero,
+ NameClaimType = OpenIddictConstants.Claims.Name,
+ RoleClaimType = OpenIddictConstants.Claims.Role,
+ // Note: audience and lifetime are manually validated by OpenIddict itself.
+ ValidateAudience = false,
+ ValidateLifetime = false
+ };
}
}