Browse Source

Add native support for private_key_jwt in the client

pull/1430/head
Kévin Chalet 4 years ago
parent
commit
e1436f7f59
  1. 12
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  2. 6
      src/OpenIddict.Client.DataProtection/OpenIddictClientDataProtectionHandlers.Protection.cs
  3. 11
      src/OpenIddict.Client/OpenIddictClientBuilder.cs
  4. 2
      src/OpenIddict.Client/OpenIddictClientConfiguration.cs
  5. 8
      src/OpenIddict.Client/OpenIddictClientEvents.Discovery.cs
  6. 57
      src/OpenIddict.Client/OpenIddictClientEvents.cs
  7. 3
      src/OpenIddict.Client/OpenIddictClientExtensions.cs
  8. 52
      src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
  9. 19
      src/OpenIddict.Client/OpenIddictClientHandlers.Authentication.cs
  10. 2
      src/OpenIddict.Client/OpenIddictClientHandlers.Discovery.cs
  11. 58
      src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
  12. 17
      src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs
  13. 810
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  14. 6
      src/OpenIddict.Client/OpenIddictClientOptions.cs
  15. 14
      src/OpenIddict.Client/OpenIddictClientRegistration.cs
  16. 14
      src/OpenIddict.Client/OpenIddictClientRetriever.cs
  17. 53
      src/OpenIddict.Client/OpenIddictClientService.cs
  18. 5
      src/OpenIddict.Client/OpenIddictClientTransaction.cs
  19. 13
      src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
  20. 25
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  21. 4
      src/OpenIddict.Server/OpenIddictServerOptions.cs
  22. 2
      src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs
  23. 8
      src/OpenIddict.Validation/OpenIddictValidationEvents.Discovery.cs
  24. 9
      src/OpenIddict.Validation/OpenIddictValidationEvents.cs
  25. 4
      src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs
  26. 15
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs
  27. 33
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
  28. 43
      src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
  29. 4
      src/OpenIddict.Validation/OpenIddictValidationOptions.cs
  30. 5
      src/OpenIddict.Validation/OpenIddictValidationTransaction.cs

12
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -128,6 +128,7 @@ public static class OpenIddictConstants
public const string ExpirationDate = "oi_exp_dt";
public const string GrantType = "oi_grt_typ";
public const string IdentityTokenLifetime = "oi_idt_lft";
public const string Issuer = "oi_iss";
public const string Nonce = "oi_nce";
public const string Presenter = "oi_prst";
public const string RedirectUri = "oi_reduri";
@ -143,10 +144,18 @@ public static class OpenIddictConstants
}
}
public static class ClientAssertionTypes
{
public const string JwtBearer = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
public const string Saml2Bearer = "urn:ietf:params:oauth:client-assertion-type:saml2-bearer";
}
public static class ClientAuthenticationMethods
{
public const string ClientSecretBasic = "client_secret_basic";
public const string ClientSecretJwt = "client_secret_jwt";
public const string ClientSecretPost = "client_secret_post";
public const string PrivateKeyJwt = "private_key_jwt";
}
public static class ClientTypes
@ -219,7 +228,7 @@ public static class OpenIddictConstants
public static class JsonWebTokenTypes
{
public const string AccessToken = "at+jwt";
public const string IdentityToken = "JWT";
public const string JsonWebToken = "JWT";
public static class Prefixes
{
@ -482,6 +491,7 @@ public static class OpenIddictConstants
{
public const string AccessToken = "access_token";
public const string AuthorizationCode = "authorization_code";
public const string ClientAssertionToken = "client_assertion_token";
public const string DeviceCode = "device_code";
public const string IdToken = "id_token";
public const string RefreshToken = "refresh_token";

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

@ -77,7 +77,8 @@ public static partial class OpenIddictClientDataProtectionHandlers
_ when context.ValidTokenTypes.Contains(TokenTypeHints.StateToken)
=> ValidateToken(TokenTypeHints.StateToken),
_ => null // The token type is not supported by the Data Protection integration (e.g identity tokens).
// The token type is not supported by the Data Protection integration (e.g client assertion tokens).
_ => null
};
if (principal is null)
@ -167,7 +168,8 @@ public static partial class OpenIddictClientDataProtectionHandlers
{
TokenTypeHints.StateToken => _options.CurrentValue.PreferDefaultStateTokenFormat,
_ => true // The token type is not supported by the Data Protection integration (e.g identity tokens).
// The token type is not supported by the Data Protection integration (e.g client assertion tokens).
_ => true
})
{
return default;

11
src/OpenIddict.Client/OpenIddictClientBuilder.cs

@ -1018,6 +1018,17 @@ public class OpenIddictClientBuilder
});
}
/// <summary>
/// Sets the client assertion token lifetime, after which backchannel requests
/// using an expired state token should be automatically rejected by the server.
/// Using long-lived state tokens or tokens that never expire is not recommended.
/// While discouraged, <see langword="null"/> can be specified to issue tokens that never expire.
/// </summary>
/// <param name="lifetime">The access token lifetime.</param>
/// <returns>The <see cref="OpenIddictClientBuilder"/>.</returns>
public OpenIddictClientBuilder SetClientAssertionTokenLifetime(TimeSpan? lifetime)
=> Configure(options => options.ClientAssertionTokenLifetime = lifetime);
/// <summary>
/// Sets the state token lifetime, after which authorization callbacks
/// using an expired state token will be automatically rejected by OpenIddict.

2
src/OpenIddict.Client/OpenIddictClientConfiguration.cs

@ -90,7 +90,7 @@ public class OpenIddictClientConfiguration : IPostConfigureOptions<OpenIddictCli
}
registration.ConfigurationManager = new ConfigurationManager<OpenIddictConfiguration>(
registration.MetadataAddress.AbsoluteUri, new OpenIddictClientRetriever(_service))
registration.MetadataAddress.AbsoluteUri, new OpenIddictClientRetriever(_service, registration))
{
AutomaticRefreshInterval = ConfigurationManager<OpenIddictConfiguration>.DefaultAutomaticRefreshInterval,
RefreshInterval = ConfigurationManager<OpenIddictConfiguration>.DefaultRefreshInterval

8
src/OpenIddict.Client/OpenIddictClientEvents.Discovery.cs

@ -101,8 +101,7 @@ public static partial class OpenIddictClientEvents
/// </summary>
public HandleConfigurationResponseContext(OpenIddictClientTransaction transaction)
: base(transaction)
{
}
=> Configuration = new();
/// <summary>
/// Gets or sets the request.
@ -121,11 +120,6 @@ public static partial class OpenIddictClientEvents
get => Transaction.Response!;
set => Transaction.Response = value;
}
/// <summary>
/// Gets the OpenID Connect configuration.
/// </summary>
public OpenIddictConfiguration Configuration { get; } = new OpenIddictConfiguration();
}
/// <summary>

57
src/OpenIddict.Client/OpenIddictClientEvents.cs

@ -57,6 +57,15 @@ public static partial class OpenIddictClientEvents
set => Transaction.Issuer = value;
}
/// <summary>
/// Gets or sets the server configuration used for the current request.
/// </summary>
public OpenIddictConfiguration Configuration
{
get => Transaction.Configuration;
set => Transaction.Configuration = value;
}
/// <summary>
/// Gets or sets the client registration used for the current request.
/// </summary>
@ -304,6 +313,16 @@ public static partial class OpenIddictClientEvents
/// </summary>
public Uri? UserinfoEndpoint { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a token request should be sent.
/// </summary>
public bool SendTokenRequest { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a token request should be sent.
/// </summary>
public bool SendUserinfoRequest { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether an authorization
/// code should be extracted from the current context.
@ -595,6 +614,42 @@ public static partial class OpenIddictClientEvents
/// Gets or sets the response returned by the userinfo endpoint, if applicable.
/// </summary>
public OpenIddictResponse? UserinfoResponse { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a client assertion
/// token should be generated (and optionally included in the request).
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool GenerateClientAssertionToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the generated client
/// assertion token should be included as part of the request.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool IncludeClientAssertionToken { get; set; }
/// <summary>
/// Gets or sets the generated client assertion token, if applicable.
/// The client assertion token will only be returned if
/// <see cref="IncludeClientAssertionToken"/> is set to <see langword="true"/>.
/// </summary>
public string? ClientAssertionToken { get; set; }
/// <summary>
/// Gets or sets type of the generated client assertion token, if applicable.
/// The client assertion token type will only be returned if
/// <see cref="IncludeClientAssertionToken"/> is set to <see langword="true"/>.
/// </summary>
public string? ClientAssertionTokenType { get; set; }
/// <summary>
/// Gets or sets the principal containing the claims that will be
/// used to create the client assertion token, if applicable.
/// </summary>
public ClaimsPrincipal? ClientAssertionTokenPrincipal { get; set; }
}
/// <summary>
@ -718,7 +773,7 @@ public static partial class OpenIddictClientEvents
/// <summary>
/// Gets or sets the generated state token, if applicable.
/// The access token will only be returned if
/// The state token will only be returned if
/// <see cref="IncludeStateToken"/> is set to <see langword="true"/>.
/// </summary>
public string? StateToken { get; set; }

3
src/OpenIddict.Client/OpenIddictClientExtensions.cs

@ -41,6 +41,7 @@ public static class OpenIddictClientExtensions
builder.Services.TryAddSingleton<RequireBackchannelAccessTokenValidated>();
builder.Services.TryAddSingleton<RequireBackchannelIdentityTokenValidated>();
builder.Services.TryAddSingleton<RequireBackchannelIdentityTokenPrincipal>();
builder.Services.TryAddSingleton<RequireClientAssertionTokenGenerated>();
builder.Services.TryAddSingleton<RequireFrontchannelAccessTokenValidated>();
builder.Services.TryAddSingleton<RequireFrontchannelIdentityTokenValidated>();
builder.Services.TryAddSingleton<RequireFrontchannelIdentityTokenPrincipal>();
@ -52,10 +53,8 @@ public static class OpenIddictClientExtensions
builder.Services.TryAddSingleton<RequireTokenEntryCreated>();
builder.Services.TryAddSingleton<RequireTokenPayloadPersisted>();
builder.Services.TryAddSingleton<RequireTokenRequest>();
builder.Services.TryAddSingleton<RequireTokenResponse>();
builder.Services.TryAddSingleton<RequireTokenStorageEnabled>();
builder.Services.TryAddSingleton<RequireUserinfoRequest>();
builder.Services.TryAddSingleton<RequireUserinfoResponse>();
builder.Services.TryAddSingleton<RequireUserinfoTokenExtracted>();
builder.Services.TryAddSingleton<RequireUserinfoTokenPrincipal>();

52
src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs

@ -92,6 +92,22 @@ public static class OpenIddictClientHandlerFilters
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no client assertion token is generated.
/// </summary>
public class RequireClientAssertionTokenGenerated : IOpenIddictClientHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.GenerateClientAssertionToken);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no frontchannel access token is validated.
/// </summary>
@ -264,23 +280,7 @@ public static class OpenIddictClientHandlerFilters
throw new ArgumentNullException(nameof(context));
}
return new(context.TokenRequest is not null);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no token response was received.
/// </summary>
public class RequireTokenResponse : IOpenIddictClientHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.TokenResponse is not null);
return new(context.SendTokenRequest);
}
}
@ -312,23 +312,7 @@ public static class OpenIddictClientHandlerFilters
throw new ArgumentNullException(nameof(context));
}
return new(context.UserinfoRequest is not null);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no userinfo response was received.
/// </summary>
public class RequireUserinfoResponse : IOpenIddictClientHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(context.UserinfoResponse is not null);
return new(context.SendUserinfoRequest);
}
}

19
src/OpenIddict.Client/OpenIddictClientHandlers.Authentication.cs

@ -149,30 +149,23 @@ public static partial class OpenIddictClientHandlers
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
public ValueTask HandleAsync(ApplyAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var configuration = await context.Registration.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
// Ensure the issuer resolved from the configuration matches the expected value.
if (configuration.Issuer != context.Issuer)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0307));
}
// Ensure the authorization endpoint is present and is a valid absolute URL.
if (configuration.AuthorizationEndpoint is not { IsAbsoluteUri: true } ||
!configuration.AuthorizationEndpoint.IsWellFormedOriginalString())
if (context.Configuration.AuthorizationEndpoint is not { IsAbsoluteUri: true } ||
!context.Configuration.AuthorizationEndpoint.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.FormatID0301(Metadata.AuthorizationEndpoint));
}
context.AuthorizationEndpoint = configuration.AuthorizationEndpoint.AbsoluteUri;
context.AuthorizationEndpoint = context.Configuration.AuthorizationEndpoint.AbsoluteUri;
return default;
}
}

2
src/OpenIddict.Client/OpenIddictClientHandlers.Discovery.cs

@ -622,7 +622,7 @@ public static partial class OpenIddictClientHandlers
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<HandleConfigurationResponseContext>()
.UseSingletonHandler<ExtractTokenEndpoint>()
.UseSingletonHandler<ExtractTokenEndpointClientAuthenticationMethods>()
.SetOrder(ExtractIssuerParameterRequirement.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();

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

@ -56,7 +56,7 @@ public static partial class OpenIddictClientHandlers
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenContext context)
public ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
@ -85,12 +85,14 @@ public static partial class OpenIddictClientHandlers
=> GetClientTokenValidationParameters(context.Options),
// Otherwise, use the token validation parameters of the authorization server.
_ => await GetServerTokenValidationParametersAsync(context.Registration)
_ => GetServerTokenValidationParameters(context.Registration, context.Configuration)
};
context.SecurityTokenHandler = context.Options.JsonWebTokenHandler;
context.TokenValidationParameters = parameters;
return default;
static TokenValidationParameters GetClientTokenValidationParameters(OpenIddictClientOptions options)
{
var parameters = options.TokenValidationParameters.Clone();
@ -102,18 +104,9 @@ public static partial class OpenIddictClientHandlers
return parameters;
}
static async Task<TokenValidationParameters> GetServerTokenValidationParametersAsync(
OpenIddictClientRegistration registration)
static TokenValidationParameters GetServerTokenValidationParameters(
OpenIddictClientRegistration registration, OpenIddictConfiguration configuration)
{
var configuration = await registration.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
// Ensure the issuer resolved from the configuration matches the expected value.
if (configuration.Issuer != registration!.Issuer)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0307));
}
var parameters = registration!.TokenValidationParameters.Clone();
parameters.ValidIssuers ??= configuration.Issuer switch
@ -621,8 +614,26 @@ public static partial class OpenIddictClientHandlers
context.SecurityTokenHandler = context.Options.JsonWebTokenHandler;
context.EncryptionCredentials = context.Options.EncryptionCredentials.First();
context.SigningCredentials = context.Options.SigningCredentials.First();
context.EncryptionCredentials = context.TokenType switch
{
// For client assertions, use the encryption credentials
// configured for the client registration, if available.
TokenTypeHints.ClientAssertionToken
=> context.Registration.EncryptionCredentials.FirstOrDefault(),
// For other types of tokens, use the global encryption credentials.
_ => context.Options.EncryptionCredentials.First()
};
context.SigningCredentials = context.TokenType switch
{
// For client assertions, use the signing credentials configured for the client registration.
TokenTypeHints.ClientAssertionToken
=> context.Registration.SigningCredentials.First(),
// For other types of tokens, use the global signing credentials.
_ => context.Options.SigningCredentials.First()
};
return default;
}
@ -723,9 +734,11 @@ public static partial class OpenIddictClientHandlers
// Clone the principal and exclude the private claims mapped to standard JWT claims.
var principal = context.Principal.Clone(claim => claim.Type switch
{
Claims.Private.CreationDate or Claims.Private.ExpirationDate or Claims.Private.TokenType => false,
Claims.Private.CreationDate or Claims.Private.ExpirationDate or
Claims.Private.Issuer or Claims.Private.TokenType => false,
Claims.Private.Audience when context.TokenType is TokenTypeHints.StateToken => false,
Claims.Private.Audience when context.TokenType is
TokenTypeHints.ClientAssertionToken or TokenTypeHints.StateToken => false,
_ => true
});
@ -734,9 +747,9 @@ public static partial class OpenIddictClientHandlers
var claims = new Dictionary<string, object>(StringComparer.Ordinal);
// For state tokens, set the public audience claims using
// the private audience claims from the security principal.
if (context.TokenType is TokenTypeHints.StateToken)
// For client assertion tokens, set the public audience claims
// using the private audience claims from the security principal.
if (context.TokenType is TokenTypeHints.ClientAssertionToken)
{
var audiences = context.Principal.GetAudiences();
if (audiences.Any())
@ -755,12 +768,17 @@ public static partial class OpenIddictClientHandlers
EncryptingCredentials = context.EncryptionCredentials,
Expires = context.Principal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.Principal.GetCreationDate()?.UtcDateTime,
Issuer = context.Principal.GetClaim(Claims.Private.Issuer),
SigningCredentials = context.SigningCredentials,
Subject = (ClaimsIdentity) principal.Identity,
TokenType = context.TokenType switch
{
null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)),
// For client assertion tokens, use the generic "JWT" type.
TokenTypeHints.ClientAssertionToken => JsonWebTokenTypes.JsonWebToken,
// For state tokens, use its private representation.
TokenTypeHints.StateToken => JsonWebTokenTypes.Private.StateToken,
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))

17
src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs

@ -101,7 +101,7 @@ public static partial class OpenIddictClientHandlers
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(HandleUserinfoResponseContext context)
public ValueTask HandleAsync(HandleUserinfoResponseContext context)
{
if (context is null)
{
@ -111,16 +111,7 @@ public static partial class OpenIddictClientHandlers
// Ignore the response instance if a userinfo token was extracted.
if (!string.IsNullOrEmpty(context.UserinfoToken))
{
return;
}
var configuration = await context.Registration.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
// Ensure the issuer resolved from the configuration matches the expected value.
if (configuration.Issuer != context.Issuer)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0307));
return default;
}
// Create a new claims-based identity using the same authentication type
@ -133,7 +124,7 @@ public static partial class OpenIddictClientHandlers
// Resolve the issuer that will be attached to the claims created by this handler.
// Note: at this stage, the optional issuer extracted from the response is assumed
// to be valid, as it is guarded against unknown values by the ValidateIssuer handler.
var issuer = (string?) context.Response[Claims.Issuer] ?? configuration.Issuer!.AbsoluteUri;
var issuer = (string?) context.Response[Claims.Issuer] ?? context.Configuration.Issuer!.AbsoluteUri;
foreach (var parameter in context.Response.GetParameters())
{
@ -196,6 +187,8 @@ public static partial class OpenIddictClientHandlers
context.Principal = new ClaimsPrincipal(identity);
return default;
static string GetClaimValueType(JsonElement element) => element.ValueKind switch
{
JsonValueKind.String => ClaimValueTypes.String,

810
src/OpenIddict.Client/OpenIddictClientHandlers.cs

File diff suppressed because it is too large

6
src/OpenIddict.Client/OpenIddictClientOptions.cs

@ -57,6 +57,12 @@ public class OpenIddictClientOptions
/// </remarks>
public List<SigningCredentials> SigningCredentials { get; } = new();
/// <summary>
/// Gets or sets the period of time client assertion tokens remain valid after being issued. The default value is 5 minutes.
/// While not recommended, this property can be set to <see langword="null"/> to issue client assertion tokens that never expire.
/// </summary>
public TimeSpan? ClientAssertionTokenLifetime { get; set; } = TimeSpan.FromMinutes(5);
/// <summary>
/// Gets or sets the period of time state tokens remain valid after being issued. The default value is 15 minutes.
/// While not recommended, this property can be set to <see langword="null"/> to issue state tokens that never expire.

14
src/OpenIddict.Client/OpenIddictClientRegistration.cs

@ -29,6 +29,20 @@ public class OpenIddictClientRegistration
/// </summary>
public Uri? RedirectUri { get; set; }
/// <summary>
/// Gets the list of encryption credentials used to create tokens for this client.
/// Multiple credentials can be added to support key rollover, but if X.509 keys
/// are used, at least one of them must have a valid creation/expiration date.
/// </summary>
public List<EncryptingCredentials> EncryptionCredentials { get; } = new();
/// <summary>
/// Gets the list of signing credentials used to create tokens for this client.
/// Multiple credentials can be added to support key rollover, but if X.509 keys
/// are used, at least one of them must have a valid creation/expiration date.
/// </summary>
public List<SigningCredentials> SigningCredentials { get; } = new();
/// <summary>
/// Gets the code challenge methods allowed by the client instance.
/// If no value is explicitly set, the default code challenge methods are automatically used.

14
src/OpenIddict.Client/OpenIddictClientRetriever.cs

@ -11,13 +11,19 @@ namespace OpenIddict.Client;
public class OpenIddictClientRetriever : IConfigurationRetriever<OpenIddictConfiguration>
{
private readonly OpenIddictClientService _service;
private readonly OpenIddictClientRegistration _registration;
/// <summary>
/// Creates a new instance of the <see cref="OpenIddictClientRetriever"/> class.
/// </summary>
/// <param name="service">The validation service.</param>
public OpenIddictClientRetriever(OpenIddictClientService service)
=> _service = service ?? throw new ArgumentNullException(nameof(service));
/// <param name="registration">The client registration.</param>
public OpenIddictClientRetriever(
OpenIddictClientService service, OpenIddictClientRegistration registration)
{
_service = service ?? throw new ArgumentNullException(nameof(service));
_registration = registration ?? throw new ArgumentNullException(nameof(registration));
}
/// <summary>
/// Retrieves the OpenID Connect server configuration from the specified address.
@ -40,7 +46,7 @@ public class OpenIddictClientRetriever : IConfigurationRetriever<OpenIddictConfi
cancel.ThrowIfCancellationRequested();
var configuration = await _service.GetConfigurationAsync(uri, cancel) ??
var configuration = await _service.GetConfigurationAsync(_registration, uri, cancel) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0145));
if (configuration.JwksUri is null)
@ -48,7 +54,7 @@ public class OpenIddictClientRetriever : IConfigurationRetriever<OpenIddictConfi
throw new InvalidOperationException(SR.GetResourceString(SR.ID0146));
}
configuration.JsonWebKeySet = await _service.GetSecurityKeysAsync(configuration.JwksUri, cancel) ??
configuration.JsonWebKeySet = await _service.GetSecurityKeysAsync(_registration, configuration.JwksUri, cancel) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0147));
// Copy the signing keys found in the JSON Web Key Set to the SigningKeys collection.

53
src/OpenIddict.Client/OpenIddictClientService.cs

@ -25,11 +25,18 @@ public class OpenIddictClientService
/// <summary>
/// Retrieves the OpenID Connect server configuration from the specified address.
/// </summary>
/// <param name="registration">The client registration.</param>
/// <param name="address">The address of the remote metadata endpoint.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The OpenID Connect server configuration retrieved from the remote server.</returns>
public async ValueTask<OpenIddictConfiguration> GetConfigurationAsync(Uri address, CancellationToken cancellationToken = default)
public async ValueTask<OpenIddictConfiguration> GetConfigurationAsync(
OpenIddictClientRegistration registration, Uri address, CancellationToken cancellationToken = default)
{
if (registration is null)
{
throw new ArgumentNullException(nameof(address));
}
if (address is null)
{
throw new ArgumentNullException(nameof(address));
@ -68,6 +75,7 @@ public class OpenIddictClientService
var context = new PrepareConfigurationRequestContext(transaction)
{
Address = address,
Registration = registration,
Request = request
};
@ -88,6 +96,7 @@ public class OpenIddictClientService
var context = new ApplyConfigurationRequestContext(transaction)
{
Address = address,
Registration = registration,
Request = request
};
@ -108,6 +117,7 @@ public class OpenIddictClientService
var context = new ExtractConfigurationResponseContext(transaction)
{
Address = address,
Registration = registration,
Request = request
};
@ -130,6 +140,7 @@ public class OpenIddictClientService
var context = new HandleConfigurationResponseContext(transaction)
{
Address = address,
Registration = registration,
Request = request,
Response = response
};
@ -164,11 +175,18 @@ public class OpenIddictClientService
/// <summary>
/// Retrieves the security keys exposed by the specified JWKS endpoint.
/// </summary>
/// <param name="registration">The client registration.</param>
/// <param name="address">The address of the remote metadata endpoint.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The security keys retrieved from the remote server.</returns>
public async ValueTask<JsonWebKeySet> GetSecurityKeysAsync(Uri address, CancellationToken cancellationToken = default)
public async ValueTask<JsonWebKeySet> GetSecurityKeysAsync(
OpenIddictClientRegistration registration, Uri address, CancellationToken cancellationToken = default)
{
if (registration is null)
{
throw new ArgumentNullException(nameof(registration));
}
if (address is null)
{
throw new ArgumentNullException(nameof(address));
@ -208,6 +226,7 @@ public class OpenIddictClientService
var context = new PrepareCryptographyRequestContext(transaction)
{
Address = address,
Registration = registration,
Request = request
};
@ -228,6 +247,7 @@ public class OpenIddictClientService
var context = new ApplyCryptographyRequestContext(transaction)
{
Address = address,
Registration = registration,
Request = request
};
@ -248,6 +268,7 @@ public class OpenIddictClientService
var context = new ExtractCryptographyResponseContext(transaction)
{
Address = address,
Registration = registration,
Request = request
};
@ -270,6 +291,7 @@ public class OpenIddictClientService
var context = new HandleCryptographyResponseContext(transaction)
{
Address = address,
Registration = registration,
Request = request,
Response = response
};
@ -347,6 +369,7 @@ public class OpenIddictClientService
var context = new ProcessAuthenticationContext(transaction)
{
Configuration = configuration,
GrantType = GrantTypes.RefreshToken,
Issuer = registration.Issuer,
RefreshToken = token,
@ -450,6 +473,15 @@ public class OpenIddictClientService
throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address));
}
var configuration = await registration.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
if (configuration.TokenEndpoint is not { IsAbsoluteUri: true } ||
!configuration.TokenEndpoint.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.FormatID0301(Metadata.TokenEndpoint));
}
cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
@ -477,6 +509,7 @@ public class OpenIddictClientService
var context = new PrepareTokenRequestContext(transaction)
{
Address = address,
Configuration = configuration,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -499,6 +532,7 @@ public class OpenIddictClientService
var context = new ApplyTokenRequestContext(transaction)
{
Address = address,
Configuration = configuration,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -521,6 +555,7 @@ public class OpenIddictClientService
var context = new ExtractTokenResponseContext(transaction)
{
Address = address,
Configuration = configuration,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -545,6 +580,7 @@ public class OpenIddictClientService
var context = new HandleTokenResponseContext(transaction)
{
Address = address,
Configuration = configuration,
Issuer = registration.Issuer,
Registration = registration,
Request = request,
@ -604,6 +640,15 @@ public class OpenIddictClientService
throw new ArgumentException(SR.GetResourceString(SR.ID0144), nameof(address));
}
var configuration = await registration.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
if (configuration.TokenEndpoint is not { IsAbsoluteUri: true } ||
!configuration.TokenEndpoint.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.FormatID0301(Metadata.TokenEndpoint));
}
cancellationToken.ThrowIfCancellationRequested();
// Note: this service is registered as a singleton service. As such, it cannot
@ -631,6 +676,7 @@ public class OpenIddictClientService
var context = new PrepareUserinfoRequestContext(transaction)
{
Address = address,
Configuration = configuration,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -653,6 +699,7 @@ public class OpenIddictClientService
var context = new ApplyUserinfoRequestContext(transaction)
{
Address = address,
Configuration = configuration,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -675,6 +722,7 @@ public class OpenIddictClientService
var context = new ExtractUserinfoResponseContext(transaction)
{
Address = address,
Configuration = configuration,
Issuer = registration.Issuer,
Registration = registration,
Request = request
@ -699,6 +747,7 @@ public class OpenIddictClientService
var context = new HandleUserinfoResponseContext(transaction)
{
Address = address,
Configuration = configuration,
Issuer = registration.Issuer,
Registration = registration,
Request = request,

5
src/OpenIddict.Client/OpenIddictClientTransaction.cs

@ -43,6 +43,11 @@ public class OpenIddictClientTransaction
/// </summary>
public OpenIddictClientRegistration Registration { get; set; } = default!;
/// <summary>
/// Gets or sets the server configuration used for the current request.
/// </summary>
public OpenIddictConfiguration Configuration { get; set; } = default!;
/// <summary>
/// Gets or sets the current OpenID Connect request.
/// </summary>

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

@ -90,8 +90,8 @@ public static partial class OpenIddictServerHandlers
// For identity tokens, both "JWT" and "application/jwt" are valid.
TokenTypeHints.IdToken => new[]
{
JsonWebTokenTypes.IdentityToken,
JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.IdentityToken
JsonWebTokenTypes.JsonWebToken,
JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.JsonWebToken
},
// For authorization codes, only the short "oi_auc+jwt" form is valid.
@ -369,7 +369,7 @@ public static partial class OpenIddictServerHandlers
=> TokenTypeHints.AccessToken,
// Both JWT and application/JWT are supported for identity tokens.
JsonWebTokenTypes.IdentityToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.IdentityToken
JsonWebTokenTypes.JsonWebToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.JsonWebToken
=> TokenTypeHints.IdToken,
JsonWebTokenTypes.Private.AuthorizationCode => TokenTypeHints.AuthorizationCode,
@ -1172,7 +1172,8 @@ public static partial class OpenIddictServerHandlers
// Clone the principal and exclude the private claims mapped to standard JWT claims.
var principal = context.Principal.Clone(claim => claim.Type switch
{
Claims.Private.CreationDate or Claims.Private.ExpirationDate or Claims.Private.TokenType => false,
Claims.Private.CreationDate or Claims.Private.ExpirationDate or
Claims.Private.Issuer or Claims.Private.TokenType => false,
Claims.Private.Audience
when context.TokenType is TokenTypeHints.AccessToken or TokenTypeHints.IdToken => false,
@ -1232,7 +1233,7 @@ public static partial class OpenIddictServerHandlers
EncryptingCredentials = context.EncryptionCredentials,
Expires = context.Principal.GetExpirationDate()?.UtcDateTime,
IssuedAt = context.Principal.GetCreationDate()?.UtcDateTime,
Issuer = context.Issuer?.AbsoluteUri,
Issuer = context.Principal.GetClaim(Claims.Private.Issuer),
SigningCredentials = context.SigningCredentials,
Subject = (ClaimsIdentity) principal.Identity,
TokenType = context.TokenType switch
@ -1240,7 +1241,7 @@ public static partial class OpenIddictServerHandlers
null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)),
TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken,
TokenTypeHints.IdToken => JsonWebTokenTypes.IdentityToken,
TokenTypeHints.IdToken => JsonWebTokenTypes.JsonWebToken,
TokenTypeHints.AuthorizationCode => JsonWebTokenTypes.Private.AuthorizationCode,
TokenTypeHints.DeviceCode => JsonWebTokenTypes.Private.DeviceCode,
TokenTypeHints.RefreshToken => JsonWebTokenTypes.Private.RefreshToken,

25
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -1663,6 +1663,9 @@ public static partial class OpenIddictServerHandlers
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
}
// Use the server identity as the token issuer.
principal.SetClaim(Claims.Private.Issuer, context.Issuer?.AbsoluteUri);
// Set the audiences based on the resource claims stored in the principal.
principal.SetAudiences(context.Principal.GetResources());
@ -1747,6 +1750,9 @@ public static partial class OpenIddictServerHandlers
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
}
// Use the server identity as the token issuer.
principal.SetClaim(Claims.Private.Issuer, context.Issuer?.AbsoluteUri);
// Attach the redirect_uri to allow for later comparison when
// receiving a grant_type=authorization_code token request.
principal.SetClaim(Claims.Private.RedirectUri, context.Request.RedirectUri);
@ -1833,6 +1839,9 @@ public static partial class OpenIddictServerHandlers
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
}
// Use the server identity as the token issuer.
principal.SetClaim(Claims.Private.Issuer, context.Issuer?.AbsoluteUri);
// Restore the device code internal token identifier from the principal
// resolved from the user code used in the user code verification request.
if (context.EndpointType is OpenIddictServerEndpointType.Verification)
@ -1924,6 +1933,9 @@ public static partial class OpenIddictServerHandlers
}
}
// Use the server identity as the token issuer.
principal.SetClaim(Claims.Private.Issuer, context.Issuer?.AbsoluteUri);
context.RefreshTokenPrincipal = principal;
return default;
@ -2017,15 +2029,17 @@ public static partial class OpenIddictServerHandlers
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
}
// Use the server identity as the token issuer.
principal.SetClaim(Claims.Private.Issuer, context.Issuer?.AbsoluteUri);
// If available, use the client_id as both the audience and the authorized party.
// See https://openid.net/specs/openid-connect-core-1_0.html#IDToken for more information.
if (!string.IsNullOrEmpty(context.ClientId))
{
principal.SetAudiences(context.ClientId);
principal.SetClaim(Claims.AuthorizedParty, context.ClientId);
}
// Use the client_id as the authorized party, if available.
// See https://openid.net/specs/openid-connect-core-1_0.html#IDToken for more information.
principal.SetClaim(Claims.AuthorizedParty, context.ClientId);
// If a nonce was present in the authorization request, it MUST be included in the id_token generated
// by the token endpoint. For that, OpenIddict simply flows the nonce as an authorization code claim.
// See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation for more information.
@ -2103,6 +2117,9 @@ public static partial class OpenIddictServerHandlers
principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value);
}
// Use the server identity as the token issuer.
principal.SetClaim(Claims.Private.Issuer, context.Issuer?.AbsoluteUri);
// Store the client_id as a public client_id claim.
principal.SetClaim(Claims.ClientId, context.Request.ClientId);

4
src/OpenIddict.Server/OpenIddictServerOptions.cs

@ -135,7 +135,7 @@ public class OpenIddictServerOptions
// were issued with the generic "typ": "JWT" header. To prevent confused deputy and token substitution
// attacks, a special "token_usage" claim was added to the JWT payload to convey the actual token type.
// This validator overrides the default logic used by IdentityModel to resolve the type from this claim.
TypeValidator = (type, token, parameters) =>
TypeValidator = static (type, token, parameters) =>
{
// If available, try to resolve the actual type from the "token_usage" claim.
if (((JsonWebToken) token).TryGetPayloadValue(OpenIddictConstants.Claims.TokenUsage, out string usage))
@ -143,7 +143,7 @@ public class OpenIddictServerOptions
type = usage switch
{
TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken,
TokenTypeHints.IdToken => JsonWebTokenTypes.IdentityToken,
TokenTypeHints.IdToken => JsonWebTokenTypes.JsonWebToken,
_ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269))
};

2
src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs

@ -44,7 +44,7 @@ public class OpenIddictValidationConfiguration : IPostConfigureOptions<OpenIddic
throw new InvalidOperationException(SR.GetResourceString(SR.ID0128));
}
if (options.ValidationType == OpenIddictValidationType.Introspection)
if (options.ValidationType is OpenIddictValidationType.Introspection)
{
if (!options.Handlers.Any(descriptor => descriptor.ContextType == typeof(ApplyIntrospectionRequestContext)))
{

8
src/OpenIddict.Validation/OpenIddictValidationEvents.Discovery.cs

@ -101,8 +101,7 @@ public static partial class OpenIddictValidationEvents
/// </summary>
public HandleConfigurationResponseContext(OpenIddictValidationTransaction transaction)
: base(transaction)
{
}
=> Configuration = new();
/// <summary>
/// Gets or sets the request.
@ -121,11 +120,6 @@ public static partial class OpenIddictValidationEvents
get => Transaction.Response!;
set => Transaction.Response = value;
}
/// <summary>
/// Gets the OpenID Connect configuration.
/// </summary>
public OpenIddictConfiguration Configuration { get; } = new OpenIddictConfiguration();
}
/// <summary>

9
src/OpenIddict.Validation/OpenIddictValidationEvents.cs

@ -47,6 +47,15 @@ public static partial class OpenIddictValidationEvents
set => Transaction.Issuer = value;
}
/// <summary>
/// Gets or sets the server configuration used for the current request.
/// </summary>
public OpenIddictConfiguration Configuration
{
get => Transaction.Configuration;
set => Transaction.Configuration = value;
}
/// <summary>
/// Gets the logger responsible for logging processed operations.
/// </summary>

4
src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs

@ -71,7 +71,7 @@ public static class OpenIddictValidationHandlerFilters
throw new ArgumentNullException(nameof(context));
}
return new(context.Options.ValidationType == OpenIddictValidationType.Direct);
return new(context.Options.ValidationType is OpenIddictValidationType.Direct);
}
}
@ -87,7 +87,7 @@ public static class OpenIddictValidationHandlerFilters
throw new ArgumentNullException(nameof(context));
}
return new(context.Options.ValidationType == OpenIddictValidationType.Introspection);
return new(context.Options.ValidationType is OpenIddictValidationType.Introspection);
}
}

15
src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs

@ -357,22 +357,13 @@ public static partial class OpenIddictValidationHandlers
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(HandleIntrospectionResponseContext context)
public ValueTask HandleAsync(HandleIntrospectionResponseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
// Ensure the issuer resolved from the configuration matches the expected value.
if (configuration is not null && configuration.Issuer != context.Issuer)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0307));
}
// Create a new claims-based identity using the same authentication type
// and the name/role claims as the one used by IdentityModel for JWT tokens.
var identity = new ClaimsIdentity(
@ -384,7 +375,7 @@ public static partial class OpenIddictValidationHandlers
// Note: at this stage, the optional issuer extracted from the response is assumed
// to be valid, as it is guarded against unknown values by the ValidateIssuer handler.
var issuer = (string?) context.Response[Claims.Issuer] ??
configuration?.Issuer?.AbsoluteUri ??
context.Configuration.Issuer?.AbsoluteUri ??
context.Issuer?.AbsoluteUri ?? ClaimsIdentity.DefaultIssuer;
foreach (var parameter in context.Response.GetParameters())
@ -448,6 +439,8 @@ public static partial class OpenIddictValidationHandlers
context.Principal = new ClaimsPrincipal(identity);
return default;
static string GetClaimValueType(JsonElement element) => element.ValueKind switch
{
JsonValueKind.String => ClaimValueTypes.String,

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

@ -51,27 +51,18 @@ public static partial class OpenIddictValidationHandlers
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenContext context)
public ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
// Ensure the issuer resolved from the configuration matches the expected value.
if (context.Options.Issuer is not null && configuration.Issuer != context.Options.Issuer)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0307));
}
// Clone the token validation parameters and set the issuer using the value found in the
// OpenID Connect server configuration (that can be static or retrieved using discovery).
var parameters = context.Options.TokenValidationParameters.Clone();
parameters.ValidIssuers ??= configuration.Issuer switch
parameters.ValidIssuers ??= context.Configuration.Issuer switch
{
null => null,
@ -92,7 +83,7 @@ public static partial class OpenIddictValidationHandlers
// Combine the signing keys registered statically in the token validation parameters
// with the signing keys resolved from the OpenID Connect server configuration.
parameters.IssuerSigningKeys =
parameters.IssuerSigningKeys?.Concat(configuration.SigningKeys) ?? configuration.SigningKeys;
parameters.IssuerSigningKeys?.Concat(context.Configuration.SigningKeys) ?? context.Configuration.SigningKeys;
parameters.ValidTypes = context.ValidTokenTypes.Count switch
{
@ -116,6 +107,8 @@ public static partial class OpenIddictValidationHandlers
context.SecurityTokenHandler = context.Options.JsonWebTokenHandler;
context.TokenValidationParameters = parameters;
return default;
}
}
@ -322,18 +315,9 @@ public static partial class OpenIddictValidationHandlers
Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010));
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
// Ensure the issuer resolved from the configuration matches the expected value.
if (context.Options.Issuer is not null && configuration.Issuer != context.Options.Issuer)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0307));
}
// Ensure the introspection endpoint is present and is a valid absolute URL.
if (configuration.IntrospectionEndpoint is not { IsAbsoluteUri: true } ||
!configuration.IntrospectionEndpoint.IsWellFormedOriginalString())
if (context.Configuration.IntrospectionEndpoint is not { IsAbsoluteUri: true } ||
!context.Configuration.IntrospectionEndpoint.IsWellFormedOriginalString())
{
throw new InvalidOperationException(SR.FormatID0301(Metadata.IntrospectionEndpoint));
}
@ -342,7 +326,8 @@ public static partial class OpenIddictValidationHandlers
try
{
principal = await _service.IntrospectTokenAsync(configuration.IntrospectionEndpoint, context.Token, context.ValidTokenTypes.Count switch
principal = await _service.IntrospectTokenAsync(context.Configuration.IntrospectionEndpoint,
context.Token, context.ValidTokenTypes.Count switch
{
// Infer the token type hint sent to the authorization server to help speed up
// the token resolution lookup. If multiple types are accepted, no hint is sent.

43
src/OpenIddict.Validation/OpenIddictValidationHandlers.cs

@ -16,6 +16,7 @@ public static partial class OpenIddictValidationHandlers
/*
* Authentication processing:
*/
ResolveServerConfiguration.Descriptor,
EvaluateValidatedTokens.Descriptor,
ValidateRequiredTokens.Descriptor,
ValidateAccessToken.Descriptor,
@ -29,6 +30,42 @@ public static partial class OpenIddictValidationHandlers
.AddRange(Introspection.DefaultHandlers)
.AddRange(Protection.DefaultHandlers);
/// <summary>
/// Contains the logic responsible for resolving the server configuration.
/// </summary>
public class ResolveServerConfiguration : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<ResolveServerConfiguration>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
// Ensure the issuer resolved from the configuration matches the expected value.
if (context.Options.Issuer is not null && configuration.Issuer != context.Options.Issuer)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0307));
}
context.Configuration = configuration;
}
}
/// <summary>
/// Contains the logic responsible for selecting the token types that should be validated.
/// </summary>
@ -40,7 +77,7 @@ public static partial class OpenIddictValidationHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<EvaluateValidatedTokens>()
.SetOrder(int.MinValue + 100_000)
.SetOrder(ResolveServerConfiguration.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@ -52,7 +89,9 @@ public static partial class OpenIddictValidationHandlers
throw new ArgumentNullException(nameof(context));
}
(context.ExtractAccessToken, context.RequireAccessToken, context.ValidateAccessToken) = context.EndpointType switch
(context.ExtractAccessToken,
context.RequireAccessToken,
context.ValidateAccessToken) = context.EndpointType switch
{
// The validation handler is responsible for validating access tokens for endpoints
// it doesn't manage (typically, API endpoints using token authentication).

4
src/OpenIddict.Validation/OpenIddictValidationOptions.cs

@ -120,7 +120,7 @@ public class OpenIddictValidationOptions
// were issued with the generic "typ": "JWT" header. To prevent confused deputy and token substitution
// attacks, a special "token_usage" claim was added to the JWT payload to convey the actual token type.
// This validator overrides the default logic used by IdentityModel to resolve the type from this claim.
TypeValidator = (type, token, parameters) =>
TypeValidator = static (type, token, parameters) =>
{
// If available, try to resolve the actual type from the "token_usage" claim.
if (((JsonWebToken) token).TryGetPayloadValue(Claims.TokenUsage, out string usage))
@ -128,7 +128,7 @@ public class OpenIddictValidationOptions
type = usage switch
{
TokenTypeHints.AccessToken => JsonWebTokenTypes.AccessToken,
TokenTypeHints.IdToken => JsonWebTokenTypes.IdentityToken,
TokenTypeHints.IdToken => JsonWebTokenTypes.JsonWebToken,
_ => throw new NotSupportedException(SR.GetResourceString(SR.ID0269))
};

5
src/OpenIddict.Validation/OpenIddictValidationTransaction.cs

@ -38,6 +38,11 @@ public class OpenIddictValidationTransaction
/// </summary>
public Dictionary<string, object?> Properties { get; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets or sets the server configuration used for the current request.
/// </summary>
public OpenIddictConfiguration Configuration { get; set; } = default!;
/// <summary>
/// Gets or sets the current OpenID Connect request.
/// </summary>

Loading…
Cancel
Save