Browse Source

Move the client authentication logic to the ProcessAuthentication event

pull/1883/head
Kévin Chalet 2 years ago
parent
commit
a941660a41
  1. 92
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  2. 26
      src/OpenIddict.Server/OpenIddictServerConfiguration.cs
  3. 2
      src/OpenIddict.Server/OpenIddictServerEvents.cs
  4. 1
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  5. 17
      src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
  6. 128
      src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
  7. 224
      src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
  8. 276
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  9. 280
      src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
  10. 280
      src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
  11. 121
      src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
  12. 10
      src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
  13. 302
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  14. 24
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
  15. 425
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs
  16. 304
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs
  17. 47
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

92
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -117,13 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ID0001" xml:space="preserve">
<value>An identity cannot be extracted from this token request.
This generally indicates that the OpenIddict server stack was asked to validate a token for an invalid grant type (e.g password).</value>
</data>
<data name="ID0002" xml:space="preserve">
<value>An identity cannot be extracted from this request.
This generally indicates that the OpenIddict server stack was asked to validate a token for an endpoint it doesn't manage.
This generally indicates that the OpenIddict server stack was asked to authenticate a request for an endpoint it doesn't manage.
To validate tokens received by custom API endpoints, the OpenIddict validation handler (e.g OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme or OpenIddictValidationOwinDefaults.AuthenticationType) must be used instead.</value>
</data>
<data name="ID0003" xml:space="preserve">
@ -426,19 +422,19 @@ To use key rollover, register both the new certificate and the old one in the cr
<value>No custom authorization request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateAuthorizationRequestContext&gt;' must be implemented to validate authorization requests (e.g to ensure the client_id and redirect_uri are valid).</value>
</data>
<data name="ID0090" xml:space="preserve">
<value>No custom device request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateDeviceRequestContext&gt;' must be implemented to validate device requests (e.g to ensure the client_id and client_secret are valid).</value>
<value>No custom device request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateDeviceRequestContext&gt;' (or 'IOpenIddictServerHandler&lt;ProcessAuthenticationContext&gt;') must be implemented to validate device requests (e.g to ensure the client_id and client_secret are valid).</value>
</data>
<data name="ID0091" xml:space="preserve">
<value>No custom introspection request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateIntrospectionRequestContext&gt;' must be implemented to validate introspection requests (e.g to ensure the client_id and client_secret are valid).</value>
<value>No custom introspection request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateIntrospectionRequestContext&gt;' (or 'IOpenIddictServerHandler&lt;ProcessAuthenticationContext&gt;') must be implemented to validate introspection requests (e.g to ensure the client_id and client_secret are valid).</value>
</data>
<data name="ID0092" xml:space="preserve">
<value>No custom logout request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateLogoutRequestContext&gt;' must be implemented to validate logout requests (e.g to ensure the post_logout_redirect_uri is valid).</value>
</data>
<data name="ID0093" xml:space="preserve">
<value>No custom revocation request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateRevocationRequestContext&gt;' must be implemented to validate revocation requests (e.g to ensure the client_id and client_secret are valid).</value>
<value>No custom revocation request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateRevocationRequestContext&gt;' (or 'IOpenIddictServerHandler&lt;ProcessAuthenticationContext&gt;') must be implemented to validate revocation requests (e.g to ensure the client_id and client_secret are valid).</value>
</data>
<data name="ID0094" xml:space="preserve">
<value>No custom token request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateTokenRequestContext&gt;' must be implemented to validate token requests (e.g to ensure the client_id and client_secret are valid).</value>
<value>No custom token request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateTokenRequestContext&gt;' (or 'IOpenIddictServerHandler&lt;ProcessAuthenticationContext&gt;') must be implemented to validate token requests (e.g to ensure the client_id and client_secret are valid).</value>
</data>
<data name="ID0095" xml:space="preserve">
<value>No custom verification request validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateVerificationRequestContext&gt;' must be implemented to validate verification requests (e.g to ensure the user_code is valid).</value>
@ -2235,9 +2231,6 @@ The principal used to create the token contained the following claims: {Claims}.
<data name="ID6043" xml:space="preserve">
<value>The authorization request was rejected because the specified response type was not compatible with PKCE.</value>
</data>
<data name="ID6044" xml:space="preserve">
<value>The authorization request was rejected because the client application was not found: '{ClientId}'.</value>
</data>
<data name="ID6045" xml:space="preserve">
<value>The authorization request was rejected because the confidential application '{ClientId}' was not allowed to retrieve an access token from the authorization endpoint.</value>
</data>
@ -2271,24 +2264,9 @@ The principal used to create the token contained the following claims: {Claims}.
<data name="ID6055" xml:space="preserve">
<value>The device request was successfully validated.</value>
</data>
<data name="ID6056" xml:space="preserve">
<value>The device request was rejected because the mandatory '{Parameter}' parameter was missing.</value>
</data>
<data name="ID6057" xml:space="preserve">
<value>The device request was rejected because invalid scopes were specified: {Scopes}.</value>
</data>
<data name="ID6058" xml:space="preserve">
<value>The device request was rejected because the client application was not found: '{ClientId}'.</value>
</data>
<data name="ID6059" xml:space="preserve">
<value>The device request was rejected because the public application '{ClientId}' was not allowed to send a client secret.</value>
</data>
<data name="ID6060" xml:space="preserve">
<value>The device request was rejected because the confidential application '{ClientId}' didn't specify a client secret.</value>
</data>
<data name="ID6061" xml:space="preserve">
<value>The device request was rejected because the confidential application '{ClientId}' didn't specify valid client credentials.</value>
</data>
<data name="ID6062" xml:space="preserve">
<value>The device request was rejected because the application '{ClientId}' was not allowed to use the device endpoint.</value>
</data>
@ -2346,21 +2324,6 @@ The principal used to create the token contained the following claims: {Claims}.
<data name="ID6080" xml:space="preserve">
<value>The token request was rejected because invalid scopes were specified: {Scopes}.</value>
</data>
<data name="ID6081" xml:space="preserve">
<value>The token request was rejected because the client application was not found: '{ClientId}'.</value>
</data>
<data name="ID6082" xml:space="preserve">
<value>The token request was rejected because the public client application '{ClientId}' was not allowed to use the client credentials grant.</value>
</data>
<data name="ID6083" xml:space="preserve">
<value>The token request was rejected because the public application '{ClientId}' was not allowed to send a client secret.</value>
</data>
<data name="ID6084" xml:space="preserve">
<value>The token request was rejected because the confidential application '{ClientId}' didn't specify a client secret.</value>
</data>
<data name="ID6085" xml:space="preserve">
<value>The token request was rejected because the confidential application '{ClientId}' didn't specify valid client credentials.</value>
</data>
<data name="ID6086" xml:space="preserve">
<value>The token request was rejected because the application '{ClientId}' was not allowed to use the token endpoint.</value>
</data>
@ -2400,18 +2363,6 @@ The principal used to create the token contained the following claims: {Claims}.
<data name="ID6098" xml:space="preserve">
<value>The introspection request was rejected because the mandatory '{Parameter}' parameter was missing.</value>
</data>
<data name="ID6099" xml:space="preserve">
<value>The introspection request was rejected because the client application was not found: '{ClientId}'.</value>
</data>
<data name="ID6100" xml:space="preserve">
<value>The introspection request was rejected because the public application '{ClientId}' was not allowed to send a client secret.</value>
</data>
<data name="ID6101" xml:space="preserve">
<value>The introspection request was rejected because the confidential application '{ClientId}' didn't specify a client secret.</value>
</data>
<data name="ID6102" xml:space="preserve">
<value>The introspection request was rejected because the confidential application '{ClientId}' didn't specify valid client credentials.</value>
</data>
<data name="ID6103" xml:space="preserve">
<value>The introspection request was rejected because the application '{ClientId}' was not allowed to use the introspection endpoint.</value>
</data>
@ -2439,18 +2390,6 @@ The principal used to create the token contained the following claims: {Claims}.
<data name="ID6111" xml:space="preserve">
<value>The revocation request was rejected because the mandatory '{Parameter}' parameter was missing.</value>
</data>
<data name="ID6112" xml:space="preserve">
<value>The revocation request was rejected because the client application was not found: '{ClientId}'.</value>
</data>
<data name="ID6113" xml:space="preserve">
<value>The revocation request was rejected because the public application '{ClientId}' was not allowed to send a client secret.</value>
</data>
<data name="ID6114" xml:space="preserve">
<value>The revocation request was rejected because the confidential application '{ClientId}' didn't specify a client secret.</value>
</data>
<data name="ID6115" xml:space="preserve">
<value>The revocation request was rejected because the confidential application '{ClientId}' didn't specify valid client credentials.</value>
</data>
<data name="ID6116" xml:space="preserve">
<value>The revocation request was rejected because the application '{ClientId}' was not allowed to use the revocation endpoint.</value>
</data>
@ -2689,9 +2628,6 @@ This may indicate that the hashed entry is corrupted or malformed.</value>
<data name="ID6195" xml:space="preserve">
<value>The userinfo response returned by {Uri} was successfully extracted: {Response}.</value>
</data>
<data name="ID6196" xml:space="preserve">
<value>The logout request was rejected because the client application was not found: '{ClientId}'.</value>
</data>
<data name="ID6197" xml:space="preserve">
<value>The authorization request was rejected because the identity token used as a hint was issued to a different client.</value>
</data>
@ -2758,6 +2694,24 @@ This may indicate that the hashed entry is corrupted or malformed.</value>
<data name="ID6219" xml:space="preserve">
<value>An error occurred while retrieving the configuration of the remote authorization server.</value>
</data>
<data name="ID6220" xml:space="preserve">
<value>The authentication demand was rejected because the mandatory '{Parameter}' parameter was missing.</value>
</data>
<data name="ID6221" xml:space="preserve">
<value>The authentication demand was rejected because the client application was not found: '{ClientId}'.</value>
</data>
<data name="ID6222" xml:space="preserve">
<value>The authentication demand was rejected because the public client application '{ClientId}' was not allowed to use the client credentials grant.</value>
</data>
<data name="ID6223" xml:space="preserve">
<value>The authentication demand was rejected because the public application '{ClientId}' was not allowed to send a client secret.</value>
</data>
<data name="ID6224" xml:space="preserve">
<value>The authentication demand was rejected because the confidential application '{ClientId}' didn't specify a client secret.</value>
</data>
<data name="ID6225" xml:space="preserve">
<value>The authentication demand was rejected because the confidential application '{ClientId}' didn't specify valid client credentials.</value>
</data>
<data name="ID8000" xml:space="preserve">
<value>https://documentation.openiddict.com/errors/{0}</value>
</data>

26
src/OpenIddict.Server/OpenIddictServerConfiguration.cs

@ -178,7 +178,8 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
}
if (options.DeviceEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
descriptor.ContextType == typeof(ValidateDeviceRequestContext) &&
(descriptor.ContextType == typeof(ValidateDeviceRequestContext) ||
descriptor.ContextType == typeof(ProcessAuthenticationContext)) &&
descriptor.Type == OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
@ -186,7 +187,8 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
}
if (options.IntrospectionEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
descriptor.ContextType == typeof(ValidateIntrospectionRequestContext) &&
(descriptor.ContextType == typeof(ValidateIntrospectionRequestContext) ||
descriptor.ContextType == typeof(ProcessAuthenticationContext)) &&
descriptor.Type == OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
@ -202,7 +204,8 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
}
if (options.RevocationEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
descriptor.ContextType == typeof(ValidateRevocationRequestContext) &&
(descriptor.ContextType == typeof(ValidateRevocationRequestContext) ||
descriptor.ContextType == typeof(ProcessAuthenticationContext)) &&
descriptor.Type == OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
@ -210,7 +213,8 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
}
if (options.TokenEndpointUris.Count is not 0 && !options.Handlers.Exists(static descriptor =>
descriptor.ContextType == typeof(ValidateTokenRequestContext) &&
(descriptor.ContextType == typeof(ValidateTokenRequestContext) ||
descriptor.ContextType == typeof(ProcessAuthenticationContext)) &&
descriptor.Type == OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
@ -232,16 +236,16 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
{
if (!options.Handlers.Exists(static descriptor =>
descriptor.ContextType == typeof(ValidateTokenContext) &&
descriptor.Type == OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(static type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0096));
}
if (!options.Handlers.Exists(static descriptor =>
descriptor.ContextType == typeof(GenerateTokenContext) &&
descriptor.Type == OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
descriptor.Type is OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(static type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0097));
}
@ -249,11 +253,11 @@ public sealed class OpenIddictServerConfiguration : IPostConfigureOptions<OpenId
}
// Sort the handlers collection using the order associated with each handler.
options.Handlers.Sort((left, right) => left.Order.CompareTo(right.Order));
options.Handlers.Sort(static (left, right) => left.Order.CompareTo(right.Order));
// Sort the encryption and signing credentials.
options.EncryptionCredentials.Sort((left, right) => Compare(left.Key, right.Key));
options.SigningCredentials.Sort((left, right) => Compare(left.Key, right.Key));
options.EncryptionCredentials.Sort(static (left, right) => Compare(left.Key, right.Key));
options.SigningCredentials.Sort(static (left, right) => Compare(left.Key, right.Key));
// Generate a key identifier for the encryption/signing keys that don't already have one.
foreach (var key in options.EncryptionCredentials.Select(credentials => credentials.Key)

2
src/OpenIddict.Server/OpenIddictServerEvents.cs

@ -294,7 +294,7 @@ public static partial class OpenIddictServerEvents
/// <summary>
/// Represents an event called when processing an authentication operation.
/// </summary>
public sealed class ProcessAuthenticationContext : BaseValidatingContext
public sealed class ProcessAuthenticationContext : BaseValidatingClientContext
{
/// <summary>
/// Creates a new instance of the <see cref="ProcessAuthenticationContext"/> class.

1
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -48,6 +48,7 @@ public static class OpenIddictServerExtensions
builder.Services.TryAddSingleton<RequireAuthorizationStorageEnabled>();
builder.Services.TryAddSingleton<RequireAuthorizationRequest>();
builder.Services.TryAddSingleton<RequireClientIdParameter>();
builder.Services.TryAddSingleton<RequireClientSecretParameter>();
builder.Services.TryAddSingleton<RequireConfigurationRequest>();
builder.Services.TryAddSingleton<RequireCryptographyRequest>();
builder.Services.TryAddSingleton<RequireDegradedModeDisabled>();

17
src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs

@ -147,6 +147,23 @@ public static class OpenIddictServerHandlerFilters
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers when no client secret is received.
/// </summary>
public sealed class RequireClientSecretParameter : IOpenIddictServerHandlerFilter<BaseContext>
{
/// <inheritdoc/>
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!string.IsNullOrEmpty(context.Transaction.Request?.ClientSecret));
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the request is not a configuration request.
/// </summary>

128
src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs

@ -42,8 +42,8 @@ public static partial class OpenIddictServerHandlers
ValidateNonceParameter.Descriptor,
ValidatePromptParameter.Descriptor,
ValidateProofKeyForCodeExchangeParameters.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateAuthentication.Descriptor,
ValidateResponseType.Descriptor,
ValidateClientRedirectUri.Descriptor,
ValidateScopes.Descriptor,
ValidateEndpointPermissions.Descriptor,
@ -51,7 +51,6 @@ public static partial class OpenIddictServerHandlers
ValidateResponseTypePermissions.Descriptor,
ValidateScopePermissions.Descriptor,
ValidateProofKeyForCodeExchangeRequirement.Descriptor,
ValidateToken.Descriptor,
ValidateAuthorizedParty.Descriptor,
/*
@ -1019,25 +1018,21 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for rejecting authorization requests that use an invalid client_id.
/// Note: this handler is not used when the degraded mode is enabled.
/// Contains the logic responsible for applying the authentication logic to authorization requests.
/// </summary>
public sealed class ValidateClientId : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
public sealed class ValidateAuthentication : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateClientId(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
public ValidateAuthentication(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientId>()
.UseScopedHandler<ValidateAuthentication>()
.SetOrder(ValidateProofKeyForCodeExchangeParameters.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -1050,20 +1045,36 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
if (notification.IsRequestHandled)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6044), context.ClientId);
context.HandleRequest();
return;
}
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2052(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2052));
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
else if (notification.IsRejected)
{
context.Reject(
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.IdentityTokenHintPrincipal = notification.IdentityTokenPrincipal;
}
}
@ -1073,13 +1084,13 @@ public static partial class OpenIddictServerHandlers
/// Note: this handler is not used when the degraded mode is enabled
/// or when response type permissions enforcement is not disabled.
/// </summary>
public sealed class ValidateClientType : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
public sealed class ValidateResponseType : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateResponseType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientType(IOpenIddictApplicationManager applicationManager)
public ValidateResponseType(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
@ -1088,8 +1099,8 @@ public static partial class OpenIddictServerHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.UseScopedHandler<ValidateResponseType>()
.SetOrder(ValidateAuthentication.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -1151,7 +1162,7 @@ public static partial class OpenIddictServerHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientRedirectUri>()
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.SetOrder(ValidateResponseType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -1635,67 +1646,6 @@ public static partial class OpenIddictServerHandlers
}
}
/// <summary>
/// Contains the logic responsible for validating the token(s) present in the request.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateToken(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseScopedHandler<ValidateToken>()
.SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
if (notification.IsRequestHandled)
{
context.HandleRequest();
return;
}
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
else if (notification.IsRejected)
{
context.Reject(
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.IdentityTokenHintPrincipal = notification.IdentityTokenPrincipal;
}
}
/// <summary>
/// Contains the logic responsible for rejecting authorization requests that specify an identity
/// token hint that cannot be used by the client application sending the authorization request.
@ -1708,7 +1658,7 @@ public static partial class OpenIddictServerHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateAuthorizedParty>()
.SetOrder(ValidateToken.Descriptor.Order + 1_000)
.SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();

224
src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs

@ -33,12 +33,9 @@ public static partial class OpenIddictServerHandlers
/*
* Device request validation:
*/
ValidateClientIdParameter.Descriptor,
ValidateScopeParameter.Descriptor,
ValidateScopes.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientSecret.Descriptor,
ValidateDeviceAuthentication.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateGrantTypePermissions.Descriptor,
ValidateScopePermissions.Descriptor,
@ -57,7 +54,7 @@ public static partial class OpenIddictServerHandlers
/*
* Verification request validation:
*/
ValidateToken.Descriptor,
ValidateVerificationAuthentication.Descriptor,
/*
* Verification request handling:
@ -332,47 +329,6 @@ public static partial class OpenIddictServerHandlers
}
}
/// <summary>
/// Contains the logic responsible for rejecting device requests that don't specify a client identifier.
/// </summary>
public sealed class ValidateClientIdParameter : IOpenIddictServerHandler<ValidateDeviceRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateDeviceRequestContext>()
.UseSingletonHandler<ValidateClientIdParameter>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateDeviceRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// client_id is a required parameter and MUST cause an error when missing.
// See https://tools.ietf.org/html/rfc8628#section-3.1 for more information.
if (string.IsNullOrEmpty(context.ClientId))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6056), Parameters.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2029(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2029));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for rejecting device requests that don't specify a valid scope parameter.
/// </summary>
@ -384,7 +340,7 @@ public static partial class OpenIddictServerHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateDeviceRequestContext>()
.UseSingletonHandler<ValidateScopeParameter>()
.SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000)
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -491,26 +447,21 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for rejecting device requests that use an invalid client_id.
/// Note: this handler is not used when the degraded mode is enabled.
/// Contains the logic responsible for applying the authentication logic to device requests.
/// </summary>
public sealed class ValidateClientId : IOpenIddictServerHandler<ValidateDeviceRequestContext>
public sealed class ValidateDeviceAuthentication : IOpenIddictServerHandler<ValidateDeviceRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateClientId(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
public ValidateDeviceAuthentication(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateDeviceRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientId>()
.UseScopedHandler<ValidateDeviceAuthentication>()
.SetOrder(ValidateScopes.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -523,152 +474,31 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
// Retrieve the application details corresponding to the requested client_id.
// If no entity can be found, this likely indicates that the client_id is invalid.
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6058), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2052(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2052));
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting device requests made by applications
/// whose client type is not compatible with the requested grant type.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientType : IOpenIddictServerHandler<ValidateDeviceRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientType(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateDeviceRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateDeviceRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
{
// Reject device requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6059), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2053(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2053));
return;
}
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
return;
}
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
// Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks.
if (string.IsNullOrEmpty(context.ClientSecret))
if (notification.IsRequestHandled)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6060), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2054(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2054));
context.HandleRequest();
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting device requests specifying an invalid client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientSecret : IOpenIddictServerHandler<ValidateDeviceRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientSecret(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateDeviceRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientSecret>()
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateDeviceRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
// If the application is a public client, don't validate the client secret.
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret));
if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret))
else if (notification.IsRejected)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6061), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.GetResourceString(SR.ID2055),
uri: SR.FormatID8000(SR.ID2055));
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
}
@ -697,7 +527,7 @@ public static partial class OpenIddictServerHandlers
.AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireEndpointPermissionsEnabled>()
.UseScopedHandler<ValidateEndpointPermissions>()
.SetOrder(ValidateClientSecret.Descriptor.Order + 1_000)
.SetOrder(ValidateDeviceAuthentication.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -1131,13 +961,13 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for validating the token(s) present in the request.
/// Contains the logic responsible for applying the authentication logic to verification requests.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateVerificationRequestContext>
public sealed class ValidateVerificationAuthentication : IOpenIddictServerHandler<ValidateVerificationRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateToken(IOpenIddictServerDispatcher dispatcher)
public ValidateVerificationAuthentication(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
@ -1145,7 +975,7 @@ public static partial class OpenIddictServerHandlers
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateVerificationRequestContext>()
.UseScopedHandler<ValidateToken>()
.UseScopedHandler<ValidateVerificationAuthentication>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();

276
src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs

@ -46,14 +46,11 @@ public static partial class OpenIddictServerHandlers
ValidateProofKeyForCodeExchangeParameters.Descriptor,
ValidateScopeParameter.Descriptor,
ValidateScopes.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientSecret.Descriptor,
ValidateAuthentication.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateGrantTypePermissions.Descriptor,
ValidateScopePermissions.Descriptor,
ValidateProofKeyForCodeExchangeRequirement.Descriptor,
ValidateToken.Descriptor,
ValidatePresenters.Descriptor,
ValidateRedirectUri.Descriptor,
ValidateCodeVerifier.Descriptor,
@ -425,17 +422,12 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
if (!string.IsNullOrEmpty(context.ClientId))
{
return default;
}
// At this stage, reject the token request unless the client identification requirement was disabled.
// Independently of this setting, also reject grant_type=authorization_code requests that don't specify
// a client_id, as the client identifier MUST be sent by the client application in the request body
// if it cannot be inferred from the client authentication method (e.g the username when using basic).
// Reject grant_type=authorization_code requests that don't specify a client_id, as the client
// identifier MUST be sent by the client application in the request body if it cannot be
// inferred from the client authentication method (e.g the username when using basic).
//
// See https://tools.ietf.org/html/rfc6749#section-4.1.3 for more information.
if (!context.Options.AcceptAnonymousClients || context.Request.IsAuthorizationCodeGrantType())
if (string.IsNullOrEmpty(context.ClientId) && context.Request.IsAuthorizationCodeGrantType())
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6077), Parameters.ClientId);
@ -834,26 +826,21 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for rejecting token requests that use an invalid client_id.
/// Note: this handler is not used when the degraded mode is enabled.
/// Contains the logic responsible for applying the authentication logic to token requests.
/// </summary>
public sealed class ValidateClientId : IOpenIddictServerHandler<ValidateTokenRequestContext>
public sealed class ValidateAuthentication : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateClientId(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
public ValidateAuthentication(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientId>()
.UseScopedHandler<ValidateAuthentication>()
.SetOrder(ValidateScopes.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -866,167 +853,38 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
// Retrieve the application details corresponding to the requested client_id.
// If no entity can be found, this likely indicates that the client_id is invalid.
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6081), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2052(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2052));
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting token requests made by applications
/// whose client type is not compatible with the requested grant type.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientType : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientType(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
{
// Public applications are not allowed to use the client credentials grant.
if (context.Request.IsClientCredentialsGrantType())
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6082), context.Request.ClientId);
context.Reject(
error: Errors.UnauthorizedClient,
description: SR.FormatID2043(Parameters.GrantType),
uri: SR.FormatID8000(SR.ID2043));
return;
}
// Reject token requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6083), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2053(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2053));
return;
}
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
return;
}
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
// Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks.
if (string.IsNullOrEmpty(context.ClientSecret))
if (notification.IsRequestHandled)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6084), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2054(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2054));
context.HandleRequest();
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting token requests specifying an invalid client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientSecret : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientSecret(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientSecret>()
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
// If the application is a public client, don't validate the client secret.
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret));
if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret))
else if (notification.IsRejected)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6085), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.GetResourceString(SR.ID2055),
uri: SR.FormatID8000(SR.ID2055));
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = context.Request.IsAuthorizationCodeGrantType() ? notification.AuthorizationCodePrincipal :
context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal :
context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null;
}
}
@ -1053,7 +911,7 @@ public static partial class OpenIddictServerHandlers
.AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireEndpointPermissionsEnabled>()
.UseScopedHandler<ValidateEndpointPermissions>()
.SetOrder(ValidateClientSecret.Descriptor.Order + 1_000)
.SetOrder(ValidateAuthentication.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -1285,76 +1143,6 @@ public static partial class OpenIddictServerHandlers
}
}
/// <summary>
/// Contains the logic responsible for validating the token(s) present in the request.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateToken(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
.UseScopedHandler<ValidateToken>()
.SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Request.IsAuthorizationCodeGrantType() &&
!context.Request.IsDeviceCodeGrantType() &&
!context.Request.IsRefreshTokenGrantType())
{
return;
}
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
if (notification.IsRequestHandled)
{
context.HandleRequest();
return;
}
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
else if (notification.IsRejected)
{
context.Reject(
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = context.Request.IsAuthorizationCodeGrantType() ? notification.AuthorizationCodePrincipal :
context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal :
context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null;
}
}
/// <summary>
/// Contains the logic responsible for rejecting token requests that use an authorization code,
/// a device code or a refresh token that was issued for a different client application.
@ -1367,7 +1155,7 @@ public static partial class OpenIddictServerHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenRequestContext>()
.UseSingletonHandler<ValidatePresenters>()
.SetOrder(ValidateToken.Descriptor.Order + 1_000)
.SetOrder(ValidateProofKeyForCodeExchangeRequirement.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();

280
src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs

@ -35,12 +35,8 @@ public static partial class OpenIddictServerHandlers
* Introspection request validation:
*/
ValidateTokenParameter.Descriptor,
ValidateClientIdParameter.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientSecret.Descriptor,
ValidateAuthentication.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateToken.Descriptor,
ValidateTokenType.Descriptor,
ValidateAuthorizedParty.Descriptor,
@ -371,120 +367,22 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for rejecting introspection requests that don't specify a client identifier.
/// Contains the logic responsible for applying the authentication logic to introspection requests.
/// </summary>
public sealed class ValidateClientIdParameter : IOpenIddictServerHandler<ValidateIntrospectionRequestContext>
public sealed class ValidateAuthentication : IOpenIddictServerHandler<ValidateIntrospectionRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateIntrospectionRequestContext>()
.UseSingletonHandler<ValidateClientIdParameter>()
.SetOrder(ValidateTokenParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateIntrospectionRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// At this stage, reject the introspection request unless the client identification requirement was disabled.
if (!context.Options.AcceptAnonymousClients && string.IsNullOrEmpty(context.ClientId))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6098), Parameters.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2029(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2029));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for rejecting introspection requests that use an invalid client_id.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientId : IOpenIddictServerHandler<ValidateIntrospectionRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientId(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateIntrospectionRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientId>()
.SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
// Retrieve the application details corresponding to the requested client_id.
// If no entity can be found, this likely indicates that the client_id is invalid.
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6099), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2052(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2052));
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting introspection requests made by applications
/// whose client type is not compatible with the presence or absence of a client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientType : IOpenIddictServerHandler<ValidateIntrospectionRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateClientType(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
public ValidateAuthentication(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateIntrospectionRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.UseScopedHandler<ValidateAuthentication>()
.SetOrder(ValidateTokenParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -496,101 +394,36 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
{
// Reject introspection requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6100), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2053(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2053));
return;
}
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
return;
}
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
// Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks.
if (string.IsNullOrEmpty(context.ClientSecret))
if (notification.IsRequestHandled)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6101), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2054(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2054));
context.HandleRequest();
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting introspection requests specifying an invalid client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientSecret : IOpenIddictServerHandler<ValidateIntrospectionRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientSecret(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateIntrospectionRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientSecret>()
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
// If the application is a public client, don't validate the client secret.
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret));
if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret))
else if (notification.IsRejected)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6102), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.GetResourceString(SR.ID2055),
uri: SR.FormatID8000(SR.ID2055));
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.GenericTokenPrincipal;
}
}
@ -617,7 +450,7 @@ public static partial class OpenIddictServerHandlers
.AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireEndpointPermissionsEnabled>()
.UseScopedHandler<ValidateEndpointPermissions>()
.SetOrder(ValidateClientSecret.Descriptor.Order + 1_000)
.SetOrder(ValidateAuthentication.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -649,67 +482,6 @@ public static partial class OpenIddictServerHandlers
}
}
/// <summary>
/// Contains the logic responsible for validating the token(s) present in the request.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateIntrospectionRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateToken(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateIntrospectionRequestContext>()
.UseScopedHandler<ValidateToken>()
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateIntrospectionRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
if (notification.IsRequestHandled)
{
context.HandleRequest();
return;
}
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
else if (notification.IsRejected)
{
context.Reject(
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.GenericTokenPrincipal;
}
}
/// <summary>
/// Contains the logic responsible for rejecting introspection requests that specify an unsupported token.
/// </summary>
@ -721,7 +493,7 @@ public static partial class OpenIddictServerHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateIntrospectionRequestContext>()
.UseSingletonHandler<ValidateTokenType>()
.SetOrder(ValidateToken.Descriptor.Order + 1_000)
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();

280
src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs

@ -29,12 +29,8 @@ public static partial class OpenIddictServerHandlers
* Revocation request validation:
*/
ValidateTokenParameter.Descriptor,
ValidateClientIdParameter.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientSecret.Descriptor,
ValidateAuthentication.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateToken.Descriptor,
ValidateTokenType.Descriptor,
ValidateAuthorizedParty.Descriptor,
@ -318,120 +314,22 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for rejecting revocation requests that don't specify a client identifier.
/// Contains the logic responsible for applying the authentication logic to revocation requests.
/// </summary>
public sealed class ValidateClientIdParameter : IOpenIddictServerHandler<ValidateRevocationRequestContext>
public sealed class ValidateAuthentication : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.UseSingletonHandler<ValidateClientIdParameter>()
.SetOrder(ValidateTokenParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateRevocationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// At this stage, reject the revocation request unless the client identification requirement was disabled.
if (!context.Options.AcceptAnonymousClients && string.IsNullOrEmpty(context.ClientId))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6111), Parameters.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2029(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2029));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for rejecting revocation requests that use an invalid client_id.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientId : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientId(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientId>()
.SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateRevocationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
// Retrieve the application details corresponding to the requested client_id.
// If no entity can be found, this likely indicates that the client_id is invalid.
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6112), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2052(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2052));
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting revocation requests made by applications
/// whose client type is not compatible with the presence or absence of a client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientType : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateClientType(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
public ValidateAuthentication(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.UseScopedHandler<ValidateAuthentication>()
.SetOrder(ValidateTokenParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -443,101 +341,36 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
{
// Reject revocation requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6113), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2053(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2053));
return;
}
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
return;
}
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
// Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks.
if (string.IsNullOrEmpty(context.ClientSecret))
if (notification.IsRequestHandled)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6114), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2054(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2054));
context.HandleRequest();
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting revocation requests specifying an invalid client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientSecret : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientSecret(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientSecret>()
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateRevocationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
// If the application is a public client, don't validate the client secret.
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret));
if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret))
else if (notification.IsRejected)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6115), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.GetResourceString(SR.ID2055),
uri: SR.FormatID8000(SR.ID2055));
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.GenericTokenPrincipal;
}
}
@ -564,7 +397,7 @@ public static partial class OpenIddictServerHandlers
.AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequireEndpointPermissionsEnabled>()
.UseScopedHandler<ValidateEndpointPermissions>()
.SetOrder(ValidateClientSecret.Descriptor.Order + 1_000)
.SetOrder(ValidateAuthentication.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -596,67 +429,6 @@ public static partial class OpenIddictServerHandlers
}
}
/// <summary>
/// Contains the logic responsible for validating the token(s) present in the request.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateToken(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.UseScopedHandler<ValidateToken>()
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateRevocationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
if (notification.IsRequestHandled)
{
context.HandleRequest();
return;
}
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
else if (notification.IsRejected)
{
context.Reject(
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.GenericTokenPrincipal;
}
}
/// <summary>
/// Contains the logic responsible for rejecting revocation requests that specify an unsupported token.
/// </summary>
@ -668,7 +440,7 @@ public static partial class OpenIddictServerHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateRevocationRequestContext>()
.UseSingletonHandler<ValidateTokenType>()
.SetOrder(ValidateToken.Descriptor.Order + 1_000)
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();

121
src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs

@ -33,10 +33,9 @@ public static partial class OpenIddictServerHandlers
* Logout request validation:
*/
ValidatePostLogoutRedirectUriParameter.Descriptor,
ValidateClientId.Descriptor,
ValidateAuthentication.Descriptor,
ValidateClientPostLogoutRedirectUri.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateToken.Descriptor,
ValidateAuthorizedParty.Descriptor,
/*
@ -375,30 +374,21 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for rejecting logout requests
/// that use an invalid client_id, if one was explicitly specified.
/// Note: this handler is not used when the degraded mode is enabled.
/// Contains the logic responsible for applying the authentication logic to logout requests.
/// </summary>
public sealed class ValidateClientId : IOpenIddictServerHandler<ValidateLogoutRequestContext>
public sealed class ValidateAuthentication : IOpenIddictServerHandler<ValidateLogoutRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateClientId(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
public ValidateAuthentication(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateLogoutRequestContext>()
.AddFilter<RequireDegradedModeDisabled>()
// Note: support for the client_id parameter was only added in the second draft of the
// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout specification
// and is optional. As such, the client identifier is only validated if it was specified.
.AddFilter<RequireClientIdParameter>()
.UseScopedHandler<ValidateClientId>()
.UseScopedHandler<ValidateAuthentication>()
.SetOrder(ValidatePostLogoutRedirectUriParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -411,20 +401,36 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
if (notification.IsRequestHandled)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6196), context.ClientId);
context.HandleRequest();
return;
}
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2052(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2052));
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
else if (notification.IsRejected)
{
context.Reject(
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.IdentityTokenHintPrincipal = notification.IdentityTokenPrincipal;
}
}
@ -450,7 +456,7 @@ public static partial class OpenIddictServerHandlers
.AddFilter<RequireDegradedModeDisabled>()
.AddFilter<RequirePostLogoutRedirectUriParameter>()
.UseScopedHandler<ValidateClientPostLogoutRedirectUri>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.SetOrder(ValidateAuthentication.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@ -629,67 +635,6 @@ public static partial class OpenIddictServerHandlers
}
}
/// <summary>
/// Contains the logic responsible for validating the token(s) present in the request.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateLogoutRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateToken(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateLogoutRequestContext>()
.UseScopedHandler<ValidateToken>()
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateLogoutRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the authentication result without triggering a new authentication flow.
context.Transaction.SetProperty(typeof(ProcessAuthenticationContext).FullName!, notification);
if (notification.IsRequestHandled)
{
context.HandleRequest();
return;
}
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
else if (notification.IsRejected)
{
context.Reject(
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Attach the security principal extracted from the token to the validation context.
context.IdentityTokenHintPrincipal = notification.IdentityTokenPrincipal;
}
}
/// <summary>
/// Contains the logic responsible for rejecting logout requests that specify an identity
/// token hint that cannot be used by the client application sending the logout request.
@ -717,7 +662,7 @@ public static partial class OpenIddictServerHandlers
new ValidateAuthorizedParty(provider.GetService<IOpenIddictApplicationManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(ValidateToken.Descriptor.Order + 1_000)
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();

10
src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs

@ -30,7 +30,7 @@ public static partial class OpenIddictServerHandlers
* Userinfo request validation:
*/
ValidateAccessTokenParameter.Descriptor,
ValidateToken.Descriptor,
ValidateAuthentication.Descriptor,
/*
* Userinfo request handling:
@ -342,13 +342,13 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for validating the token(s) present in the request.
/// Contains the logic responsible for applying the authentication logic to userinfo requests.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateUserinfoRequestContext>
public sealed class ValidateAuthentication : IOpenIddictServerHandler<ValidateUserinfoRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateToken(IOpenIddictServerDispatcher dispatcher)
public ValidateAuthentication(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
@ -356,7 +356,7 @@ public static partial class OpenIddictServerHandlers
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateUserinfoRequestContext>()
.UseScopedHandler<ValidateToken>()
.UseScopedHandler<ValidateAuthentication>()
.SetOrder(ValidateAccessTokenParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();

302
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -34,6 +34,9 @@ public static partial class OpenIddictServerHandlers
EvaluateValidatedTokens.Descriptor,
ResolveValidatedTokens.Descriptor,
ValidateRequiredTokens.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientSecret.Descriptor,
ValidateAccessToken.Descriptor,
ValidateAuthorizationCode.Descriptor,
ValidateDeviceCode.Descriptor,
@ -52,8 +55,8 @@ public static partial class OpenIddictServerHandlers
AttachCustomChallengeParameters.Descriptor,
/*
* Sign-in processing:
*/
* Sign-in processing:
*/
ValidateSignInDemand.Descriptor,
RedeemTokenEntry.Descriptor,
RestoreInternalClaims.Descriptor,
@ -224,24 +227,16 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context));
}
switch (context.EndpointType)
return context.EndpointType switch
{
case OpenIddictServerEndpointType.Authorization:
case OpenIddictServerEndpointType.Introspection:
case OpenIddictServerEndpointType.Logout:
case OpenIddictServerEndpointType.Revocation:
case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType():
case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType():
case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType():
case OpenIddictServerEndpointType.Userinfo:
case OpenIddictServerEndpointType.Verification:
return default;
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Device or
OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Logout or
OpenIddictServerEndpointType.Revocation or OpenIddictServerEndpointType.Token or
OpenIddictServerEndpointType.Userinfo or OpenIddictServerEndpointType.Verification
=> default,
case OpenIddictServerEndpointType.Token:
throw new InvalidOperationException(SR.GetResourceString(SR.ID0001));
default: throw new InvalidOperationException(SR.GetResourceString(SR.ID0002));
}
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0002)),
};
}
}
@ -361,7 +356,7 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// Contains the logic responsible for resolving the token from the incoming request.
/// Contains the logic responsible for resolving the tokens from the incoming request.
/// </summary>
public sealed class ResolveValidatedTokens : IOpenIddictServerHandler<ProcessAuthenticationContext>
{
@ -490,6 +485,273 @@ public static partial class OpenIddictServerHandlers
}
}
/// <summary>
/// Contains the logic responsible for rejecting authentication demands that use an invalid client_id.
/// </summary>
public sealed class ValidateClientId : IOpenIddictServerHandler<ProcessAuthenticationContext>
{
private readonly IOpenIddictApplicationManager? _applicationManager;
public ValidateClientId(IOpenIddictApplicationManager? applicationManager = null)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseScopedHandler<ValidateClientId>(static provider =>
{
// Note: the application manager is only resolved if the degraded mode was not enabled to ensure
// invalid core configuration exceptions are not thrown even if the managers were registered.
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
return options.EnableDegradedMode ?
new ValidateClientId() :
new ValidateClientId(provider.GetService<IOpenIddictApplicationManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(ValidateRequiredTokens.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Don't validate the client identifier on endpoint that don't support client identification.
if (context.EndpointType is OpenIddictServerEndpointType.Userinfo or OpenIddictServerEndpointType.Verification)
{
return;
}
if (string.IsNullOrEmpty(context.ClientId))
{
switch (context.EndpointType)
{
// Note: support for the client_id parameter was only added in the second draft of the
// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout specification
// and is optional. As such, the client identifier is only validated if it was specified.
case OpenIddictServerEndpointType.Logout:
return;
case OpenIddictServerEndpointType.Introspection when context.Options.AcceptAnonymousClients:
case OpenIddictServerEndpointType.Revocation when context.Options.AcceptAnonymousClients:
case OpenIddictServerEndpointType.Token when context.Options.AcceptAnonymousClients:
return;
}
context.Logger.LogInformation(SR.GetResourceString(SR.ID6220), Parameters.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2029(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2029));
return;
}
if (!context.Options.EnableDegradedMode)
{
if (_applicationManager is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
}
// Retrieve the application details corresponding to the requested client_id.
// If no entity can be found, this likely indicates that the client_id is invalid.
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6221), context.ClientId);
context.Reject(
error: context.EndpointType switch
{
// For non-interactive endpoints, return "invalid_client" instead of "invalid_request".
OpenIddictServerEndpointType.Device or OpenIddictServerEndpointType.Introspection or
OpenIddictServerEndpointType.Revocation or OpenIddictServerEndpointType.Token
=> Errors.InvalidClient,
_ => Errors.InvalidRequest
},
description: SR.FormatID2052(Parameters.ClientId),
uri: SR.FormatID8000(SR.ID2052));
return;
}
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting authentication demands made by applications
/// whose client type is not compatible with the presence of client credentials.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientType : IOpenIddictServerHandler<ProcessAuthenticationContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientType(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
// Don't validate the client type on endpoint that don't support client authentication.
if (context.EndpointType is OpenIddictServerEndpointType.Authorization or
OpenIddictServerEndpointType.Logout or
OpenIddictServerEndpointType.Userinfo or
OpenIddictServerEndpointType.Verification)
{
return;
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
{
// Reject grant_type=client_credentials token requests if the application is a public client.
if (context.EndpointType is OpenIddictServerEndpointType.Token &&
context.Request.IsClientCredentialsGrantType())
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6222), context.Request.ClientId);
context.Reject(
error: Errors.UnauthorizedClient,
description: SR.FormatID2043(Parameters.GrantType),
uri: SR.FormatID8000(SR.ID2043));
return;
}
// Reject requests containing a client_secret when the client is a public application.
if (!string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6223), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2053(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2053));
return;
}
return;
}
// Confidential and hybrid applications MUST authenticate to protect them from impersonation attacks.
if (string.IsNullOrEmpty(context.ClientSecret))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6224), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.FormatID2054(Parameters.ClientSecret),
uri: SR.FormatID8000(SR.ID2054));
return;
}
}
}
/// <summary>
/// Contains the logic responsible for rejecting authentication demands specifying an invalid client secret.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public sealed class ValidateClientSecret : IOpenIddictServerHandler<ProcessAuthenticationContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientSecret() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientSecret(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireClientIdParameter>()
.AddFilter<RequireClientSecretParameter>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientSecret>()
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
Debug.Assert(!string.IsNullOrEmpty(context.ClientSecret), SR.FormatID4000(Parameters.ClientSecret));
// Don't validate the client secret on endpoint that don't support client authentication.
if (context.EndpointType is OpenIddictServerEndpointType.Authorization or
OpenIddictServerEndpointType.Logout or
OpenIddictServerEndpointType.Userinfo or
OpenIddictServerEndpointType.Verification)
{
return;
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
// If the application is a public client, don't validate the client secret.
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Public))
{
return;
}
if (!await _applicationManager.ValidateClientSecretAsync(application, context.ClientSecret))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6225), context.ClientId);
context.Reject(
error: Errors.InvalidClient,
description: SR.GetResourceString(SR.ID2055),
uri: SR.FormatID8000(SR.ID2055));
return;
}
}
}
/// <summary>
/// Contains the logic responsible for validating the access token resolved from the context.
/// </summary>
@ -507,7 +769,7 @@ public static partial class OpenIddictServerHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAccessTokenValidated>()
.UseScopedHandler<ValidateAccessToken>()
.SetOrder(ValidateRequiredTokens.Descriptor.Order + 1_000)
.SetOrder(ValidateClientSecret.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();

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

@ -1846,7 +1846,31 @@ public abstract partial class OpenIddictServerIntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
.SetPresenters("Fabrikam")
.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56")
.SetClaim(Claims.Subject, "Bob le Bricoleur");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.Services.AddSingleton(manager);
options.SetDeviceEndpointUris(Array.Empty<Uri>());
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.Configure(options => options.GrantTypes.Remove(GrantTypes.DeviceCode));
options.DisableTokenStorage();
});
await using var client = await server.CreateClientAsync();

425
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs

@ -152,14 +152,12 @@ public abstract partial class OpenIddictServerIntegrationTests
}
[Fact]
public async Task ValidateIntrospectionRequest_InvalidTokenCausesAnError()
public async Task ValidateIntrospectionRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
options.Configure(options => options.AcceptAnonymousClients = false);
});
await using var client = await server.CreateClientAsync();
@ -167,86 +165,28 @@ public abstract partial class OpenIddictServerIntegrationTests
// Act
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
Token = "SlAV32hkKG"
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri);
Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri);
}
[Fact]
public async Task ValidateIntrospectionRequest_ExpiredTokenCausesAnError()
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCannotBeFound()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SlAV32hkKG", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1));
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
var manager = CreateApplicationManager(mock =>
{
Token = "SlAV32hkKG",
TokenTypeHint = TokenTypeHints.RefreshToken
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri);
}
[Theory]
[InlineData(TokenTypeHints.AuthorizationCode)]
[InlineData(TokenTypeHints.DeviceCode)]
[InlineData(TokenTypeHints.IdToken)]
[InlineData(TokenTypeHints.UserCode)]
[InlineData("custom_token")]
public async Task ValidateIntrospectionRequest_UnsupportedTokenTypeCausesAnError(string type)
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("5HtRgAtc02", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(type);
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
options.Services.AddSingleton(manager);
});
await using var client = await server.CreateClientAsync();
@ -255,76 +195,47 @@ public abstract partial class OpenIddictServerIntegrationTests
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "5HtRgAtc02"
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(Errors.UnsupportedTokenType, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2076), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2076), response.ErrorUri);
Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateIntrospectionRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter()
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetAudiences("AdventureWorks")
.SetPresenters("Contoso");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
var application = new OpenIddictApplication();
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
var manager = CreateApplicationManager(mock =>
{
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
await using var client = await server.CreateClientAsync();
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
// Act
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "2YotnFZFEjr1zCsicMWpAA",
TokenTypeHint = TokenTypeHints.AccessToken
mock.Setup(manager => manager.HasPermissionAsync(application,
Permissions.Endpoints.Introspection, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2077), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2077), response.ErrorUri);
}
[Fact]
public async Task ValidateIntrospectionRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetPresenters("Contoso");
.SetTokenType(TokenTypeHints.RefreshToken);
return default;
});
@ -332,7 +243,9 @@ public abstract partial class OpenIddictServerIntegrationTests
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
options.Services.AddSingleton(manager);
options.Configure(options => options.IgnoreEndpointPermissions = false);
});
await using var client = await server.CreateClientAsync();
@ -341,23 +254,37 @@ public abstract partial class OpenIddictServerIntegrationTests
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "8xLOxBtZp8",
TokenTypeHint = TokenTypeHints.RefreshToken
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2077), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2077), response.ErrorUri);
Assert.Equal(Errors.UnauthorizedClient, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2075), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2075), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
Permissions.Endpoints.Introspection, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateIntrospectionRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired()
public async Task ValidateIntrospectionRequest_ClientSecretCannotBeUsedByPublicClients()
{
// Arrange
await using var server = await CreateServerAsync(builder =>
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(mock =>
{
builder.Configure(options => options.AcceptAnonymousClients = false);
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
await using var server = await CreateServerAsync(options =>
{
options.Services.AddSingleton(manager);
});
await using var client = await server.CreateClientAsync();
@ -365,23 +292,33 @@ public abstract partial class OpenIddictServerIntegrationTests
// Act
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2029(Parameters.ClientId), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri);
Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCannotBeFound()
public async Task ValidateIntrospectionRequest_ClientSecretIsRequiredForNonPublicClients()
{
// Arrange
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(mock =>
{
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
.ReturnsAsync(application);
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
await using var server = await CreateServerAsync(options =>
@ -395,20 +332,21 @@ public abstract partial class OpenIddictServerIntegrationTests
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
ClientSecret = null,
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2052(Parameters.ClientId), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2052), response.ErrorUri);
Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenEndpointPermissionIsNotGranted()
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCredentialsAreInvalid()
{
// Arrange
var application = new OpenIddictApplication();
@ -419,18 +357,15 @@ public abstract partial class OpenIddictServerIntegrationTests
.ReturnsAsync(application);
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
.ReturnsAsync(false);
mock.Setup(manager => manager.HasPermissionAsync(application,
Permissions.Endpoints.Introspection, It.IsAny<CancellationToken>()))
mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
await using var server = await CreateServerAsync(options =>
{
options.Services.AddSingleton(manager);
options.Configure(options => options.IgnoreEndpointPermissions = false);
});
await using var client = await server.CreateClientAsync();
@ -439,37 +374,26 @@ public abstract partial class OpenIddictServerIntegrationTests
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
});
// Assert
Assert.Equal(Errors.UnauthorizedClient, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2075), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2075), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.HasPermissionAsync(application,
Permissions.Endpoints.Introspection, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ValidateIntrospectionRequest_ClientSecretCannotBeUsedByPublicClients()
public async Task ValidateIntrospectionRequest_InvalidTokenCausesAnError()
{
// Arrange
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(mock =>
await using var server = await CreateServerAsync(options =>
{
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
options.EnableDegradedMode();
await using var server = await CreateServerAsync(builder =>
{
builder.Services.AddSingleton(manager);
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
@ -477,38 +401,86 @@ public abstract partial class OpenIddictServerIntegrationTests
// Act
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
Token = "SlAV32hkKG"
});
// Assert
Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2053(Parameters.ClientSecret), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2053), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()), Times.Once());
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2004), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2004), response.ErrorUri);
}
[Fact]
public async Task ValidateIntrospectionRequest_ClientSecretIsRequiredForNonPublicClients()
public async Task ValidateIntrospectionRequest_ExpiredTokenCausesAnError()
{
// Arrange
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(mock =>
await using var server = await CreateServerAsync(options =>
{
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
options.EnableDegradedMode();
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SlAV32hkKG", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetExpirationDate(DateTimeOffset.UtcNow - TimeSpan.FromDays(1));
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
Token = "SlAV32hkKG",
TokenTypeHint = TokenTypeHints.RefreshToken
});
await using var server = await CreateServerAsync(builder =>
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2018), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2018), response.ErrorUri);
}
[Theory]
[InlineData(TokenTypeHints.AuthorizationCode)]
[InlineData(TokenTypeHints.DeviceCode)]
[InlineData(TokenTypeHints.IdToken)]
[InlineData(TokenTypeHints.UserCode)]
[InlineData("custom_token")]
public async Task ValidateIntrospectionRequest_UnsupportedTokenTypeCausesAnError(string type)
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
builder.Services.AddSingleton(manager);
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("5HtRgAtc02", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(type);
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
@ -517,40 +489,84 @@ public abstract partial class OpenIddictServerIntegrationTests
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
ClientSecret = null,
Token = "2YotnFZFEjr1zCsicMWpAA"
Token = "5HtRgAtc02"
});
// Assert
Assert.Equal(Errors.InvalidClient, response.Error);
Assert.Equal(SR.FormatID2054(Parameters.ClientSecret), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2054), response.ErrorUri);
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()), Times.Once());
Assert.Equal(Errors.UnsupportedTokenType, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2076), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2076), response.ErrorUri);
}
[Fact]
public async Task ValidateIntrospectionRequest_RequestIsRejectedWhenClientCredentialsAreInvalid()
public async Task ValidateIntrospectionRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter()
{
// Arrange
var application = new OpenIddictApplication();
var manager = CreateApplicationManager(mock =>
await using var server = await CreateServerAsync(options =>
{
mock.Setup(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
options.EnableDegradedMode();
mock.Setup(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token);
mock.Setup(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetAudiences("AdventureWorks")
.SetPresenters("Contoso");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "2YotnFZFEjr1zCsicMWpAA",
TokenTypeHint = TokenTypeHints.AccessToken
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2077), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2077), response.ErrorUri);
}
[Fact]
public async Task ValidateIntrospectionRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.Services.AddSingleton(manager);
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetPresenters("Contoso");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
@ -559,15 +575,14 @@ public abstract partial class OpenIddictServerIntegrationTests
var response = await client.PostAsync("/connect/introspect", new OpenIddictRequest
{
ClientId = "Fabrikam",
ClientSecret = "7Fjfp0ZBr1KtDRbnfVdmIw",
Token = "2YotnFZFEjr1zCsicMWpAA"
Token = "8xLOxBtZp8",
TokenTypeHint = TokenTypeHints.RefreshToken
});
// Assert
Mock.Get(manager).Verify(manager => manager.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.HasClientTypeAsync(application, ClientTypes.Public, It.IsAny<CancellationToken>()), Times.AtLeastOnce());
Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()), Times.Once());
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2077), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2077), response.ErrorUri);
}
[Theory]

304
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs

@ -7,6 +7,7 @@
using System.Net.Http;
using System.Security.Claims;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
using static OpenIddict.Server.OpenIddictServerEvents;
@ -150,146 +151,13 @@ public abstract partial class OpenIddictServerIntegrationTests
Assert.Equal(SR.FormatID8000(SR.ID2029), response.ErrorUri);
}
[Theory]
[InlineData(TokenTypeHints.AuthorizationCode)]
[InlineData(TokenTypeHints.DeviceCode)]
[InlineData(TokenTypeHints.IdToken)]
[InlineData(TokenTypeHints.UserCode)]
[InlineData("custom_token")]
public async Task ValidateRevocationRequest_UnsupportedTokenTypeCausesAnError(string type)
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("5HtRgAtc02", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(type);
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "5HtRgAtc02"
});
// Assert
Assert.Equal(Errors.UnsupportedTokenType, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2079), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2079), response.ErrorUri);
}
[Fact]
public async Task ValidateRevocationRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetAudiences("AdventureWorks")
.SetPresenters("Contoso");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "2YotnFZFEjr1zCsicMWpAA",
TokenTypeHint = TokenTypeHints.AccessToken
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri);
}
[Fact]
public async Task ValidateRevocationRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetPresenters("Contoso");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "8xLOxBtZp8",
TokenTypeHint = TokenTypeHints.RefreshToken
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri);
}
[Fact]
public async Task ValidateRevocationRequest_RequestWithoutClientIdIsRejectedWhenClientIdentificationIsRequired()
{
// Arrange
await using var server = await CreateServerAsync(builder =>
await using var server = await CreateServerAsync(options =>
{
builder.Configure(options => options.AcceptAnonymousClients = false);
options.Configure(options => options.AcceptAnonymousClients = false);
});
await using var client = await server.CreateClientAsync();
@ -317,9 +185,9 @@ public abstract partial class OpenIddictServerIntegrationTests
.ReturnsAsync(value: null);
});
await using var server = await CreateServerAsync(builder =>
await using var server = await CreateServerAsync(options =>
{
builder.Services.AddSingleton(manager);
options.Services.AddSingleton(manager);
});
await using var client = await server.CreateClientAsync();
@ -359,11 +227,26 @@ public abstract partial class OpenIddictServerIntegrationTests
.ReturnsAsync(false);
});
await using var server = await CreateServerAsync(builder =>
await using var server = await CreateServerAsync(options =>
{
builder.Services.AddSingleton(manager);
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SlAV32hkKG", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken);
builder.Configure(options => options.IgnoreEndpointPermissions = false);
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.Services.AddSingleton(manager);
options.Configure(options => options.IgnoreEndpointPermissions = false);
});
await using var client = await server.CreateClientAsync();
@ -401,9 +284,9 @@ public abstract partial class OpenIddictServerIntegrationTests
.ReturnsAsync(true);
});
await using var server = await CreateServerAsync(builder =>
await using var server = await CreateServerAsync(options =>
{
builder.Services.AddSingleton(manager);
options.Services.AddSingleton(manager);
});
await using var client = await server.CreateClientAsync();
@ -441,9 +324,9 @@ public abstract partial class OpenIddictServerIntegrationTests
.ReturnsAsync(false);
});
await using var server = await CreateServerAsync(builder =>
await using var server = await CreateServerAsync(options =>
{
builder.Services.AddSingleton(manager);
options.Services.AddSingleton(manager);
});
await using var client = await server.CreateClientAsync();
@ -510,6 +393,139 @@ public abstract partial class OpenIddictServerIntegrationTests
Mock.Get(manager).Verify(manager => manager.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()), Times.Once());
}
[Theory]
[InlineData(TokenTypeHints.AuthorizationCode)]
[InlineData(TokenTypeHints.DeviceCode)]
[InlineData(TokenTypeHints.IdToken)]
[InlineData(TokenTypeHints.UserCode)]
[InlineData("custom_token")]
public async Task ValidateRevocationRequest_UnsupportedTokenTypeCausesAnError(string type)
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("5HtRgAtc02", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(type);
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "5HtRgAtc02"
});
// Assert
Assert.Equal(Errors.UnsupportedTokenType, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2079), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2079), response.ErrorUri);
}
[Fact]
public async Task ValidateRevocationRequest_AccessTokenCausesAnErrorWhenCallerIsNotAValidAudienceOrPresenter()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetAudiences("AdventureWorks")
.SetPresenters("Contoso");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "2YotnFZFEjr1zCsicMWpAA",
TokenTypeHint = TokenTypeHints.AccessToken
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri);
}
[Fact]
public async Task ValidateRevocationRequest_RefreshTokenCausesAnErrorWhenCallerIsNotAValidPresenter()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
.SetPresenters("Contoso");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
await using var client = await server.CreateClientAsync();
// Act
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "8xLOxBtZp8",
TokenTypeHint = TokenTypeHints.RefreshToken
});
// Assert
Assert.Equal(Errors.InvalidToken, response.Error);
Assert.Equal(SR.GetResourceString(SR.ID2080), response.ErrorDescription);
Assert.Equal(SR.FormatID8000(SR.ID2080), response.ErrorUri);
}
[Theory]
[InlineData("custom_error", null, null)]
[InlineData("custom_error", "custom_description", null)]

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

@ -484,40 +484,6 @@ public abstract partial class OpenIddictServerIntegrationTests
Assert.Equal(SR.GetResourceString(SR.ID0002), exception.Message);
}
[Fact]
public async Task ProcessAuthentication_UnsupportedGrantTypeThrowsAnException()
{
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.EnableDegradedMode();
options.SetTokenEndpointUris("/authenticate");
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
context.SkipRequest();
return default;
}));
});
await using var client = await server.CreateClientAsync();
// Act and assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(delegate
{
return client.PostAsync("/authenticate", new OpenIddictRequest
{
GrantType = GrantTypes.Password,
Username = "johndoe",
Password = "A3ddj3w",
});
});
Assert.Equal(SR.GetResourceString(SR.ID0001), exception.Message);
}
[Fact]
public async Task ProcessAuthentication_MissingAccessTokenReturnsNull()
{
@ -3880,22 +3846,13 @@ public abstract partial class OpenIddictServerIntegrationTests
options.AddEventHandler<ValidateAuthorizationRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateDeviceRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateIntrospectionRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateLogoutRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateRevocationRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateTokenRequestContext>(builder =>
options.AddEventHandler<ValidateVerificationRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateVerificationRequestContext>(builder =>
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ValidateTokenContext>(builder =>

Loading…
Cancel
Save