Browse Source

Revamp the token authentication/generation mechanism and introduce new events

pull/1305/head
Kévin Chalet 5 years ago
parent
commit
8004a82c1d
  1. 1
      samples/Mvc.Server/Startup.cs
  2. 9
      src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
  3. 62
      src/OpenIddict.Abstractions/OpenIddictResources.resx
  4. 35
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  5. 6
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs
  6. 140
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs
  7. 16
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlerFilters.cs
  8. 8
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs
  9. 131
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlerFilters.cs
  10. 332
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs
  11. 467
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs
  12. 6
      src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs
  13. 100
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs
  14. 89
      src/OpenIddict.Server.Owin/OpenIddictServerOwinProperties.cs
  15. 9
      src/OpenIddict.Server/OpenIddictServerConfiguration.cs
  16. 16
      src/OpenIddict.Server/OpenIddictServerConstants.cs
  17. 129
      src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs
  18. 184
      src/OpenIddict.Server/OpenIddictServerEvents.cs
  19. 7
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  20. 112
      src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
  21. 2
      src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
  22. 4
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  23. 6
      src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
  24. 1543
      src/OpenIddict.Server/OpenIddictServerHandlers.Protection.cs
  25. 6
      src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
  26. 6
      src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
  27. 2783
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  28. 1
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConstants.cs
  29. 61
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs
  30. 22
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs
  31. 130
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs
  32. 107
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs
  33. 1
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs
  34. 50
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs
  35. 22
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs
  36. 89
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinProperties.cs
  37. 16
      src/OpenIddict.Validation/OpenIddictValidationConstants.cs
  38. 4
      src/OpenIddict.Validation/OpenIddictValidationEvents.Discovery.cs
  39. 9
      src/OpenIddict.Validation/OpenIddictValidationEvents.Introspection.cs
  40. 72
      src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs
  41. 23
      src/OpenIddict.Validation/OpenIddictValidationEvents.cs
  42. 1
      src/OpenIddict.Validation/OpenIddictValidationExtensions.cs
  43. 16
      src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs
  44. 51
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs
  45. 864
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
  46. 798
      src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
  47. 15
      src/OpenIddict.Validation/OpenIddictValidationService.cs
  48. 9
      test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs
  49. 167
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
  50. 41
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs
  51. 27
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs
  52. 29
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Userinfo.cs
  53. 177
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs
  54. 9
      test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs

1
samples/Mvc.Server/Startup.cs

@ -45,6 +45,7 @@ namespace Mvc.Server
options.ClaimsIdentity.UserNameClaimType = Claims.Name;
options.ClaimsIdentity.UserIdClaimType = Claims.Subject;
options.ClaimsIdentity.RoleClaimType = Claims.Role;
options.ClaimsIdentity.EmailClaimType = Claims.Email;
});
// OpenIddict offers native integration with Quartz.NET to perform scheduled tasks

9
src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs

@ -341,6 +341,15 @@ namespace OpenIddict.Abstractions
/// <returns><c>true</c> if the token has the specified type, <c>false</c> otherwise.</returns>
ValueTask<bool> HasTypeAsync(object token, string type, CancellationToken cancellationToken = default);
/// <summary>
/// Determines whether a given token has any of the specified types.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="types">The expected types.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns><c>true</c> if the token has any of the specified types, <c>false</c> otherwise.</returns>
ValueTask<bool> HasTypeAsync(object token, ImmutableArray<string> types, CancellationToken cancellationToken = default);
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>

62
src/OpenIddict.Abstractions/OpenIddictResources.resx

@ -134,7 +134,7 @@ To validate tokens received by custom API endpoints, the OpenIddict validation h
When implementing custom token deserialization, a 'oi_tkn_typ' claim containing the type of the token being processed must be added to the security principal.</value>
</data>
<data name="ID0005" xml:space="preserve">
<value>The type of token associated with the deserialized principal ({0}) doesn't match the expected token type ({1}).</value>
<value>The type of token associated with the deserialized principal ({0}) doesn't match one of the expected token types ({1}).</value>
</data>
<data name="ID0006" xml:space="preserve">
<value>A challenge response cannot be returned from this endpoint.</value>
@ -447,10 +447,10 @@ To use key rollover, register both the new certificate and the old one in the cr
<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>
</data>
<data name="ID0096" xml:space="preserve">
<value>No custom verification authentication handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ProcessAuthenticationContext&gt;' must be implemented to handle device and user codes (e.g by retrieving them from a database).</value>
<value>No custom token validation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ValidateTokenContext&gt;' must be implemented to handle device and user codes (e.g by retrieving them from a database).</value>
</data>
<data name="ID0097" xml:space="preserve">
<value>No custom verification sign-in handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;ProcessSignInContext&gt;' must be implemented to handle device and user codes and store them in a database, if applicable.</value>
<value>No custom token generation handler was found. When enabling the degraded mode, a custom 'IOpenIddictServerHandler&lt;GenerateTokenContext&gt;' must be implemented to handle device and user codes (e.g by storing them in a database).</value>
</data>
<data name="ID0098" xml:space="preserve">
<value>The event handler of type '{0}' couldn't be resolved.
@ -1476,6 +1476,9 @@ To register the OpenIddict core services, reference the 'OpenIddict.Core' packag
<data name="ID2117" xml:space="preserve">
<value>The specified authorization type is not supported by the default token manager.</value>
</data>
<data name="ID2118" xml:space="preserve">
<value>The token usage returned by the authorization server is not supported.</value>
</data>
<data name="ID4000" xml:space="preserve">
<value>The '{0}' parameter shouldn't be null or empty at this point.</value>
</data>
@ -1552,65 +1555,18 @@ To register the OpenIddict core services, reference the 'OpenIddict.Core' packag
<value>'{Claim}' was excluded from the identity token claims.</value>
</data>
<data name="ID6012" xml:space="preserve">
<value>The token entry for access token '{Identifier}' was successfully created.</value>
<value>The token entry for '{Type}' token '{Identifier}' was successfully created.</value>
</data>
<data name="ID6013" xml:space="preserve">
<value>A new access token was successfully created: {Payload}.
<value>A new '{Type}' token was successfully created: {Payload}.
The principal used to create the token contained the following claims: {Claims}.</value>
</data>
<data name="ID6014" xml:space="preserve">
<value>The token entry for access token '{Identifier}' was successfully converted to a reference token with the identifier '{ReferenceId}'.</value>
</data>
<data name="ID6015" xml:space="preserve">
<value>The token entry for authorization code '{Identifier}' was successfully created.</value>
</data>
<data name="ID6016" xml:space="preserve">
<value>A new authorization code was successfully created: {Payload}.
The principal used to create the token contained the following claims: {Claims}.</value>
</data>
<data name="ID6017" xml:space="preserve">
<value>The token entry for authorization code '{Identifier}' was successfully converted to a reference token with the identifier '{ReferenceId}'.</value>
</data>
<data name="ID6018" xml:space="preserve">
<value>The token entry for device code '{Identifier}' was successfully created.</value>
</data>
<data name="ID6019" xml:space="preserve">
<value>A new device code was successfully created: {Payload}.
The principal used to create the token contained the following claims: {Claims}.</value>
</data>
<data name="ID6020" xml:space="preserve">
<value>The token entry for device code '{Identifier}' was successfully converted to a reference token with the identifier '{ReferenceId}'.</value>
<value>The token entry for '{Type}' token '{Identifier}' was successfully converted to a reference token with the identifier '{ReferenceId}'.</value>
</data>
<data name="ID6021" xml:space="preserve">
<value>The reference token entry for device code '{Identifier}' was successfully updated'.</value>
</data>
<data name="ID6022" xml:space="preserve">
<value>The token entry for refresh token '{Identifier}' was successfully created.</value>
</data>
<data name="ID6023" xml:space="preserve">
<value>A new refresh token was successfully created: {Payload}.
The principal used to create the token contained the following claims: {Claims}.</value>
</data>
<data name="ID6024" xml:space="preserve">
<value>The token entry for refresh token '{Identifier}' was successfully converted to a reference token with the identifier '{ReferenceId}'.</value>
</data>
<data name="ID6025" xml:space="preserve">
<value>The token entry for user code '{Identifier}' was successfully created.</value>
</data>
<data name="ID6026" xml:space="preserve">
<value>A new user code was successfully created: {Payload}.
The principal used to create the token contained the following claims: {Claims}.</value>
</data>
<data name="ID6027" xml:space="preserve">
<value>The token entry for user code '{Identifier}' was successfully converted to a reference token with the identifier '{ReferenceId}'.</value>
</data>
<data name="ID6028" xml:space="preserve">
<value>The token entry for identity token '{Identifier}' was successfully created.</value>
</data>
<data name="ID6029" xml:space="preserve">
<value>A new identity token was successfully created: {Payload}.
The principal used to create the token contained the following claims: {Claims}.</value>
</data>
<data name="ID6030" xml:space="preserve">
<value>The authorization request was successfully extracted: {Request}.</value>
</data>

35
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -898,6 +898,37 @@ namespace OpenIddict.Core
return string.Equals(await Store.GetTypeAsync(token, cancellationToken), type, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Determines whether a given token has any of the specified types.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="types">The expected types.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns><c>true</c> if the token has any of the specified types, <c>false</c> otherwise.</returns>
public virtual async ValueTask<bool> HasTypeAsync(TToken token, ImmutableArray<string> types, CancellationToken cancellationToken = default)
{
if (token is null)
{
throw new ArgumentNullException(nameof(token));
}
var type = await Store.GetTypeAsync(token, cancellationToken);
if (string.IsNullOrEmpty(type))
{
return false;
}
for (var index = 0; index < types.Length; index++)
{
if (string.Equals(type, types[index], StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
/// <summary>
/// Executes the specified query and returns all the corresponding elements.
/// </summary>
@ -1430,6 +1461,10 @@ namespace OpenIddict.Core
ValueTask<bool> IOpenIddictTokenManager.HasTypeAsync(object token, string type, CancellationToken cancellationToken)
=> HasTypeAsync((TToken) token, type, cancellationToken);
/// <inheritdoc/>
ValueTask<bool> IOpenIddictTokenManager.HasTypeAsync(object token, ImmutableArray<string> types, CancellationToken cancellationToken)
=> HasTypeAsync((TToken) token, types, cancellationToken);
/// <inheritdoc/>
IAsyncEnumerable<object> IOpenIddictTokenManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken)
=> ListAsync(count, offset, cancellationToken);

6
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConstants.cs

@ -28,10 +28,16 @@ namespace OpenIddict.Server.AspNetCore
public static class Properties
{
public const string AccessTokenPrincipal = ".access_token_principal";
public const string AuthorizationCodePrincipal = ".authorization_code_principal";
public const string DeviceCodePrincipal = ".device_code_principal";
public const string Error = ".error";
public const string ErrorDescription = ".error_description";
public const string ErrorUri = ".error_uri";
public const string IdentityTokenPrincipal = ".identity_token_principal";
public const string RefreshTokenPrincipal = ".refresh_token_principal";
public const string Scope = ".scope";
public const string UserCodePrincipal = ".user_code_principal";
}
}
}

140
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandler.cs

@ -6,7 +6,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
@ -17,6 +16,7 @@ using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using Properties = OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreConstants.Properties;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.AspNetCore
@ -145,9 +145,9 @@ namespace OpenIddict.Server.AspNetCore
var properties = new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = context.Error,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = context.ErrorDescription,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorUri] = context.ErrorUri
[Properties.Error] = context.Error,
[Properties.ErrorDescription] = context.ErrorDescription,
[Properties.ErrorUri] = context.ErrorUri
});
return AuthenticateResult.Fail(SR.GetResourceString(SR.ID0113), properties);
@ -155,29 +155,131 @@ namespace OpenIddict.Server.AspNetCore
else
{
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
Debug.Assert(!string.IsNullOrEmpty(context.Principal.GetTokenType()), SR.GetResourceString(SR.ID4009));
Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010));
// A single main claims-based principal instance can be attached to an authentication ticket.
// To return the most appropriate one, the principal is selected based on the endpoint type.
// Independently of the selected main principal, all principals resolved from validated tokens
// are attached to the authentication properties bag so they can be accessed from user code.
var principal = context.EndpointType switch
{
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout
=> context.IdentityTokenPrincipal,
OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation
=> context.AccessTokenPrincipal ??
context.RefreshTokenPrincipal ??
context.IdentityTokenPrincipal ??
context.AuthorizationCodePrincipal ??
context.DeviceCodePrincipal ??
context.UserCodePrincipal,
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
=> context.AuthorizationCodePrincipal,
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType()
=> context.DeviceCodePrincipal,
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> context.RefreshTokenPrincipal,
OpenIddictServerEndpointType.Userinfo => context.AccessTokenPrincipal,
OpenIddictServerEndpointType.Verification => context.UserCodePrincipal,
_ => null
};
if (principal is null)
{
return AuthenticateResult.NoResult();
}
// Store the token to allow any OWIN/Katana component (e.g a controller)
// to retrieve it (e.g to make an API request to another application).
var properties = new AuthenticationProperties
{
ExpiresUtc = context.Principal.GetExpirationDate(),
IssuedUtc = context.Principal.GetCreationDate()
ExpiresUtc = principal.GetExpirationDate(),
IssuedUtc = principal.GetCreationDate()
};
properties.StoreTokens(new[]
List<AuthenticationToken>? tokens = null;
// Attach the tokens to allow any ASP.NET Core component (e.g a controller)
// to retrieve them (e.g to make an API request to another application).
if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken))
{
new AuthenticationToken
tokens ??= new(capacity: 1);
tokens.Add(new AuthenticationToken
{
Name = context.Principal.GetTokenType()!,
Value = context.Token
}
});
Name = TokenTypeHints.AccessToken,
Value = context.AccessToken
});
properties.SetParameter(Properties.AccessTokenPrincipal, context.AccessTokenPrincipal);
}
if (context.AuthorizationCodePrincipal is not null && !string.IsNullOrEmpty(context.AuthorizationCode))
{
tokens ??= new(capacity: 1);
tokens.Add(new AuthenticationToken
{
Name = TokenTypeHints.AuthorizationCode,
Value = context.AuthorizationCode
});
properties.SetParameter(Properties.AuthorizationCodePrincipal, context.AuthorizationCodePrincipal);
}
if (context.DeviceCodePrincipal is not null && !string.IsNullOrEmpty(context.DeviceCode))
{
tokens ??= new(capacity: 1);
tokens.Add(new AuthenticationToken
{
Name = TokenTypeHints.DeviceCode,
Value = context.DeviceCode
});
properties.SetParameter(Properties.DeviceCodePrincipal, context.DeviceCodePrincipal);
}
if (context.IdentityTokenPrincipal is not null && !string.IsNullOrEmpty(context.IdentityToken))
{
tokens ??= new(capacity: 1);
tokens.Add(new AuthenticationToken
{
Name = TokenTypeHints.IdToken,
Value = context.IdentityToken
});
properties.SetParameter(Properties.IdentityTokenPrincipal, context.IdentityTokenPrincipal);
}
if (context.RefreshTokenPrincipal is not null && !string.IsNullOrEmpty(context.RefreshToken))
{
tokens ??= new(capacity: 1);
tokens.Add(new AuthenticationToken
{
Name = TokenTypeHints.RefreshToken,
Value = context.RefreshToken
});
properties.SetParameter(Properties.RefreshTokenPrincipal, context.RefreshTokenPrincipal);
}
if (context.UserCodePrincipal is not null && !string.IsNullOrEmpty(context.UserCode))
{
tokens ??= new(capacity: 1);
tokens.Add(new AuthenticationToken
{
Name = TokenTypeHints.UserCode,
Value = context.UserCode
});
properties.SetParameter(Properties.UserCodePrincipal, context.UserCodePrincipal);
}
if (tokens is { Count: > 0 })
{
properties.StoreTokens(tokens);
}
return AuthenticateResult.Success(new AuthenticationTicket(
context.Principal, properties,
return AuthenticateResult.Success(new AuthenticationTicket(principal, properties,
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme));
}
}

16
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlerFilters.cs

@ -143,13 +143,13 @@ namespace OpenIddict.Server.AspNetCore
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the HTTPS requirement was disabled.
/// Represents a filter that excludes the associated handlers if status code pages support was not enabled.
/// </summary>
public class RequireTransportSecurityRequirementEnabled : IOpenIddictServerHandlerFilter<BaseContext>
public class RequireStatusCodePagesIntegrationEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictServerAspNetCoreOptions> _options;
public RequireTransportSecurityRequirementEnabled(IOptionsMonitor<OpenIddictServerAspNetCoreOptions> options)
public RequireStatusCodePagesIntegrationEnabled(IOptionsMonitor<OpenIddictServerAspNetCoreOptions> options)
=> _options = options;
public ValueTask<bool> IsActiveAsync(BaseContext context)
@ -159,18 +159,18 @@ namespace OpenIddict.Server.AspNetCore
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(!_options.CurrentValue.DisableTransportSecurityRequirement);
return new ValueTask<bool>(_options.CurrentValue.EnableStatusCodePagesIntegration);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if status code pages support was not enabled.
/// Represents a filter that excludes the associated handlers if the HTTPS requirement was disabled.
/// </summary>
public class RequireStatusCodePagesIntegrationEnabled : IOpenIddictServerHandlerFilter<BaseContext>
public class RequireTransportSecurityRequirementEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictServerAspNetCoreOptions> _options;
public RequireStatusCodePagesIntegrationEnabled(IOptionsMonitor<OpenIddictServerAspNetCoreOptions> options)
public RequireTransportSecurityRequirementEnabled(IOptionsMonitor<OpenIddictServerAspNetCoreOptions> options)
=> _options = options;
public ValueTask<bool> IsActiveAsync(BaseContext context)
@ -180,7 +180,7 @@ namespace OpenIddict.Server.AspNetCore
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(_options.CurrentValue.EnableStatusCodePagesIntegration);
return new ValueTask<bool>(!_options.CurrentValue.DisableTransportSecurityRequirement);
}
}

8
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs

@ -10,7 +10,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using OpenIddict.Server;
using OpenIddict.Server.DataProtection;
using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHandlerFilters;
using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHandlers;
namespace Microsoft.Extensions.DependencyInjection
@ -40,13 +39,6 @@ namespace Microsoft.Extensions.DependencyInjection
// Note: the order used here is not important, as the actual order is set in the options.
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor));
// Register the built-in filter used by the default OpenIddict Data Protection event handlers.
builder.Services.TryAddSingleton<RequireDataProtectionAccessTokenFormatEnabled>();
builder.Services.TryAddSingleton<RequireDataProtectionAuthorizationCodeFormatEnabled>();
builder.Services.TryAddSingleton<RequireDataProtectionDeviceCodeFormatEnabled>();
builder.Services.TryAddSingleton<RequireDataProtectionRefreshTokenFormatEnabled>();
builder.Services.TryAddSingleton<RequireDataProtectionUserCodeFormatEnabled>();
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(new[]
{

131
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlerFilters.cs

@ -1,131 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using static OpenIddict.Server.OpenIddictServerEvents;
namespace OpenIddict.Server.DataProtection
{
/// <summary>
/// Contains a collection of event handler filters commonly used by the Data Protection handlers.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static class OpenIddictServerDataProtectionHandlerFilters
{
/// <summary>
/// Represents a filter that excludes the associated handlers if OpenIddict
/// was not configured to issue ASP.NET Core Data Protection access tokens.
/// </summary>
public class RequireDataProtectionAccessTokenFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public RequireDataProtectionAccessTokenFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultAccessTokenFormat);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if OpenIddict
/// was not configured to issue ASP.NET Core Data Protection authorization codes.
/// </summary>
public class RequireDataProtectionAuthorizationCodeFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public RequireDataProtectionAuthorizationCodeFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultAuthorizationCodeFormat);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if OpenIddict
/// was not configured to issue ASP.NET Core Data Protection device codes.
/// </summary>
public class RequireDataProtectionDeviceCodeFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public RequireDataProtectionDeviceCodeFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultDeviceCodeFormat);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if OpenIddict
/// was not configured to issue ASP.NET Core Data Protection refresh tokens.
/// </summary>
public class RequireDataProtectionRefreshTokenFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public RequireDataProtectionRefreshTokenFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultRefreshTokenFormat);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if OpenIddict
/// was not configured to issue ASP.NET Core Data Protection user codes.
/// </summary>
public class RequireDataProtectionUserCodeFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public RequireDataProtectionUserCodeFormatEnabled(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
public ValueTask<bool> IsActiveAsync(BaseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultUserCodeFormat);
}
}
}
}

332
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Protection.cs

@ -0,0 +1,332 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
using Schemes = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes.Schemes;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.DataProtection
{
public static partial class OpenIddictServerDataProtectionHandlers
{
public static class Protection
{
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Token validation:
*/
ValidateDataProtectionToken.Descriptor,
/*
* Token validation:
*/
GenerateDataProtectionToken.Descriptor);
/// <summary>
/// Contains the logic responsible of validating tokens generated using Data Protection.
/// </summary>
public class ValidateDataProtectionToken : IOpenIddictServerHandler<ValidateTokenContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public ValidateDataProtectionToken(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidateDataProtectionToken>()
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
// If a principal was already attached, don't overwrite it.
if (context.Principal is not null)
{
return default;
}
// Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds
// to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads.
if (!context.Token.StartsWith("CfDJ8", StringComparison.Ordinal))
{
return default;
}
// Tokens generated using ASP.NET Core Data Protection are encrypted by symmetric keys
// that are derived from both a master key resolved from the key ring and a specific value
// known as "purpose" that helps ensure that Data Protection payloads can't be decrypted
// without the correct "purpose" value, which is different for all types of tokens.
//
// While offering extensive protection at the cryptographic level, this prevents decrypting
// unknown tokens without re-executing the entire decryption routine for each type of token
// considered valid. To speed up this process when supporting multiple types is required,
// the Data Protection integration relies on the "token_type_hint" parameter specified
// by the client when it is available (e.g with introspection or revocation requests).
var principal = context.ValidTokenTypes.Count switch
{
// If no valid token type was set, all supported token types are allowed.
//
// Note: if a "token_type_hint" was specified by the client, use it to optimize
// the token decryption lookup but fall back to other types of tokens
// if the token can't be decrypted using the specified token type hint.
//
// In this case, common types (e.g access/refresh tokens) are checked first.
0 => context.TokenTypeHint switch
{
TokenTypeHints.AuthorizationCode =>
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ??
ValidateToken(context.Token, TokenTypeHints.AccessToken) ??
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ??
ValidateToken(context.Token, TokenTypeHints.DeviceCode) ??
ValidateToken(context.Token, TokenTypeHints.UserCode),
TokenTypeHints.DeviceCode =>
ValidateToken(context.Token, TokenTypeHints.DeviceCode) ??
ValidateToken(context.Token, TokenTypeHints.AccessToken) ??
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ??
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ??
ValidateToken(context.Token, TokenTypeHints.UserCode),
TokenTypeHints.RefreshToken =>
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ??
ValidateToken(context.Token, TokenTypeHints.AccessToken) ??
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ??
ValidateToken(context.Token, TokenTypeHints.DeviceCode) ??
ValidateToken(context.Token, TokenTypeHints.UserCode),
TokenTypeHints.UserCode =>
ValidateToken(context.Token, TokenTypeHints.UserCode) ??
ValidateToken(context.Token, TokenTypeHints.AccessToken) ??
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ??
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ??
ValidateToken(context.Token, TokenTypeHints.DeviceCode),
_ =>
ValidateToken(context.Token, TokenTypeHints.AccessToken) ??
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ??
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ??
ValidateToken(context.Token, TokenTypeHints.DeviceCode) ??
ValidateToken(context.Token, TokenTypeHints.UserCode),
},
// If a single valid token type was set, ignore the specified token type hint.
1 => context.ValidTokenTypes.ElementAt(0) switch
{
TokenTypeHints.AccessToken => ValidateToken(context.Token, TokenTypeHints.AccessToken),
TokenTypeHints.RefreshToken => ValidateToken(context.Token, TokenTypeHints.RefreshToken),
TokenTypeHints.AuthorizationCode => ValidateToken(context.Token, TokenTypeHints.AuthorizationCode),
TokenTypeHints.DeviceCode => ValidateToken(context.Token, TokenTypeHints.DeviceCode),
TokenTypeHints.UserCode => ValidateToken(context.Token, TokenTypeHints.UserCode),
_ => null // The token type is not supported by the Data Protection integration (e.g identity tokens).
},
// If multiple valid types were set, use the specified token type hint
// and select the first non-null token that can be successfully decrypted.
_ => context.ValidTokenTypes.OrderBy(type => type switch
{
// If the token type hint corresponds to one of the valid types, test it first.
string value when value == context.TokenTypeHint => 0,
TokenTypeHints.AccessToken => 1,
TokenTypeHints.RefreshToken => 2,
TokenTypeHints.AuthorizationCode => 3,
TokenTypeHints.DeviceCode => 4,
TokenTypeHints.UserCode => 5,
_ => int.MaxValue
})
.Select(type => type switch
{
TokenTypeHints.AccessToken => ValidateToken(context.Token, TokenTypeHints.AccessToken),
TokenTypeHints.RefreshToken => ValidateToken(context.Token, TokenTypeHints.RefreshToken),
TokenTypeHints.AuthorizationCode => ValidateToken(context.Token, TokenTypeHints.AuthorizationCode),
TokenTypeHints.DeviceCode => ValidateToken(context.Token, TokenTypeHints.DeviceCode),
TokenTypeHints.UserCode => ValidateToken(context.Token, TokenTypeHints.UserCode),
_ => null // The token type is not supported by the Data Protection integration (e.g identity tokens).
})
.Where(static principal => principal is not null)
.FirstOrDefault()
};
if (principal is null)
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return default;
}
context.Principal = principal;
context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims);
return default;
ClaimsPrincipal? ValidateToken(string token, string type)
{
// Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch
{
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId)
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
TokenTypeHints.AuthorizationCode when !string.IsNullOrEmpty(context.TokenId)
=> new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server },
TokenTypeHints.DeviceCode when !string.IsNullOrEmpty(context.TokenId)
=> new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server },
TokenTypeHints.RefreshToken when !string.IsNullOrEmpty(context.TokenId)
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server },
TokenTypeHints.UserCode when !string.IsNullOrEmpty(context.TokenId)
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
try
{
using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(token)));
using var reader = new BinaryReader(buffer);
// Note: since the data format relies on a data protector using different "purposes" strings
// per token type, the token processed at this stage is guaranteed to be of the expected type.
return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type);
}
catch (Exception exception)
{
context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), token);
return null;
}
}
}
}
/// <summary>
/// Contains the logic responsible of generating a token using Data Protection.
/// </summary>
public class GenerateDataProtectionToken : IOpenIddictServerHandler<GenerateTokenContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public GenerateDataProtectionToken(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<GenerateTokenContext>()
.UseSingletonHandler<GenerateDataProtectionToken>()
.SetOrder(GenerateIdentityModelToken.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(GenerateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If an access token was already attached by another handler, don't overwrite it.
if (!string.IsNullOrEmpty(context.Token))
{
return default;
}
if (context.TokenType switch
{
TokenTypeHints.AccessToken => _options.CurrentValue.PreferDefaultAccessTokenFormat,
TokenTypeHints.AuthorizationCode => _options.CurrentValue.PreferDefaultAuthorizationCodeFormat,
TokenTypeHints.DeviceCode => _options.CurrentValue.PreferDefaultDeviceCodeFormat,
TokenTypeHints.RefreshToken => _options.CurrentValue.PreferDefaultRefreshTokenFormat,
TokenTypeHints.UserCode => _options.CurrentValue.PreferDefaultUserCodeFormat,
_ => true // The token type is not supported by the Data Protection integration (e.g identity tokens).
})
{
return default;
}
// Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(context.TokenType switch
{
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
TokenTypeHints.AccessToken when context.Options.UseReferenceAccessTokens
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
TokenTypeHints.AuthorizationCode when !context.Options.DisableTokenStorage
=> new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server },
TokenTypeHints.DeviceCode when !context.Options.DisableTokenStorage
=> new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server },
TokenTypeHints.RefreshToken when context.Options.UseReferenceRefreshTokens
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server },
TokenTypeHints.UserCode when !context.Options.DisableTokenStorage
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer);
_options.CurrentValue.Formatter.WriteToken(writer, context.Principal);
context.Token = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray()));
context.Logger.LogTrace(SR.GetResourceString(SR.ID6013), context.TokenType,
context.Token, context.Principal.Claims);
return default;
}
}
}
}
}

467
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs

@ -4,478 +4,15 @@
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.IO;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes;
using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHandlerFilters;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using static OpenIddict.Server.OpenIddictServerHandlers;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
using Schemes = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes.Schemes;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.DataProtection
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static partial class OpenIddictServerDataProtectionHandlers
{
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Authentication processing:
*/
ValidateDataProtectionToken.Descriptor,
/*
* Sign-in processing:
*/
GenerateDataProtectionAccessToken.Descriptor,
GenerateDataProtectionAuthorizationCode.Descriptor,
GenerateDataProtectionDeviceCode.Descriptor,
GenerateDataProtectionRefreshToken.Descriptor,
GenerateDataProtectionUserCode.Descriptor);
/// <summary>
/// Contains the logic responsible of validating tokens generated using Data Protection.
/// </summary>
public class ValidateDataProtectionToken : IOpenIddictServerHandler<ProcessAuthenticationContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public ValidateDataProtectionToken(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<ValidateDataProtectionToken>()
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If a principal was already attached, don't overwrite it.
if (context.Principal is not null)
{
return default;
}
// Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds
// to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads.
if (string.IsNullOrEmpty(context.Token) || !context.Token.StartsWith("CfDJ8", StringComparison.Ordinal))
{
return default;
}
var principal = !string.IsNullOrEmpty(context.TokenType) ?
ValidateToken(context.Token, context.TokenType) :
ValidateToken(context.Token, TokenTypeHints.AccessToken) ??
ValidateToken(context.Token, TokenTypeHints.RefreshToken) ??
ValidateToken(context.Token, TokenTypeHints.AuthorizationCode) ??
ValidateToken(context.Token, TokenTypeHints.DeviceCode) ??
ValidateToken(context.Token, TokenTypeHints.UserCode);
if (principal is null)
{
context.Reject(
error: context.EndpointType switch
{
OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
_ => Errors.InvalidToken
},
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return default;
}
context.Principal = principal;
context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims);
return default;
ClaimsPrincipal? ValidateToken(string token, string type)
{
// Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch
{
TokenTypeHints.AccessToken when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AuthorizationCode when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.DeviceCode when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.RefreshToken when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.UserCode when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
TokenTypeHints.AuthorizationCode => new[] { Handlers.Server, Formats.AuthorizationCode, Schemes.Server },
TokenTypeHints.DeviceCode => new[] { Handlers.Server, Formats.DeviceCode, Schemes.Server },
TokenTypeHints.RefreshToken => new[] { Handlers.Server, Formats.RefreshToken, Schemes.Server },
TokenTypeHints.UserCode => new[] { Handlers.Server, Formats.UserCode, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
try
{
using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(token)));
using var reader = new BinaryReader(buffer);
// Note: since the data format relies on a data protector using different "purposes" strings
// per token type, the token processed at this stage is guaranteed to be of the expected type.
return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type);
}
catch (Exception exception)
{
context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), token);
return null;
}
}
}
}
/// <summary>
/// Contains the logic responsible of generating an access token using Data Protection.
/// </summary>
public class GenerateDataProtectionAccessToken : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public GenerateDataProtectionAccessToken(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireAccessTokenGenerated>()
.AddFilter<RequireDataProtectionAccessTokenFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionAccessToken>()
.SetOrder(GenerateIdentityModelAccessToken.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If an access token was already attached by another handler, don't overwrite it.
if (!string.IsNullOrEmpty(context.Response.AccessToken))
{
return default;
}
if (context.AccessTokenPrincipal is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0022));
}
// Create a Data Protection protector using the provider registered in the options.
var protector = context.Options.UseReferenceAccessTokens ?
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.AccessToken, Schemes.Server);
using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer);
_options.CurrentValue.Formatter.WriteToken(writer, context.AccessTokenPrincipal);
context.AccessToken = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray()));
context.Logger.LogTrace(SR.GetResourceString(SR.ID6013),
context.AccessTokenPrincipal.GetClaim(Claims.JwtId),
context.AccessToken, context.AccessTokenPrincipal.Claims);
return default;
}
}
/// <summary>
/// Contains the logic responsible of generating an authorization code using Data Protection.
/// </summary>
public class GenerateDataProtectionAuthorizationCode : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public GenerateDataProtectionAuthorizationCode(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireAuthorizationCodeGenerated>()
.AddFilter<RequireDataProtectionAuthorizationCodeFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionAuthorizationCode>()
.SetOrder(GenerateIdentityModelAuthorizationCode.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If an authorization code was already attached by another handler, don't overwrite it.
if (!string.IsNullOrEmpty(context.Response.Code))
{
return default;
}
if (context.AuthorizationCodePrincipal is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0022));
}
// Create a Data Protection protector using the provider registered in the options.
var protector = !context.Options.DisableTokenStorage ?
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.AuthorizationCode, Schemes.Server);
using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer);
_options.CurrentValue.Formatter.WriteToken(writer, context.AuthorizationCodePrincipal);
context.AuthorizationCode = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray()));
context.Logger.LogTrace(SR.GetResourceString(SR.ID6016),
context.AuthorizationCodePrincipal.GetClaim(Claims.JwtId),
context.AuthorizationCode, context.AuthorizationCodePrincipal.Claims);
return default;
}
}
/// <summary>
/// Contains the logic responsible of generating a device code using Data Protection.
/// </summary>
public class GenerateDataProtectionDeviceCode : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public GenerateDataProtectionDeviceCode(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireDeviceCodeGenerated>()
.AddFilter<RequireDataProtectionDeviceCodeFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionDeviceCode>()
.SetOrder(GenerateIdentityModelDeviceCode.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If a device code was already attached by another handler, don't overwrite it.
if (!string.IsNullOrEmpty(context.Response.DeviceCode))
{
return default;
}
if (context.DeviceCodePrincipal is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0022));
}
// Create a Data Protection protector using the provider registered in the options.
var protector = !context.Options.DisableTokenStorage ?
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.DeviceCode, Schemes.Server);
using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer);
_options.CurrentValue.Formatter.WriteToken(writer, context.DeviceCodePrincipal);
context.DeviceCode = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray()));
context.Logger.LogTrace(SR.GetResourceString(SR.ID6019),
context.DeviceCodePrincipal.GetClaim(Claims.JwtId),
context.DeviceCode, context.DeviceCodePrincipal.Claims);
return default;
}
}
/// <summary>
/// Contains the logic responsible of generating a refresh token using Data Protection.
/// </summary>
public class GenerateDataProtectionRefreshToken : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public GenerateDataProtectionRefreshToken(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireRefreshTokenGenerated>()
.AddFilter<RequireDataProtectionRefreshTokenFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionRefreshToken>()
.SetOrder(GenerateIdentityModelRefreshToken.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If a refresh token was already attached by another handler, don't overwrite it.
if (!string.IsNullOrEmpty(context.Response.RefreshToken))
{
return default;
}
if (context.RefreshTokenPrincipal is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0022));
}
// Create a Data Protection protector using the provider registered in the options.
var protector = context.Options.UseReferenceRefreshTokens ?
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.RefreshToken, Schemes.Server);
using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer);
_options.CurrentValue.Formatter.WriteToken(writer, context.RefreshTokenPrincipal);
context.RefreshToken = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray()));
context.Logger.LogTrace(SR.GetResourceString(SR.ID6023),
context.RefreshTokenPrincipal.GetClaim(Claims.JwtId),
context.RefreshToken, context.RefreshTokenPrincipal.Claims);
return default;
}
}
/// <summary>
/// Contains the logic responsible of generating a user code using Data Protection.
/// </summary>
public class GenerateDataProtectionUserCode : IOpenIddictServerHandler<ProcessSignInContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public GenerateDataProtectionUserCode(IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireUserCodeGenerated>()
.AddFilter<RequireDataProtectionUserCodeFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionUserCode>()
.SetOrder(GenerateIdentityModelUserCode.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignInContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If a user code was already attached by another handler, don't overwrite it.
if (!string.IsNullOrEmpty(context.Response.UserCode))
{
return default;
}
if (context.UserCodePrincipal is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0022));
}
// Create a Data Protection protector using the provider registered in the options.
var protector = !context.Options.DisableTokenStorage ?
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.UserCode, Schemes.Server);
using var buffer = new MemoryStream();
using var writer = new BinaryWriter(buffer);
_options.CurrentValue.Formatter.WriteToken(writer, context.UserCodePrincipal);
context.UserCode = Base64UrlEncoder.Encode(protector.Protect(buffer.ToArray()));
context.Logger.LogTrace(SR.GetResourceString(SR.ID6026),
context.UserCodePrincipal.GetClaim(Claims.JwtId),
context.UserCode, context.UserCodePrincipal.Claims);
return default;
}
}
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; }
= ImmutableArray.CreateRange(Protection.DefaultHandlers);
}
}

6
src/OpenIddict.Server.Owin/OpenIddictServerOwinConstants.cs

@ -39,10 +39,16 @@ namespace OpenIddict.Server.Owin
public static class Properties
{
public const string AccessTokenPrincipal = ".access_token_principal";
public const string AuthorizationCodePrincipal = ".authorization_code_principal";
public const string DeviceCodePrincipal = ".device_code_principal";
public const string Error = ".error";
public const string ErrorDescription = ".error_description";
public const string ErrorUri = ".error_uri";
public const string IdentityTokenPrincipal = ".identity_token_principal";
public const string RefreshTokenPrincipal = ".refresh_token_principal";
public const string Scope = ".scope";
public const string UserCodePrincipal = ".user_code_principal";
}
}
}

100
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandler.cs

@ -6,7 +6,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin;
@ -15,6 +14,7 @@ using Microsoft.Owin.Security.Infrastructure;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using Properties = OpenIddict.Server.Owin.OpenIddictServerOwinConstants.Properties;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.Owin
@ -155,11 +155,11 @@ namespace OpenIddict.Server.Owin
return null;
}
var properties = new AuthenticationProperties(new Dictionary<string, string?>
var properties = new OpenIddictServerOwinProperties(new Dictionary<string, string?>
{
[OpenIddictServerOwinConstants.Properties.Error] = context.Error,
[OpenIddictServerOwinConstants.Properties.ErrorDescription] = context.ErrorDescription,
[OpenIddictServerOwinConstants.Properties.ErrorUri] = context.ErrorUri
[Properties.Error] = context.Error,
[Properties.ErrorDescription] = context.ErrorDescription,
[Properties.ErrorUri] = context.ErrorUri
});
return new AuthenticationTicket(null, properties);
@ -167,22 +167,88 @@ namespace OpenIddict.Server.Owin
else
{
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
Debug.Assert(!string.IsNullOrEmpty(context.Principal.GetTokenType()), SR.GetResourceString(SR.ID4009));
Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010));
// A single main claims-based principal instance can be attached to an authentication ticket.
// To return the most appropriate one, the principal is selected based on the endpoint type.
// Independently of the selected main principal, all principals resolved from validated tokens
// are attached to the authentication properties bag so they can be accessed from user code.
var principal = context.EndpointType switch
{
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout
=> context.IdentityTokenPrincipal,
OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation
=> context.AccessTokenPrincipal ??
context.RefreshTokenPrincipal ??
context.IdentityTokenPrincipal ??
context.AuthorizationCodePrincipal ??
context.DeviceCodePrincipal ??
context.UserCodePrincipal,
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
=> context.AuthorizationCodePrincipal,
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType()
=> context.DeviceCodePrincipal,
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> context.RefreshTokenPrincipal,
OpenIddictServerEndpointType.Userinfo => context.AccessTokenPrincipal,
// Store the token to allow any OWIN/Katana component (e.g a controller)
// to retrieve it (e.g to make an API request to another application).
var properties = new AuthenticationProperties(new Dictionary<string, string?>
OpenIddictServerEndpointType.Verification => context.UserCodePrincipal,
_ => null
};
if (principal is null)
{
[context.Principal.GetTokenType()!] = context.Token
})
return null;
}
var properties = new OpenIddictServerOwinProperties
{
ExpiresUtc = context.Principal.GetExpirationDate(),
IssuedUtc = context.Principal.GetCreationDate()
ExpiresUtc = principal.GetExpirationDate(),
IssuedUtc = principal.GetCreationDate()
};
return new AuthenticationTicket((ClaimsIdentity) context.Principal.Identity, properties);
// Attach the tokens to allow any ASP.NET Core component (e.g a controller)
// to retrieve them (e.g to make an API request to another application).
if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken))
{
properties.Dictionary[TokenTypeHints.AccessToken] = context.AccessToken;
properties.SetParameter(Properties.AccessTokenPrincipal, context.AccessTokenPrincipal);
}
if (context.AuthorizationCodePrincipal is not null && !string.IsNullOrEmpty(context.AuthorizationCode))
{
properties.Dictionary[TokenTypeHints.AuthorizationCode] = context.AuthorizationCode;
properties.SetParameter(Properties.AuthorizationCodePrincipal, context.AuthorizationCodePrincipal);
}
if (context.DeviceCodePrincipal is not null && !string.IsNullOrEmpty(context.DeviceCode))
{
properties.Dictionary[TokenTypeHints.DeviceCode] = context.DeviceCode;
properties.SetParameter(Properties.DeviceCodePrincipal, context.DeviceCodePrincipal);
}
if (context.IdentityTokenPrincipal is not null && !string.IsNullOrEmpty(context.IdentityToken))
{
properties.Dictionary[TokenTypeHints.IdToken] = context.IdentityToken;
properties.SetParameter(Properties.IdentityTokenPrincipal, context.IdentityTokenPrincipal);
}
if (context.RefreshTokenPrincipal is not null && !string.IsNullOrEmpty(context.RefreshToken))
{
properties.Dictionary[TokenTypeHints.RefreshToken] = context.RefreshToken;
properties.SetParameter(Properties.RefreshTokenPrincipal, context.RefreshTokenPrincipal);
}
if (context.UserCodePrincipal is not null && !string.IsNullOrEmpty(context.UserCode))
{
properties.Dictionary[TokenTypeHints.UserCode] = context.UserCode;
properties.SetParameter(Properties.UserCodePrincipal, context.UserCodePrincipal);
}
return new AuthenticationTicket((ClaimsIdentity) principal.Identity, properties);
}
}
@ -205,7 +271,7 @@ namespace OpenIddict.Server.Owin
// corresponds to a challenge response, as LookupChallenge() will always return a non-null
// value when active authentication is used, even if no challenge was actually triggered.
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge is not null && (Response.StatusCode == 401 || Response.StatusCode == 403))
if (challenge is not null && Response.StatusCode is 401 or 403)
{
var transaction = Context.Get<OpenIddictServerTransaction>(typeof(OpenIddictServerTransaction).FullName) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0112));

89
src/OpenIddict.Server.Owin/OpenIddictServerOwinProperties.cs

@ -0,0 +1,89 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Generic;
using Microsoft.Owin.Security;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.Owin
{
/// <inheritdoc/>
public class OpenIddictServerOwinProperties : AuthenticationProperties
{
/// <inheritdoc/>
public OpenIddictServerOwinProperties()
: this(items: null)
{
}
/// <inheritdoc/>
public OpenIddictServerOwinProperties(IDictionary<string, string?>? items)
: this(items, parameters: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OpenIddictServerOwinProperties"/> class.
/// </summary>
/// <param name="items">State values dictionary to use.</param>
/// <param name="parameters">Parameters dictionary to use.</param>
public OpenIddictServerOwinProperties(
IDictionary<string, string?>? items,
IDictionary<string, object?>? parameters)
: base(items)
=> Parameters = parameters ?? new Dictionary<string, object?>(StringComparer.Ordinal);
/// <summary>
/// Gets the collection of parameters passed to the authentication handler.
/// </summary>
/// <remarks>
/// Note: these properties are not intended for serialization or persistence,
/// only for flowing data between call sites.
/// </remarks>
public IDictionary<string, object?> Parameters { get; }
/// <summary>
/// Gets a parameter from the <see cref="Parameters"/> collection.
/// </summary>
/// <typeparam name="T">The parameter type.</typeparam>
/// <param name="name">The parameter name.</param>
/// <returns>The parameter value or a default value if the property is not set.</returns>
public T? GetParameter<T>(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.ID0190, nameof(name));
}
return Parameters.TryGetValue(name, out var parameter) && parameter is T value ? value : default;
}
/// <summary>
/// Sets a parameter value in the <see cref="Parameters"/> collection.
/// </summary>
/// <typeparam name="T">The parameter type.</typeparam>
/// <param name="name">The parameter key.</param>
/// <param name="value">The value to set.</param>
public void SetParameter<T>(string name, T? value)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.ID0190, nameof(name));
}
if (value is null)
{
Parameters.Remove(name);
}
else
{
Parameters[name] = value;
}
}
}
}

9
src/OpenIddict.Server/OpenIddictServerConfiguration.cs

@ -227,13 +227,13 @@ namespace OpenIddict.Server
throw new InvalidOperationException(SR.GetResourceString(SR.ID0095));
}
// If the degraded mode was enabled, ensure custom authentication/sign-in handlers
// If the degraded mode was enabled, ensure custom validation/generation handlers
// have been registered to deal with device/user codes validation and generation.
if (options.GrantTypes.Contains(GrantTypes.DeviceCode))
{
if (!options.Handlers.Any(
descriptor => descriptor.ContextType == typeof(ProcessAuthenticationContext) &&
descriptor => descriptor.ContextType == typeof(ValidateTokenContext) &&
descriptor.Type == OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
@ -241,7 +241,7 @@ namespace OpenIddict.Server
}
if (!options.Handlers.Any(
descriptor => descriptor.ContextType == typeof(ProcessSignInContext) &&
descriptor => descriptor.ContextType == typeof(GenerateTokenContext) &&
descriptor.Type == OpenIddictServerHandlerType.Custom &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
@ -258,8 +258,7 @@ namespace OpenIddict.Server
options.SigningCredentials.Sort((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)
foreach (var key in options.EncryptionCredentials.Select(credentials => credentials.Key)
.Concat(options.SigningCredentials.Select(credentials => credentials.Key))
.Where(key => string.IsNullOrEmpty(key.KeyId)))
{

16
src/OpenIddict.Server/OpenIddictServerConstants.cs

@ -1,16 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
namespace OpenIddict.Server
{
public static class OpenIddictServerConstants
{
public static class Properties
{
public const string ReferenceTokenIdentifier = ".reference_token_identifier";
}
}
}

129
src/OpenIddict.Server/OpenIddictServerEvents.Protection.cs

@ -0,0 +1,129 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Generic;
using System.Security.Claims;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
namespace OpenIddict.Server
{
public static partial class OpenIddictServerEvents
{
/// <summary>
/// Represents an event called when generating a token.
/// </summary>
public class GenerateTokenContext : BaseValidatingContext
{
/// <summary>
/// Creates a new instance of the <see cref="GenerateTokenContext"/> class.
/// </summary>
public GenerateTokenContext(OpenIddictServerTransaction transaction)
: base(transaction)
{
}
/// <summary>
/// Gets or sets the request.
/// </summary>
public OpenIddictRequest Request
{
get => Transaction.Request!;
set => Transaction.Request = value;
}
/// <summary>
/// Gets or sets the security principal used to create the token.
/// </summary>
public ClaimsPrincipal Principal { get; set; } = default!;
/// <summary>
/// Gets or sets the encryption credentials used to encrypt the token.
/// </summary>
public EncryptingCredentials? EncryptionCredentials { get; set; }
/// <summary>
/// Gets or sets the signing credentials used to sign the token.
/// </summary>
public SigningCredentials? SigningCredentials { get; set; }
/// <summary>
/// Gets or sets the security token handler used to serialize the security principal.
/// </summary>
public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!;
/// <summary>
/// Gets or sets the token returned to the client application.
/// </summary>
public string? Token { get; set; }
/// <summary>
/// Gets or sets the type of the token to create.
/// </summary>
public string TokenType { get; set; } = default!;
}
/// <summary>
/// Represents an event called when validating a token.
/// </summary>
public class ValidateTokenContext : BaseValidatingContext
{
/// <summary>
/// Creates a new instance of the <see cref="ValidateTokenContext"/> class.
/// </summary>
public ValidateTokenContext(OpenIddictServerTransaction transaction)
: base(transaction)
{
}
/// <summary>
/// Gets or sets the request.
/// </summary>
public OpenIddictRequest Request
{
get => Transaction.Request!;
set => Transaction.Request = value;
}
/// <summary>
/// Gets or sets the security token handler used to validate the token.
/// </summary>
public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!;
/// <summary>
/// Gets or sets the validation parameters used to verify the authenticity of tokens.
/// </summary>
public TokenValidationParameters TokenValidationParameters { get; set; } = default!;
/// <summary>
/// Gets or sets the token to validate.
/// </summary>
public string Token { get; set; } = default!;
/// <summary>
/// Gets or sets the token type hint specified by the client, if applicable.
/// </summary>
public string? TokenTypeHint { get; set; } = default!;
/// <summary>
/// Gets or sets the token entry identifier associated with the token, if applicable.
/// </summary>
public string? TokenId { get; set; }
/// <summary>
/// Gets or sets the security principal resolved from the token.
/// </summary>
public ClaimsPrincipal? Principal { get; set; }
/// <summary>
/// Gets the token types that are considered valid.
/// </summary>
public HashSet<string> ValidTokenTypes { get; } = new(StringComparer.OrdinalIgnoreCase);
}
}
}

184
src/OpenIddict.Server/OpenIddictServerEvents.cs

@ -272,19 +272,191 @@ namespace OpenIddict.Server
}
/// <summary>
/// Gets or sets the security principal.
/// Gets or sets a boolean indicating whether an access token
/// must be resolved for the authentication to considered valid.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public ClaimsPrincipal? Principal { get; set; }
public bool RequireAccessToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether an authorization code
/// must be resolved for the authentication to considered valid.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool RequireAuthorizationCode { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a device code
/// must be resolved for the authentication to considered valid.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool RequireDeviceCode { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a generic token
/// must be resolved for the authentication to considered valid.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool RequireGenericToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether an identity token
/// must be resolved for the authentication to considered valid.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool RequireIdentityToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a refresh token
/// must be resolved for the authentication to considered valid.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool RequireRefreshToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a user code
/// must be resolved for the authentication to considered valid.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool RequireUserCode { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether an access token
/// should be extracted from the current context and validated.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool ValidateAccessToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether an authorization code
/// should be extracted from the current context and validated.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool ValidateAuthorizationCode { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a device code
/// should be extracted from the current context and validated.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool ValidateDeviceCode { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a generic token
/// should be extracted from the current context and validated.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool ValidateGenericToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether an identity token
/// should be extracted from the current context and validated.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool ValidateIdentityToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a refresh token
/// should be extracted from the current context and validated.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool ValidateRefreshToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether a user code
/// should be extracted from the current context and validated.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool ValidateUserCode { get; set; }
/// <summary>
/// Gets or sets the access token to validate, if applicable.
/// </summary>
public string? AccessToken { get; set; }
/// <summary>
/// Gets or sets the principal extracted from the access token, if applicable.
/// </summary>
public ClaimsPrincipal? AccessTokenPrincipal { get; set; }
/// <summary>
/// Gets or sets the authorization code to validate, if applicable.
/// </summary>
public string? AuthorizationCode { get; set; }
/// <summary>
/// Gets or sets the principal extracted from the authorization code, if applicable.
/// </summary>
public ClaimsPrincipal? AuthorizationCodePrincipal { get; set; }
/// <summary>
/// Gets or sets the device code to validate, if applicable.
/// </summary>
public string? DeviceCode { get; set; }
/// <summary>
/// Gets or sets the principal extracted from the device code, if applicable.
/// </summary>
public ClaimsPrincipal? DeviceCodePrincipal { get; set; }
/// <summary>
/// Gets or sets the generic token to validate, if applicable.
/// </summary>
public string? GenericToken { get; set; }
/// <summary>
/// Gets or sets the token to validate.
/// Gets or sets the optional hint indicating the type of the generic token, if applicable.
/// </summary>
public string? Token { get; set; }
public string? GenericTokenTypeHint { get; set; }
/// <summary>
/// Gets or sets the expected type of the token.
/// Gets or sets the principal extracted from the generic token, if applicable.
/// </summary>
public string? TokenType { get; set; }
public ClaimsPrincipal? GenericTokenPrincipal { get; set; }
/// <summary>
/// Gets or sets the identity token to validate, if applicable.
/// </summary>
public string? IdentityToken { get; set; }
/// <summary>
/// Gets or sets the principal extracted from the identity token, if applicable.
/// </summary>
public ClaimsPrincipal? IdentityTokenPrincipal { get; set; }
/// <summary>
/// Gets or sets the refresh token to validate, if applicable.
/// </summary>
public string? RefreshToken { get; set; }
/// <summary>
/// Gets or sets the principal extracted from the refresh token, if applicable.
/// </summary>
public ClaimsPrincipal? RefreshTokenPrincipal { get; set; }
/// <summary>
/// Gets or sets the user code to validate, if applicable.
/// </summary>
public string? UserCode { get; set; }
/// <summary>
/// Gets or sets the principal extracted from the user code, if applicable.
/// </summary>
public ClaimsPrincipal? UserCodePrincipal { get; set; }
}
/// <summary>

7
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -45,7 +45,9 @@ namespace Microsoft.Extensions.DependencyInjection
// Register the built-in filters used by the default OpenIddict server event handlers.
builder.Services.TryAddSingleton<RequireAccessTokenGenerated>();
builder.Services.TryAddSingleton<RequireAccessTokenValidated>();
builder.Services.TryAddSingleton<RequireAuthorizationCodeGenerated>();
builder.Services.TryAddSingleton<RequireAuthorizationCodeValidated>();
builder.Services.TryAddSingleton<RequireAuthorizationStorageEnabled>();
builder.Services.TryAddSingleton<RequireAuthorizationRequest>();
builder.Services.TryAddSingleton<RequireClientIdParameter>();
@ -53,16 +55,20 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.TryAddSingleton<RequireCryptographyRequest>();
builder.Services.TryAddSingleton<RequireDegradedModeDisabled>();
builder.Services.TryAddSingleton<RequireDeviceCodeGenerated>();
builder.Services.TryAddSingleton<RequireDeviceCodeValidated>();
builder.Services.TryAddSingleton<RequireDeviceRequest>();
builder.Services.TryAddSingleton<RequireEndpointPermissionsEnabled>();
builder.Services.TryAddSingleton<RequireGenericTokenValidated>();
builder.Services.TryAddSingleton<RequireGrantTypePermissionsEnabled>();
builder.Services.TryAddSingleton<RequireIdentityTokenGenerated>();
builder.Services.TryAddSingleton<RequireIdentityTokenValidated>();
builder.Services.TryAddSingleton<RequireIntrospectionRequest>();
builder.Services.TryAddSingleton<RequireLogoutRequest>();
builder.Services.TryAddSingleton<RequirePostLogoutRedirectUriParameter>();
builder.Services.TryAddSingleton<RequireReferenceAccessTokensEnabled>();
builder.Services.TryAddSingleton<RequireReferenceRefreshTokensEnabled>();
builder.Services.TryAddSingleton<RequireRefreshTokenGenerated>();
builder.Services.TryAddSingleton<RequireRefreshTokenValidated>();
builder.Services.TryAddSingleton<RequireResponseTypePermissionsEnabled>();
builder.Services.TryAddSingleton<RequireRevocationRequest>();
builder.Services.TryAddSingleton<RequireSlidingRefreshTokenExpirationEnabled>();
@ -71,6 +77,7 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.TryAddSingleton<RequireTokenStorageEnabled>();
builder.Services.TryAddSingleton<RequireTokenRequest>();
builder.Services.TryAddSingleton<RequireUserCodeGenerated>();
builder.Services.TryAddSingleton<RequireUserCodeValidated>();
builder.Services.TryAddSingleton<RequireUserinfoRequest>();
builder.Services.TryAddSingleton<RequireVerificationRequest>();

112
src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs

@ -30,6 +30,22 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no access token is validated.
/// </summary>
public class RequireAccessTokenValidated : IOpenIddictServerHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(context.ValidateAccessToken);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no authorization code is generated.
/// </summary>
@ -46,6 +62,22 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no authorization code is validated.
/// </summary>
public class RequireAuthorizationCodeValidated : IOpenIddictServerHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(context.ValidateAuthorizationCode);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the request is not an authorization request.
/// </summary>
@ -158,6 +190,22 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no device code is validated.
/// </summary>
public class RequireDeviceCodeValidated : IOpenIddictServerHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(context.ValidateDeviceCode);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the request is not a device request.
/// </summary>
@ -190,6 +238,22 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no generic token is validated.
/// </summary>
public class RequireGenericTokenValidated : IOpenIddictServerHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(context.ValidateGenericToken);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if grant type permissions were disabled.
/// </summary>
@ -222,6 +286,22 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no identity token is validated.
/// </summary>
public class RequireIdentityTokenValidated : IOpenIddictServerHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(context.ValidateIdentityToken);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the request is not an introspection request.
/// </summary>
@ -318,6 +398,22 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no refresh token is validated.
/// </summary>
public class RequireRefreshTokenValidated : IOpenIddictServerHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(context.ValidateRefreshToken);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if response type permissions were disabled.
/// </summary>
@ -446,6 +542,22 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if no user code is validated.
/// </summary>
public class RequireUserCodeValidated : IOpenIddictServerHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(context.ValidateUserCode);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the request is not a userinfo request.
/// </summary>

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

@ -1190,7 +1190,7 @@ namespace OpenIddict.Server
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.Principal;
context.Principal = notification.UserCodePrincipal;
}
}
}

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

@ -1318,7 +1318,9 @@ namespace OpenIddict.Server
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.Principal;
context.Principal = context.Request.IsAuthorizationCodeGrantType() ? notification.AuthorizationCodePrincipal :
context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal :
context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null;
}
}

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

@ -700,6 +700,10 @@ namespace OpenIddict.Server
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();
@ -722,7 +726,7 @@ namespace OpenIddict.Server
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.Principal;
context.Principal = notification.GenericTokenPrincipal;
}
}

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

File diff suppressed because it is too large

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

@ -643,6 +643,10 @@ namespace OpenIddict.Server
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();
@ -665,7 +669,7 @@ namespace OpenIddict.Server
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.Principal;
context.Principal = notification.GenericTokenPrincipal;
}
}

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

@ -380,6 +380,10 @@ namespace OpenIddict.Server
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();
@ -402,7 +406,7 @@ namespace OpenIddict.Server
}
// Attach the security principal extracted from the token to the validation context.
context.Principal = notification.Principal;
context.Principal = notification.AccessTokenPrincipal;
}
}

2783
src/OpenIddict.Server/OpenIddictServerHandlers.cs

File diff suppressed because it is too large

1
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConstants.cs

@ -19,6 +19,7 @@ namespace OpenIddict.Validation.AspNetCore
public static class Properties
{
public const string AccessTokenPrincipal = ".access_token_principal";
public const string Error = ".error";
public const string ErrorDescription = ".error_description";
public const string ErrorUri = ".error_uri";

61
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandler.cs

@ -6,8 +6,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
@ -17,6 +15,7 @@ using Microsoft.Extensions.Options;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.OpenIddictValidationEvents;
using Properties = OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreConstants.Properties;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Validation.AspNetCore
@ -143,9 +142,9 @@ namespace OpenIddict.Validation.AspNetCore
var properties = new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictValidationAspNetCoreConstants.Properties.Error] = context.Error,
[OpenIddictValidationAspNetCoreConstants.Properties.ErrorDescription] = context.ErrorDescription,
[OpenIddictValidationAspNetCoreConstants.Properties.ErrorUri] = context.ErrorUri
[Properties.Error] = context.Error,
[Properties.ErrorDescription] = context.ErrorDescription,
[Properties.ErrorUri] = context.ErrorUri
});
return AuthenticateResult.Fail(SR.GetResourceString(SR.ID0113), properties);
@ -153,29 +152,51 @@ namespace OpenIddict.Validation.AspNetCore
else
{
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
Debug.Assert(!string.IsNullOrEmpty(context.Principal.GetTokenType()), SR.GetResourceString(SR.ID4009));
Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010));
// A single main claims-based principal instance can be attached to an authentication ticket.
// To return the most appropriate one, the principal is selected based on the endpoint type.
// Independently of the selected main principal, all principals resolved from validated tokens
// are attached to the authentication properties bag so they can be accessed from user code.
var principal = context.EndpointType switch
{
OpenIddictValidationEndpointType.Unknown => context.AccessTokenPrincipal,
_ => null
};
if (principal is null)
{
return AuthenticateResult.NoResult();
}
// Store the token to allow any ASP.NET Core component (e.g a controller)
// to retrieve it (e.g to make an API request to another application).
var properties = new AuthenticationProperties
{
ExpiresUtc = context.Principal.GetExpirationDate(),
IssuedUtc = context.Principal.GetCreationDate()
ExpiresUtc = principal.GetExpirationDate(),
IssuedUtc = principal.GetCreationDate()
};
properties.StoreTokens(new[]
List<AuthenticationToken>? tokens = null;
// Attach the tokens to allow any ASP.NET Core component (e.g a controller)
// to retrieve them (e.g to make an API request to another application).
if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken))
{
new AuthenticationToken
tokens ??= new(capacity: 1);
tokens.Add(new AuthenticationToken
{
Name = context.Principal.GetTokenType()!,
Value = context.Token
}
});
Name = TokenTypeHints.AccessToken,
Value = context.AccessToken
});
properties.SetParameter(Properties.AccessTokenPrincipal, context.AccessTokenPrincipal);
}
if (tokens is { Count: > 0 })
{
properties.StoreTokens(tokens);
}
return AuthenticateResult.Success(new AuthenticationTicket(
context.Principal, properties,
return AuthenticateResult.Success(new AuthenticationTicket(principal, properties,
OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme));
}
}

22
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs

@ -24,6 +24,8 @@ using Microsoft.Net.Http.Headers;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlerFilters;
using static OpenIddict.Validation.OpenIddictValidationEvents;
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters;
using static OpenIddict.Validation.OpenIddictValidationHandlers;
using Properties = OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreConstants.Properties;
using SR = OpenIddict.Abstractions.OpenIddictResources;
@ -144,8 +146,9 @@ namespace OpenIddict.Validation.AspNetCore
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireHttpRequest>()
.AddFilter<RequireAccessTokenValidated>()
.UseSingletonHandler<ExtractAccessTokenFromAuthorizationHeader>()
.SetOrder(int.MinValue + 50_000)
.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@ -158,7 +161,7 @@ namespace OpenIddict.Validation.AspNetCore
}
// If a token was already resolved, don't overwrite it.
if (!string.IsNullOrEmpty(context.Token))
if (!string.IsNullOrEmpty(context.AccessToken))
{
return default;
}
@ -176,8 +179,7 @@ namespace OpenIddict.Validation.AspNetCore
string header = request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
context.Token = header.Substring("Bearer ".Length);
context.TokenType = TokenTypeHints.AccessToken;
context.AccessToken = header.Substring("Bearer ".Length);
return default;
}
@ -198,6 +200,7 @@ namespace OpenIddict.Validation.AspNetCore
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireHttpRequest>()
.AddFilter<RequireAccessTokenValidated>()
.UseSingletonHandler<ExtractAccessTokenFromBodyForm>()
.SetOrder(ExtractAccessTokenFromAuthorizationHeader.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
@ -212,7 +215,7 @@ namespace OpenIddict.Validation.AspNetCore
}
// If a token was already resolved, don't overwrite it.
if (!string.IsNullOrEmpty(context.Token))
if (!string.IsNullOrEmpty(context.AccessToken))
{
return;
}
@ -236,8 +239,7 @@ namespace OpenIddict.Validation.AspNetCore
var form = await request.ReadFormAsync(request.HttpContext.RequestAborted);
if (form.TryGetValue(Parameters.AccessToken, out StringValues token))
{
context.Token = token;
context.TokenType = TokenTypeHints.AccessToken;
context.AccessToken = token;
return;
}
@ -256,6 +258,7 @@ namespace OpenIddict.Validation.AspNetCore
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireHttpRequest>()
.AddFilter<RequireAccessTokenValidated>()
.UseSingletonHandler<ExtractAccessTokenFromQueryString>()
.SetOrder(ExtractAccessTokenFromBodyForm.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
@ -270,7 +273,7 @@ namespace OpenIddict.Validation.AspNetCore
}
// If a token was already resolved, don't overwrite it.
if (!string.IsNullOrEmpty(context.Token))
if (!string.IsNullOrEmpty(context.AccessToken))
{
return default;
}
@ -287,8 +290,7 @@ namespace OpenIddict.Validation.AspNetCore
// See https://tools.ietf.org/html/rfc6750#section-2.3 for more information.
if (request.Query.TryGetValue(Parameters.AccessToken, out StringValues token))
{
context.Token = token;
context.TokenType = TokenTypeHints.AccessToken;
context.AccessToken = token;
return default;
}

130
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.Protection.cs

@ -0,0 +1,130 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.IO;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Purposes;
using static OpenIddict.Validation.OpenIddictValidationEvents;
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters;
using static OpenIddict.Validation.OpenIddictValidationHandlers.Protection;
using Schemes = OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Purposes.Schemes;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Validation.DataProtection
{
public static partial class OpenIddictValidationDataProtectionHandlers
{
public static class Protection
{
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Token validation:
*/
ValidateDataProtectionToken.Descriptor);
/// <summary>
/// Contains the logic responsible of validating tokens generated using Data Protection.
/// </summary>
public class ValidateDataProtectionToken : IOpenIddictValidationHandler<ValidateTokenContext>
{
private readonly IOptionsMonitor<OpenIddictValidationDataProtectionOptions> _options;
public ValidateDataProtectionToken(IOptionsMonitor<OpenIddictValidationDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>()
.UseSingletonHandler<ValidateDataProtectionToken>()
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
// If a principal was already attached, don't overwrite it.
if (context.Principal is not null)
{
return default;
}
// Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds
// to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads.
if (!context.Token.StartsWith("CfDJ8", StringComparison.Ordinal))
{
return default;
}
// Note: unlike the equivalent handler in the server stack, the logic used here is
// simpler as only access tokens are currently supported by the validation stack.
var principal = context.ValidTokenTypes.Count is 0 || context.ValidTokenTypes.Contains(TokenTypeHints.AccessToken) ?
ValidateToken(context.Token, TokenTypeHints.AccessToken) :
null;
if (principal is null)
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return default;
}
context.Principal = principal;
context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims);
return default;
ClaimsPrincipal? ValidateToken(string token, string type)
{
// Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch
{
// Note: reference tokens are encrypted using a different "purpose" string than non-reference tokens.
TokenTypeHints.AccessToken when !string.IsNullOrEmpty(context.TokenId)
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
try
{
using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(token)));
using var reader = new BinaryReader(buffer);
// Note: since the data format relies on a data protector using different "purposes" strings
// per token type, the token processed at this stage is guaranteed to be of the expected type.
return _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(type);
}
catch (Exception exception)
{
context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), token);
return null;
}
}
}
}
}
}
}

107
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs

@ -4,118 +4,15 @@
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Purposes;
using static OpenIddict.Validation.OpenIddictValidationEvents;
using static OpenIddict.Validation.OpenIddictValidationHandlers;
using Properties = OpenIddict.Validation.OpenIddictValidationConstants.Properties;
using Schemes = OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Purposes.Schemes;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Validation.DataProtection
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static partial class OpenIddictValidationDataProtectionHandlers
{
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Authentication processing:
*/
ValidateDataProtectionToken.Descriptor);
/// <summary>
/// Contains the logic responsible of validating tokens generated using Data Protection.
/// </summary>
public class ValidateDataProtectionToken : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
private readonly IOptionsMonitor<OpenIddictValidationDataProtectionOptions> _options;
public ValidateDataProtectionToken(IOptionsMonitor<OpenIddictValidationDataProtectionOptions> options)
=> _options = options;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<ValidateDataProtectionToken>()
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If a principal was already attached, don't overwrite it.
if (context.Principal is not null)
{
return default;
}
// Note: ASP.NET Core Data Protection tokens always start with "CfDJ8", that corresponds
// to the base64 representation of the magic "09 F0 C9 F0" header identifying DP payloads.
if (string.IsNullOrEmpty(context.Token) || !context.Token.StartsWith("CfDJ8", StringComparison.Ordinal))
{
return default;
}
// Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(context.TokenType switch
{
null => throw new InvalidOperationException(SR.GetResourceString(SR.ID0167)),
TokenTypeHints.AccessToken when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
try
{
using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(context.Token)));
using var reader = new BinaryReader(buffer);
// Note: since the data format relies on a data protector using different "purposes" strings
// per token type, the token processed at this stage is guaranteed to be of the expected type.
context.Principal = _options.CurrentValue.Formatter.ReadToken(reader)?.SetTokenType(context.TokenType);
}
catch (Exception exception)
{
context.Logger.LogTrace(exception, SR.GetResourceString(SR.ID6153), context.Token);
}
if (context.Principal is null)
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return default;
}
context.Logger.LogTrace(SR.GetResourceString(SR.ID6152), context.Token, context.Principal.Claims);
return default;
}
}
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; }
= ImmutableArray.CreateRange(Protection.DefaultHandlers);
}
}

1
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinConstants.cs

@ -30,6 +30,7 @@ namespace OpenIddict.Validation.Owin
public static class Properties
{
public const string AccessTokenPrincipal = ".access_token_principal";
public const string Error = ".error";
public const string ErrorDescription = ".error_description";
public const string ErrorUri = ".error_uri";

50
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandler.cs

@ -6,7 +6,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin;
@ -15,6 +14,7 @@ using Microsoft.Owin.Security.Infrastructure;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.OpenIddictValidationEvents;
using Properties = OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants.Properties;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Validation.Owin
@ -152,11 +152,11 @@ namespace OpenIddict.Validation.Owin
return null;
}
var properties = new AuthenticationProperties(new Dictionary<string, string?>
var properties = new OpenIddictValidationOwinProperties(new Dictionary<string, string?>
{
[OpenIddictValidationOwinConstants.Properties.Error] = context.Error,
[OpenIddictValidationOwinConstants.Properties.ErrorDescription] = context.ErrorDescription,
[OpenIddictValidationOwinConstants.Properties.ErrorUri] = context.ErrorUri
[Properties.Error] = context.Error,
[Properties.ErrorDescription] = context.ErrorDescription,
[Properties.ErrorUri] = context.ErrorUri
});
return new AuthenticationTicket(null, properties);
@ -164,22 +164,38 @@ namespace OpenIddict.Validation.Owin
else
{
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
Debug.Assert(!string.IsNullOrEmpty(context.Principal.GetTokenType()), SR.GetResourceString(SR.ID4009));
Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010));
// A single main claims-based principal instance can be attached to an authentication ticket.
// To return the most appropriate one, the principal is selected based on the endpoint type.
// Independently of the selected main principal, all principals resolved from validated tokens
// are attached to the authentication properties bag so they can be accessed from user code.
var principal = context.EndpointType switch
{
OpenIddictValidationEndpointType.Unknown => context.AccessTokenPrincipal,
_ => null
};
// Store the token to allow any OWIN/Katana component (e.g a controller)
// to retrieve it (e.g to make an API request to another application).
var properties = new AuthenticationProperties(new Dictionary<string, string?>
if (principal is null)
{
[context.Principal.GetTokenType()!] = context.Token
})
return null;
}
var properties = new OpenIddictValidationOwinProperties
{
ExpiresUtc = context.Principal.GetExpirationDate(),
IssuedUtc = context.Principal.GetCreationDate()
ExpiresUtc = principal.GetExpirationDate(),
IssuedUtc = principal.GetCreationDate()
};
return new AuthenticationTicket((ClaimsIdentity) context.Principal.Identity, properties);
// Attach the tokens to allow any OWIN/Katana component (e.g a controller)
// to retrieve them (e.g to make an API request to another application).
if (context.AccessTokenPrincipal is not null && !string.IsNullOrEmpty(context.AccessToken))
{
properties.Dictionary[TokenTypeHints.AccessToken] = context.AccessToken;
properties.SetParameter(Properties.AccessTokenPrincipal, context.AccessTokenPrincipal);
}
return new AuthenticationTicket((ClaimsIdentity) principal.Identity, properties);
}
}
@ -202,7 +218,7 @@ namespace OpenIddict.Validation.Owin
// corresponds to a challenge response, as LookupChallenge() will always return a non-null
// value when active authentication is used, even if no challenge was actually triggered.
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
if (challenge is not null && (Response.StatusCode == 401 || Response.StatusCode == 403))
if (challenge is not null && Response.StatusCode is 401 or 403)
{
var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0166));

22
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs

@ -20,6 +20,8 @@ using Microsoft.Owin.Security;
using Owin;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.OpenIddictValidationEvents;
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters;
using static OpenIddict.Validation.OpenIddictValidationHandlers;
using static OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants;
using static OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlerFilters;
using Properties = OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants.Properties;
@ -142,8 +144,9 @@ namespace OpenIddict.Validation.Owin
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireOwinRequest>()
.AddFilter<RequireAccessTokenValidated>()
.UseSingletonHandler<ExtractAccessTokenFromAuthorizationHeader>()
.SetOrder(int.MinValue + 50_000)
.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@ -156,7 +159,7 @@ namespace OpenIddict.Validation.Owin
}
// If a token was already resolved, don't overwrite it.
if (!string.IsNullOrEmpty(context.Token))
if (!string.IsNullOrEmpty(context.AccessToken))
{
return default;
}
@ -174,8 +177,7 @@ namespace OpenIddict.Validation.Owin
string header = request.Headers[Headers.Authorization];
if (!string.IsNullOrEmpty(header) && header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
context.Token = header.Substring("Bearer ".Length);
context.TokenType = TokenTypeHints.AccessToken;
context.AccessToken = header.Substring("Bearer ".Length);
return default;
}
@ -196,6 +198,7 @@ namespace OpenIddict.Validation.Owin
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireOwinRequest>()
.AddFilter<RequireAccessTokenValidated>()
.UseSingletonHandler<ExtractAccessTokenFromBodyForm>()
.SetOrder(ExtractAccessTokenFromAuthorizationHeader.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
@ -210,7 +213,7 @@ namespace OpenIddict.Validation.Owin
}
// If a token was already resolved, don't overwrite it.
if (!string.IsNullOrEmpty(context.Token))
if (!string.IsNullOrEmpty(context.AccessToken))
{
return;
}
@ -235,8 +238,7 @@ namespace OpenIddict.Validation.Owin
string token = form[Parameters.AccessToken];
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
context.TokenType = TokenTypeHints.AccessToken;
context.AccessToken = token;
return;
}
@ -255,6 +257,7 @@ namespace OpenIddict.Validation.Owin
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireOwinRequest>()
.AddFilter<RequireAccessTokenValidated>()
.UseSingletonHandler<ExtractAccessTokenFromQueryString>()
.SetOrder(ExtractAccessTokenFromBodyForm.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
@ -269,7 +272,7 @@ namespace OpenIddict.Validation.Owin
}
// If a token was already resolved, don't overwrite it.
if (!string.IsNullOrEmpty(context.Token))
if (!string.IsNullOrEmpty(context.AccessToken))
{
return default;
}
@ -287,8 +290,7 @@ namespace OpenIddict.Validation.Owin
string token = request.Query[Parameters.AccessToken];
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
context.TokenType = TokenTypeHints.AccessToken;
context.AccessToken = token;
return default;
}

89
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinProperties.cs

@ -0,0 +1,89 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Generic;
using Microsoft.Owin.Security;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Validation.Owin
{
/// <inheritdoc/>
public class OpenIddictValidationOwinProperties : AuthenticationProperties
{
/// <inheritdoc/>
public OpenIddictValidationOwinProperties()
: this(items: null)
{
}
/// <inheritdoc/>
public OpenIddictValidationOwinProperties(IDictionary<string, string?>? items)
: this(items, parameters: null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="OpenIddictValidationOwinProperties"/> class.
/// </summary>
/// <param name="items">State values dictionary to use.</param>
/// <param name="parameters">Parameters dictionary to use.</param>
public OpenIddictValidationOwinProperties(
IDictionary<string, string?>? items,
IDictionary<string, object?>? parameters)
: base(items)
=> Parameters = parameters ?? new Dictionary<string, object?>(StringComparer.Ordinal);
/// <summary>
/// Gets the collection of parameters passed to the authentication handler.
/// </summary>
/// <remarks>
/// Note: these properties are not intended for serialization or persistence,
/// only for flowing data between call sites.
/// </remarks>
public IDictionary<string, object?> Parameters { get; }
/// <summary>
/// Gets a parameter from the <see cref="Parameters"/> collection.
/// </summary>
/// <typeparam name="T">The parameter type.</typeparam>
/// <param name="name">The parameter name.</param>
/// <returns>The parameter value or a default value if the property is not set.</returns>
public T? GetParameter<T>(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.ID0190, nameof(name));
}
return Parameters.TryGetValue(name, out var parameter) && parameter is T value ? value : default;
}
/// <summary>
/// Sets a parameter value in the <see cref="Parameters"/> collection.
/// </summary>
/// <typeparam name="T">The parameter type.</typeparam>
/// <param name="name">The parameter key.</param>
/// <param name="value">The value to set.</param>
public void SetParameter<T>(string name, T? value)
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException(SR.ID0190, nameof(name));
}
if (value is null)
{
Parameters.Remove(name);
}
else
{
Parameters[name] = value;
}
}
}
}

16
src/OpenIddict.Validation/OpenIddictValidationConstants.cs

@ -1,16 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
namespace OpenIddict.Validation
{
public static class OpenIddictValidationConstants
{
public static class Properties
{
public const string ReferenceTokenIdentifier = ".reference_token_identifier";
}
}
}

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

@ -94,7 +94,7 @@ namespace OpenIddict.Validation
}
/// <summary>
/// Represents an event called for each validated configuration response.
/// Represents an event called for each configuration response.
/// </summary>
public class HandleConfigurationResponseContext : BaseExternalContext
{
@ -212,7 +212,7 @@ namespace OpenIddict.Validation
}
/// <summary>
/// Represents an event called for each validated cryptography response.
/// Represents an event called for each cryptography response.
/// </summary>
public class HandleCryptographyResponseContext : BaseExternalContext
{

9
src/OpenIddict.Validation/OpenIddictValidationEvents.Introspection.cs

@ -42,7 +42,7 @@ namespace OpenIddict.Validation
/// <summary>
/// Gets or sets the token type sent to the introspection endpoint.
/// </summary>
public string? TokenType { get; set; }
public string? TokenTypeHint { get; set; }
}
/// <summary>
@ -103,7 +103,7 @@ namespace OpenIddict.Validation
}
/// <summary>
/// Represents an event called for each validated introspection response.
/// Represents an event called for each introspection response.
/// </summary>
public class HandleIntrospectionResponseContext : BaseExternalContext
{
@ -138,11 +138,6 @@ namespace OpenIddict.Validation
/// </summary>
public string? Token { get; set; }
/// <summary>
/// Gets or sets the token type sent to the introspection endpoint.
/// </summary>
public string? TokenType { get; set; }
/// <summary>
/// Gets or sets the principal containing the claims resolved from the introspection response.
/// </summary>

72
src/OpenIddict.Validation/OpenIddictValidationEvents.Protection.cs

@ -0,0 +1,72 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Generic;
using System.Security.Claims;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
namespace OpenIddict.Validation
{
public static partial class OpenIddictValidationEvents
{
/// <summary>
/// Represents an event called when validating a token.
/// </summary>
public class ValidateTokenContext : BaseValidatingContext
{
/// <summary>
/// Creates a new instance of the <see cref="ValidateTokenContext"/> class.
/// </summary>
public ValidateTokenContext(OpenIddictValidationTransaction transaction)
: base(transaction)
{
}
/// <summary>
/// Gets or sets the request, or <c>null</c> if it is not available.
/// </summary>
public OpenIddictRequest? Request
{
get => Transaction.Request;
set => Transaction.Request = value;
}
/// <summary>
/// Gets or sets the security token handler used to validate the token.
/// </summary>
public JsonWebTokenHandler SecurityTokenHandler { get; set; } = default!;
/// <summary>
/// Gets or sets the validation parameters used to verify the authenticity of tokens.
/// </summary>
public TokenValidationParameters TokenValidationParameters { get; set; } = default!;
/// <summary>
/// Gets or sets the token to validate.
/// </summary>
public string Token { get; set; } = default!;
/// <summary>
/// Gets or sets the token entry identifier associated with the token, if applicable.
/// </summary>
public string? TokenId { get; set; }
/// <summary>
/// Gets or sets the security principal resolved from the token.
/// </summary>
public ClaimsPrincipal? Principal { get; set; }
/// <summary>
/// Gets the token types that are considered valid. If no value is
/// explicitly specified, all supported tokens are considered valid.
/// </summary>
public HashSet<string> ValidTokenTypes { get; } = new(StringComparer.OrdinalIgnoreCase);
}
}
}

23
src/OpenIddict.Validation/OpenIddictValidationEvents.cs

@ -238,19 +238,30 @@ namespace OpenIddict.Validation
}
/// <summary>
/// Gets or sets the security principal.
/// Gets or sets the principal extracted from the access token, if applicable.
/// </summary>
public ClaimsPrincipal? Principal { get; set; }
public ClaimsPrincipal? AccessTokenPrincipal { get; set; }
/// <summary>
/// Gets or sets the token to validate.
/// Gets or sets the access token to validate, if applicable.
/// </summary>
public string? Token { get; set; }
public string? AccessToken { get; set; }
/// <summary>
/// Gets or sets the expected type of the token.
/// Gets or sets a boolean indicating whether an access token
/// must be resolved for the authentication to considered valid.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public string? TokenType { get; set; }
public bool RequireAccessToken { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether an access token
/// should be extracted from the current context and validated.
/// Note: overriding the value of this property is generally not
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool ValidateAccessToken { get; set; }
}
/// <summary>

1
src/OpenIddict.Validation/OpenIddictValidationExtensions.cs

@ -45,6 +45,7 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor));
// Register the built-in filters used by the default OpenIddict validation event handlers.
builder.Services.TryAddSingleton<RequireAccessTokenValidated>();
builder.Services.TryAddSingleton<RequireAuthorizationEntryValidationEnabled>();
builder.Services.TryAddSingleton<RequireLocalValidation>();
builder.Services.TryAddSingleton<RequireTokenEntryValidationEnabled>();

16
src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs

@ -14,6 +14,22 @@ namespace OpenIddict.Validation
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static class OpenIddictValidationHandlerFilters
{
/// <summary>
/// Represents a filter that excludes the associated handlers if no access token is validated.
/// </summary>
public class RequireAccessTokenValidated : IOpenIddictValidationHandlerFilter<ProcessAuthenticationContext>
{
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(context.ValidateAccessToken);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if authorization validation was not enabled.
/// </summary>

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

@ -36,7 +36,7 @@ namespace OpenIddict.Validation
HandleInactiveResponse.Descriptor,
ValidateWellKnownClaims.Descriptor,
ValidateIssuer.Descriptor,
ValidateTokenType.Descriptor,
ValidateTokenUsage.Descriptor,
PopulateClaims.Descriptor);
/// <summary>
@ -93,7 +93,7 @@ namespace OpenIddict.Validation
}
context.Request.Token = context.Token;
context.Request.TokenTypeHint = context.TokenType;
context.Request.TokenTypeHint = context.TokenTypeHint;
return default;
}
@ -283,16 +283,16 @@ namespace OpenIddict.Validation
}
/// <summary>
/// Contains the logic responsible of extracting and validating the token type from the introspection response.
/// Contains the logic responsible of extracting and validating the token usage from the introspection response.
/// </summary>
public class ValidateTokenType : IOpenIddictValidationHandler<HandleIntrospectionResponseContext>
public class ValidateTokenUsage : IOpenIddictValidationHandler<HandleIntrospectionResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<HandleIntrospectionResponseContext>()
.UseSingletonHandler<ValidateTokenType>()
.UseSingletonHandler<ValidateTokenUsage>()
.SetOrder(ValidateIssuer.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@ -308,19 +308,33 @@ namespace OpenIddict.Validation
// OpenIddict-based authorization servers always return the actual token type using
// the special "token_usage" claim, that helps resource servers determine whether the
// introspected token is of the expected type and prevent token substitution attacks.
if (!string.IsNullOrEmpty(context.TokenType))
// In this handler, the "token_usage" is verified to ensure it corresponds to a supported
// value so that the component that triggered the introspection request can determine
// whether the returned token has an acceptable type depending on the context.
var usage = (string?) context.Response[Claims.TokenUsage];
if (string.IsNullOrEmpty(usage))
{
var usage = (string?) context.Response[Claims.TokenUsage];
if (!string.IsNullOrEmpty(usage) &&
!string.Equals(usage, context.TokenType, StringComparison.OrdinalIgnoreCase))
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2110),
uri: SR.FormatID8000(SR.ID2110));
return default;
}
return default;
}
if (!(usage switch
{
// Note: by default, OpenIddict only allows access/refresh tokens to be
// introspected but additional types can be added using the events model.
TokenTypeHints.AccessToken or TokenTypeHints.AuthorizationCode or
TokenTypeHints.IdToken or TokenTypeHints.RefreshToken or
TokenTypeHints.UserCode
=> true,
_ => false // Other token usages are not supported.
}))
{
context.Reject(
error: Errors.ServerError,
description: SR.GetResourceString(SR.ID2118),
uri: SR.FormatID8000(SR.ID2118));
return default;
}
return default;
@ -338,7 +352,7 @@ namespace OpenIddict.Validation
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<HandleIntrospectionResponseContext>()
.UseSingletonHandler<PopulateClaims>()
.SetOrder(ValidateTokenType.Descriptor.Order + 1_000)
.SetOrder(ValidateTokenUsage.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@ -378,8 +392,7 @@ namespace OpenIddict.Validation
}
// Ignore all protocol claims that shouldn't be mapped to CLR claims.
if (parameter.Key is Claims.Active or Claims.Issuer or Claims.NotBefore or
Claims.TokenType or Claims.TokenUsage)
if (parameter.Key is Claims.Active or Claims.Issuer or Claims.NotBefore or Claims.TokenType)
{
continue;
}

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

@ -0,0 +1,864 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.OpenIddictValidationEvents;
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Validation
{
public static partial class OpenIddictValidationHandlers
{
public static class Protection
{
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Token validation:
*/
ResolveTokenValidationParameters.Descriptor,
ValidateReferenceTokenIdentifier.Descriptor,
ValidateIdentityModelToken.Descriptor,
IntrospectToken.Descriptor,
NormalizeScopeClaims.Descriptor,
MapInternalClaims.Descriptor,
RestoreReferenceTokenProperties.Descriptor,
ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor,
ValidateAudience.Descriptor,
ValidateTokenEntry.Descriptor,
ValidateAuthorizationEntry.Descriptor);
/// <summary>
/// Contains the logic responsible of resolving the validation parameters used to validate tokens.
/// </summary>
public class ResolveTokenValidationParameters : IOpenIddictValidationHandler<ValidateTokenContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>()
.UseSingletonHandler<ResolveTokenValidationParameters>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
// Clone the token validation parameters and set the issuer using the value found in the
// OpenID Connect server configuration (that can be static or retrieved using discovery).
var parameters = context.Options.TokenValidationParameters.Clone();
parameters.ValidIssuer ??= configuration.Issuer ?? context.Issuer?.AbsoluteUri;
parameters.ValidateIssuer = !string.IsNullOrEmpty(parameters.ValidIssuer);
// Combine the signing keys registered statically in the token validation parameters
// with the signing keys resolved from the OpenID Connect server configuration.
parameters.IssuerSigningKeys =
parameters.IssuerSigningKeys?.Concat(configuration.SigningKeys) ?? configuration.SigningKeys;
parameters.ValidTypes = context.ValidTokenTypes.Count switch
{
// If no specific token type is expected, accept all token types at this stage.
// Additional filtering can be made based on the resolved/actual token type.
0 => null,
// Otherwise, map the token types to their JWT public or internal representation.
_ => context.ValidTokenTypes.SelectMany(type => type switch
{
// For access tokens, both "at+jwt" and "application/at+jwt" are valid.
TokenTypeHints.AccessToken => new[]
{
JsonWebTokenTypes.AccessToken,
JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken
},
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
})
};
context.SecurityTokenHandler = context.Options.JsonWebTokenHandler;
context.TokenValidationParameters = parameters;
}
}
/// <summary>
/// Contains the logic responsible of validating reference token identifiers.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateReferenceTokenIdentifier : IOpenIddictValidationHandler<ValidateTokenContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public ValidateReferenceTokenIdentifier() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139));
public ValidateReferenceTokenIdentifier(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>()
.AddFilter<RequireTokenEntryValidationEnabled>()
.UseScopedHandler<ValidateReferenceTokenIdentifier>()
.SetOrder(ResolveTokenValidationParameters.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reference tokens are base64url-encoded payloads of exactly 256 bits (generated using a
// crypto-secure RNG). If the token length differs, the token cannot be a reference token.
if (context.Token.Length != 43)
{
return;
}
// If the reference token cannot be found, don't return an error to allow another handler to validate it.
var token = await _tokenManager.FindByReferenceIdAsync(context.Token);
if (token is null)
{
return;
}
// If the type associated with the token entry doesn't match one of the expected types, return an error.
if (context.ValidTokenTypes.Count > 0 &&
!await _tokenManager.HasTypeAsync(token, context.ValidTokenTypes.ToImmutableArray()))
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return;
}
var payload = await _tokenManager.GetPayloadAsync(token);
if (string.IsNullOrEmpty(payload))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0026));
}
// Replace the token parameter by the payload resolved from the token entry
// and store the identifier of the reference token so it can be later
// used to restore the properties associated with the token.
context.Token = payload;
context.TokenId = await _tokenManager.GetIdAsync(token);
}
}
/// <summary>
/// Contains the logic responsible of validating tokens generated using IdentityModel.
/// </summary>
public class ValidateIdentityModelToken : IOpenIddictValidationHandler<ValidateTokenContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>()
.UseSingletonHandler<ValidateIdentityModelToken>()
.SetOrder(ValidateReferenceTokenIdentifier.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If a principal was already attached, don't overwrite it.
if (context.Principal is not null)
{
return default;
}
// If the token cannot be read, don't return an error to allow another handler to validate it.
if (!context.SecurityTokenHandler.CanReadToken(context.Token))
{
return default;
}
var result = context.SecurityTokenHandler.ValidateToken(context.Token, context.TokenValidationParameters);
if (!result.IsValid)
{
// If validation failed because of an unrecognized key identifier, inform the configuration manager
// that the configuration MAY have be refreshed by sending a new discovery request to the server.
if (result.Exception is SecurityTokenSignatureKeyNotFoundException)
{
context.Options.ConfigurationManager.RequestRefresh();
}
context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token);
context.Reject(
error: Errors.InvalidToken,
description: result.Exception switch
{
SecurityTokenInvalidIssuerException => SR.GetResourceString(SR.ID2088),
SecurityTokenInvalidTypeException => SR.GetResourceString(SR.ID2089),
SecurityTokenSignatureKeyNotFoundException => SR.GetResourceString(SR.ID2090),
SecurityTokenInvalidSignatureException => SR.GetResourceString(SR.ID2091),
_ => SR.GetResourceString(SR.ID2004)
},
uri: result.Exception switch
{
SecurityTokenInvalidIssuerException => SR.FormatID8000(SR.ID2088),
SecurityTokenInvalidTypeException => SR.FormatID8000(SR.ID2089),
SecurityTokenSignatureKeyNotFoundException => SR.FormatID8000(SR.ID2090),
SecurityTokenInvalidSignatureException => SR.FormatID8000(SR.ID2091),
_ => SR.FormatID8000(SR.ID2004)
});
return default;
}
// Attach the principal extracted from the token to the parent event context and store
// the token type (resolved from "typ" or "token_usage") as a special private claim.
context.Principal = new ClaimsPrincipal(result.ClaimsIdentity).SetTokenType(result.TokenType switch
{
null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)),
// Both at+jwt and application/at+jwt are supported for access tokens.
JsonWebTokenTypes.AccessToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken
=> TokenTypeHints.AccessToken,
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
context.Logger.LogTrace(SR.GetResourceString(SR.ID6001), context.Token, context.Principal.Claims);
return default;
}
}
/// <summary>
/// Contains the logic responsible of validating the tokens using OAuth 2.0 introspection.
/// </summary>
public class IntrospectToken : IOpenIddictValidationHandler<ValidateTokenContext>
{
private readonly OpenIddictValidationService _service;
public IntrospectToken(OpenIddictValidationService service)
=> _service = service;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireIntrospectionValidation>()
.UseSingletonHandler<IntrospectToken>()
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If a principal was already attached, don't overwrite it.
if (context.Principal is not null)
{
return;
}
Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010));
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
if (string.IsNullOrEmpty(configuration.IntrospectionEndpoint) ||
!Uri.TryCreate(configuration.IntrospectionEndpoint, UriKind.Absolute, out Uri? address) ||
!address.IsWellFormedOriginalString())
{
context.Reject(
error: Errors.ServerError,
description: SR.GetResourceString(SR.ID2092),
uri: SR.FormatID8000(SR.ID2092));
return;
}
ClaimsPrincipal principal;
try
{
principal = await _service.IntrospectTokenAsync(address, context.Token, context.ValidTokenTypes.Count switch
{
// Infer the token type hint sent to the authorization server to help speed up
// the token resolution lookup. If multiple types are accepted, no hint is sent.
1 => context.ValidTokenTypes.ElementAt(0),
_ => null
}) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0141));
}
catch (Exception exception)
{
context.Logger.LogDebug(exception, SR.GetResourceString(SR.ID6155));
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return;
}
// OpenIddict-based authorization servers always return the actual token type using
// the special "token_usage" claim, that helps resource servers determine whether the
// introspected token is one of the expected types and prevents token substitution attacks.
//
// If a "token_usage" claim can be extracted from the principal, use it to determine
// whether the token details returned by the authorization server correspond to a
// token whose type is considered acceptable based on the valid types collection.
//
// If the valid types collection is empty, all types of tokens are considered valid.
var usage = principal.GetClaim(Claims.TokenUsage);
if (!string.IsNullOrEmpty(usage) && context.ValidTokenTypes.Count > 0 &&
!context.ValidTokenTypes.Contains(usage))
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2110),
uri: SR.FormatID8000(SR.ID2110));
return;
}
// Note: at this point, the "token_usage" claim value is guaranteed to correspond
// to a known value as it is checked when validating the introspection response.
//
// If no value could be resolved, the token is assumed to be an access token.
context.Principal = principal.SetTokenType(usage ?? TokenTypeHints.AccessToken);
context.Logger.LogTrace(SR.GetResourceString(SR.ID6154), context.Token, context.Principal.Claims);
}
}
/// <summary>
/// Contains the logic responsible of normalizing the scope claims stored in the tokens.
/// </summary>
public class NormalizeScopeClaims : IOpenIddictValidationHandler<ValidateTokenContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<NormalizeScopeClaims>()
.SetOrder(IntrospectToken.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Principal is null)
{
return default;
}
// Note: in previous OpenIddict versions, scopes were represented as a JSON array
// and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim
// is formatted as a unique space-separated string containing all the granted scopes.
// To ensure access tokens generated by previous versions are still correctly handled,
// both formats (unique space-separated string or multiple scope claims) must be supported.
// To achieve that, all the "scope" claims are combined into a single one containg all the values.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information.
var scopes = context.Principal.GetClaims(Claims.Scope);
if (scopes.Length > 1)
{
context.Principal.SetClaim(Claims.Scope, string.Join(" ", scopes));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of mapping internal claims used by OpenIddict.
/// </summary>
public class MapInternalClaims : IOpenIddictValidationHandler<ValidateTokenContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<MapInternalClaims>()
.SetOrder(NormalizeScopeClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Principal is null)
{
return default;
}
// To reduce the size of tokens, some of the private claims used by OpenIddict
// are mapped to their standard equivalent before being removed from the token.
// This handler is responsible of adding back the private claims to the principal
// when receiving the token (e.g "oi_prst" is resolved from the "scope" claim).
// In OpenIddict 3.0, the creation date of a token is stored in "oi_crt_dt".
// If the claim doesn't exist, try to infer it from the standard "iat" JWT claim.
if (!context.Principal.HasClaim(Claims.Private.CreationDate))
{
var date = context.Principal.GetClaim(Claims.IssuedAt);
if (!string.IsNullOrEmpty(date) &&
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
context.Principal.SetCreationDate(DateTimeOffset.FromUnixTimeSeconds(value));
}
}
// In OpenIddict 3.0, the expiration date of a token is stored in "oi_exp_dt".
// If the claim doesn't exist, try to infer it from the standard "exp" JWT claim.
if (!context.Principal.HasClaim(Claims.Private.ExpirationDate))
{
var date = context.Principal.GetClaim(Claims.ExpiresAt);
if (!string.IsNullOrEmpty(date) &&
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
context.Principal.SetExpirationDate(DateTimeOffset.FromUnixTimeSeconds(value));
}
}
// In OpenIddict 3.0, the audiences allowed to receive a token are stored in "oi_aud".
// If no such claim exists, try to infer them from the standard "aud" JWT claims.
if (!context.Principal.HasClaim(Claims.Private.Audience))
{
var audiences = context.Principal.GetClaims(Claims.Audience);
if (audiences.Any())
{
context.Principal.SetAudiences(audiences);
}
}
// In OpenIddict 3.0, the presenters allowed to use a token are stored in "oi_prst".
// If no such claim exists, try to infer them from the standard "azp" and "client_id" JWT claims.
//
// Note: in previous OpenIddict versions, the presenters were represented in JWT tokens
// using the "azp" claim (defined by OpenID Connect), for which a single value could be
// specified. To ensure presenters stored in JWT tokens created by OpenIddict 1.x/2.x
// can still be read with OpenIddict 3.0, the presenter is automatically inferred from
// the "azp" or "client_id" claim if no "oi_prst" claim was found in the principal.
if (!context.Principal.HasClaim(Claims.Private.Presenter))
{
var presenter = context.Principal.GetClaim(Claims.AuthorizedParty) ??
context.Principal.GetClaim(Claims.ClientId);
if (!string.IsNullOrEmpty(presenter))
{
context.Principal.SetPresenters(presenter);
}
}
// In OpenIddict 3.0, the scopes granted to an application are stored in "oi_scp".
// If no such claim exists, try to infer them from the standard "scope" JWT claim,
// which is guaranteed to be a unique space-separated claim containing all the values.
if (!context.Principal.HasClaim(Claims.Private.Scope))
{
var scope = context.Principal.GetClaim(Claims.Scope);
if (!string.IsNullOrEmpty(scope))
{
context.Principal.SetScopes(scope.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries));
}
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of restoring the properties associated with a reference token entry.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class RestoreReferenceTokenProperties : IOpenIddictValidationHandler<ValidateTokenContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139));
public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>()
.AddFilter<RequireTokenEntryValidationEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Principal is null || string.IsNullOrEmpty(context.TokenId))
{
return;
}
var token = await _tokenManager.FindByIdAsync(context.TokenId);
if (token is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
}
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
/// <summary>
/// Contains the logic responsible of rejecting authentication demands for which no valid principal was resolved.
/// </summary>
public class ValidatePrincipal : IOpenIddictValidationHandler<ValidateTokenContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidatePrincipal>()
.SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Principal is null)
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return default;
}
// When using JWT or Data Protection tokens, the correct token type is always enforced by IdentityModel
// (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings).
// To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used,
// which requires that a special claim containing the token type be present in the security principal.
if (context.ValidTokenTypes.Count > 0)
{
var type = context.Principal.GetTokenType();
if (string.IsNullOrEmpty(type))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0004));
}
if (!context.ValidTokenTypes.Contains(type))
{
throw new InvalidOperationException(SR.FormatID0005(type, string.Join(", ", context.ValidTokenTypes)));
}
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authentication demands containing expired access tokens.
/// </summary>
public class ValidateExpirationDate : IOpenIddictValidationHandler<ValidateTokenContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidateExpirationDate>()
.SetOrder(ValidatePrincipal.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var date = context.Principal.GetExpirationDate();
if (date.HasValue && date.Value < DateTimeOffset.UtcNow)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6156));
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2019),
uri: SR.FormatID8000(SR.ID2019));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authentication demands containing
/// access tokens that were issued to be used by another audience/resource server.
/// </summary>
public class ValidateAudience : IOpenIddictValidationHandler<ValidateTokenContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.UseSingletonHandler<ValidateAudience>()
.SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
// If no explicit audience has been configured,
// skip the default audience validation.
if (context.Options.Audiences.Count == 0)
{
return default;
}
// If the access token doesn't have any audience attached, return an error.
var audiences = context.Principal.GetAudiences();
if (audiences.IsDefaultOrEmpty)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6157));
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2093),
uri: SR.FormatID8000(SR.ID2093));
return default;
}
// If the access token doesn't include any registered audience, return an error.
if (!audiences.Intersect(context.Options.Audiences, StringComparer.Ordinal).Any())
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6158));
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2094),
uri: SR.FormatID8000(SR.ID2094));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of authentication demands a token whose
/// associated token entry is no longer valid (e.g was revoked).
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateTokenEntry : IOpenIddictValidationHandler<ValidateTokenContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public ValidateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139));
public ValidateTokenEntry(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>()
.AddFilter<RequireTokenEntryValidationEnabled>()
.UseScopedHandler<ValidateTokenEntry>()
.SetOrder(ValidateAudience.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
}
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is null || !await _tokenManager.HasStatusAsync(token, Statuses.Valid))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier);
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2019),
uri: SR.FormatID8000(SR.ID2019));
return;
}
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
/// <summary>
/// Contains the logic responsible of authentication demands a token whose
/// associated authorization entry is no longer valid (e.g was revoked).
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateAuthorizationEntry : IOpenIddictValidationHandler<ValidateTokenContext>
{
private readonly IOpenIddictAuthorizationManager _authorizationManager;
public ValidateAuthorizationEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0142));
public ValidateAuthorizationEntry(IOpenIddictAuthorizationManager authorizationManager)
=> _authorizationManager = authorizationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ValidateTokenContext>()
.AddFilter<RequireLocalValidation>()
.AddFilter<RequireAuthorizationEntryValidationEnabled>()
.UseScopedHandler<ValidateAuthorizationEntry>()
.SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateTokenContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var identifier = context.Principal.GetAuthorizationId();
if (string.IsNullOrEmpty(identifier))
{
return;
}
var authorization = await _authorizationManager.FindByIdAsync(identifier);
if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier);
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2023),
uri: SR.FormatID8000(SR.ID2023));
return;
}
}
}
}
}
}

798
src/OpenIddict.Validation/OpenIddictValidationHandlers.cs

@ -7,18 +7,10 @@
using System;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.OpenIddictValidationEvents;
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters;
using Properties = OpenIddict.Validation.OpenIddictValidationConstants.Properties;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Validation
@ -30,18 +22,8 @@ namespace OpenIddict.Validation
/*
* Authentication processing:
*/
ValidateToken.Descriptor,
ValidateReferenceTokenIdentifier.Descriptor,
ValidateIdentityModelToken.Descriptor,
IntrospectToken.Descriptor,
NormalizeScopeClaims.Descriptor,
MapInternalClaims.Descriptor,
RestoreReferenceTokenProperties.Descriptor,
ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor,
ValidateAudience.Descriptor,
ValidateTokenEntry.Descriptor,
ValidateAuthorizationEntry.Descriptor,
EvaluateValidatedTokens.Descriptor,
ValidateAccessToken.Descriptor,
/*
* Challenge processing:
@ -49,19 +31,20 @@ namespace OpenIddict.Validation
AttachDefaultChallengeError.Descriptor)
.AddRange(Discovery.DefaultHandlers)
.AddRange(Introspection.DefaultHandlers);
.AddRange(Introspection.DefaultHandlers)
.AddRange(Protection.DefaultHandlers);
/// <summary>
/// Contains the logic responsible of ensuring a token was correctly resolved from the context.
/// Contains the logic responsible of selecting the token types that should be validated.
/// </summary>
public class ValidateToken : IOpenIddictValidationHandler<ProcessAuthenticationContext>
public class EvaluateValidatedTokens : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<ValidateToken>()
.UseSingletonHandler<EvaluateValidatedTokens>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@ -74,113 +57,46 @@ namespace OpenIddict.Validation
throw new ArgumentNullException(nameof(context));
}
(context.ValidateAccessToken, context.RequireAccessToken) = context.EndpointType switch
{
// The validation handler is responsible of validating access tokens for endpoints
// it doesn't manage (typically, API endpoints using token authentication).
//
// As such, sending an access token is not mandatory: API endpoints that require
// authentication can set up an authorization policy to reject such requests later
// in the request processing pipeline (typically, via the authorization middleware).
OpenIddictValidationEndpointType.Unknown => (true, false),
_ => (false, false)
};
// Note: unlike the equivalent event in the server stack, authentication can be triggered for
// arbitrary requests (typically, API endpoints that are not owned by the validation stack).
// As such, the token is not directly resolved from the request, that may be null at this stage.
// Instead, the token is expected to be populated by one or multiple handlers provided by the host.
//
// Note: this event can also be triggered by the validation service to validate an arbitrary token.
if (string.IsNullOrEmpty(context.Token))
{
context.Reject(
error: Errors.MissingToken,
description: SR.GetResourceString(SR.ID2000),
uri: SR.FormatID8000(SR.ID2000));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of validating reference token identifiers.
/// Note: this handler is not used when the degraded mode is enabled.
/// Contains the logic responsible of ensuring a token was correctly resolved from the context.
/// </summary>
public class ValidateReferenceTokenIdentifier : IOpenIddictValidationHandler<ProcessAuthenticationContext>
public class ValidateAccessToken : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public ValidateReferenceTokenIdentifier() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139));
private readonly IOpenIddictValidationDispatcher _dispatcher;
public ValidateReferenceTokenIdentifier(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager;
public ValidateAccessToken(IOpenIddictValidationDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireLocalValidation>()
.AddFilter<RequireTokenEntryValidationEnabled>()
.UseScopedHandler<ValidateReferenceTokenIdentifier>()
.SetOrder(ValidateToken.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reference tokens are base64url-encoded payloads of exactly 256 bits (generated using a
// crypto-secure RNG). If the token length differs, the token cannot be a reference token.
if (string.IsNullOrEmpty(context.Token) || context.Token.Length != 43)
{
return;
}
// If the reference token cannot be found, don't return an error to allow another handler to validate it.
var token = await _tokenManager.FindByReferenceIdAsync(context.Token);
if (token is null)
{
return;
}
// If the type associated with the token entry doesn't match the expected type, return an error.
if (!string.IsNullOrEmpty(context.TokenType) && !await _tokenManager.HasTypeAsync(token, context.TokenType))
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return;
}
var payload = await _tokenManager.GetPayloadAsync(token);
if (string.IsNullOrEmpty(payload))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0026));
}
// Replace the token parameter by the payload resolved from the token entry.
context.Token = payload;
// Store the identifier of the reference token in the transaction properties
// so it can be later used to restore the properties associated with the token.
context.Transaction.Properties[Properties.ReferenceTokenIdentifier] = await _tokenManager.GetIdAsync(token);
}
}
/// <summary>
/// Contains the logic responsible of validating tokens generated using IdentityModel.
/// </summary>
public class ValidateIdentityModelToken : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireLocalValidation>()
.UseSingletonHandler<ValidateIdentityModelToken>()
.SetOrder(ValidateReferenceTokenIdentifier.Descriptor.Order + 1_000)
.AddFilter<RequireAccessTokenValidated>()
.UseScopedHandler<ValidateAccessToken>()
.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@ -192,670 +108,56 @@ namespace OpenIddict.Validation
throw new ArgumentNullException(nameof(context));
}
// If a principal was already attached, don't overwrite it.
if (context.Principal is not null)
if (context.AccessTokenPrincipal is not null)
{
return;
}
// If the token cannot be read, don't return an error to allow another handler to validate it.
if (!context.Options.JsonWebTokenHandler.CanReadToken(context.Token))
if (string.IsNullOrEmpty(context.AccessToken))
{
return;
}
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
// Clone the token validation parameters and set the issuer using the value found in the
// OpenID Connect server configuration (that can be static or retrieved using discovery).
var parameters = context.Options.TokenValidationParameters.Clone();
parameters.ValidIssuer ??= configuration.Issuer ?? context.Issuer?.AbsoluteUri;
parameters.ValidateIssuer = !string.IsNullOrEmpty(parameters.ValidIssuer);
// Combine the signing keys registered statically in the token validation parameters
// with the signing keys resolved from the OpenID Connect server configuration.
parameters.IssuerSigningKeys =
parameters.IssuerSigningKeys?.Concat(configuration.SigningKeys) ?? configuration.SigningKeys;
parameters.ValidTypes = context.TokenType switch
{
// If no specific token type is expected, accept all token types at this stage.
// Additional filtering can be made based on the resolved/actual token type.
null or { Length: 0 } => null,
// For access tokens, both "at+jwt" and "application/at+jwt" are valid.
TokenTypeHints.AccessToken => new[]
if (context.RequireAccessToken)
{
JsonWebTokenTypes.AccessToken,
JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken
},
context.Reject(
error: Errors.MissingToken,
description: SR.GetResourceString(SR.ID2000),
uri: SR.FormatID8000(SR.ID2000));
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
};
var result = context.Options.JsonWebTokenHandler.ValidateToken(context.Token, parameters);
if (!result.IsValid)
{
// If validation failed because of an unrecognized key identifier, inform the configuration manager
// that the configuration MAY have be refreshed by sending a new discovery request to the server.
if (result.Exception is SecurityTokenSignatureKeyNotFoundException)
{
context.Options.ConfigurationManager.RequestRefresh();
return;
}
context.Logger.LogTrace(result.Exception, SR.GetResourceString(SR.ID6000), context.Token);
context.Reject(
error: Errors.InvalidToken,
description: result.Exception switch
{
SecurityTokenInvalidIssuerException => SR.GetResourceString(SR.ID2088),
SecurityTokenInvalidTypeException => SR.GetResourceString(SR.ID2089),
SecurityTokenSignatureKeyNotFoundException => SR.GetResourceString(SR.ID2090),
SecurityTokenInvalidSignatureException => SR.GetResourceString(SR.ID2091),
_ => SR.GetResourceString(SR.ID2004)
},
uri: result.Exception switch
{
SecurityTokenInvalidIssuerException => SR.FormatID8000(SR.ID2088),
SecurityTokenInvalidTypeException => SR.FormatID8000(SR.ID2089),
SecurityTokenSignatureKeyNotFoundException => SR.FormatID8000(SR.ID2090),
SecurityTokenInvalidSignatureException => SR.FormatID8000(SR.ID2091),
_ => SR.FormatID8000(SR.ID2004)
});
return;
}
// Attach the principal extracted from the token to the parent event context.
context.Principal = new ClaimsPrincipal(result.ClaimsIdentity);
// Store the token type (resolved from "typ" or "token_usage") as a special private claim.
context.Principal.SetTokenType(result.TokenType switch
{
null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)),
// Both at+jwt and application/at+jwt are supported for access tokens.
JsonWebTokenTypes.AccessToken or JsonWebTokenTypes.Prefixes.Application + JsonWebTokenTypes.AccessToken
=> TokenTypeHints.AccessToken,
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0003))
});
context.Logger.LogTrace(SR.GetResourceString(SR.ID6001), context.Token, context.Principal.Claims);
}
}
/// <summary>
/// Contains the logic responsible of validating the tokens using OAuth 2.0 introspection.
/// </summary>
public class IntrospectToken : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictValidationService _service;
public IntrospectToken(OpenIddictValidationService service)
=> _service = service;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireIntrospectionValidation>()
.UseSingletonHandler<IntrospectToken>()
.SetOrder(ValidateIdentityModelToken.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If a principal was already attached, don't overwrite it.
if (context.Principal is not null)
{
return;
}
Debug.Assert(!string.IsNullOrEmpty(context.Token), SR.GetResourceString(SR.ID4010));
var configuration = await context.Options.ConfigurationManager.GetConfigurationAsync(default) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0140));
if (string.IsNullOrEmpty(configuration.IntrospectionEndpoint) ||
!Uri.TryCreate(configuration.IntrospectionEndpoint, UriKind.Absolute, out Uri? address) ||
!address.IsWellFormedOriginalString())
{
context.Reject(
error: Errors.ServerError,
description: SR.GetResourceString(SR.ID2092),
uri: SR.FormatID8000(SR.ID2092));
return;
}
try
{
var principal = await _service.IntrospectTokenAsync(address, context.Token, context.TokenType) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0141));
// Note: tokens that are considered valid at this point are assumed to be of the given type,
// as the introspection handlers ensure the introspected token type matches the expected
// type when a "token_usage" claim was returned as part of the introspection response.
// If no token type can be inferred, the token is assumed to be an access token.
context.Principal = principal.SetTokenType(context.TokenType ?? TokenTypeHints.AccessToken);
context.Logger.LogTrace(SR.GetResourceString(SR.ID6154), context.Token, context.Principal.Claims);
}
catch (Exception exception)
{
context.Logger.LogDebug(exception, SR.GetResourceString(SR.ID6155));
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return;
}
}
}
/// <summary>
/// Contains the logic responsible of normalizing the scope claims stored in the tokens.
/// </summary>
public class NormalizeScopeClaims : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<NormalizeScopeClaims>()
.SetOrder(IntrospectToken.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Principal is null)
{
return default;
}
// Note: in previous OpenIddict versions, scopes were represented as a JSON array
// and deserialized as multiple claims. In OpenIddict 3.0, the public "scope" claim
// is formatted as a unique space-separated string containing all the granted scopes.
// To ensure access tokens generated by previous versions are still correctly handled,
// both formats (unique space-separated string or multiple scope claims) must be supported.
// To achieve that, all the "scope" claims are combined into a single one containg all the values.
// Visit https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information.
var scopes = context.Principal.GetClaims(Claims.Scope);
if (scopes.Length > 1)
{
context.Principal.SetClaim(Claims.Scope, string.Join(" ", scopes));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of mapping internal claims used by OpenIddict.
/// </summary>
public class MapInternalClaims : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<MapInternalClaims>()
.SetOrder(NormalizeScopeClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Principal is null)
{
return default;
}
// To reduce the size of tokens, some of the private claims used by OpenIddict
// are mapped to their standard equivalent before being removed from the token.
// This handler is responsible of adding back the private claims to the principal
// when receiving the token (e.g "oi_prst" is resolved from the "scope" claim).
// In OpenIddict 3.0, the creation date of a token is stored in "oi_crt_dt".
// If the claim doesn't exist, try to infer it from the standard "iat" JWT claim.
if (!context.Principal.HasClaim(Claims.Private.CreationDate))
{
var date = context.Principal.GetClaim(Claims.IssuedAt);
if (!string.IsNullOrEmpty(date) &&
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
context.Principal.SetCreationDate(DateTimeOffset.FromUnixTimeSeconds(value));
}
}
// In OpenIddict 3.0, the expiration date of a token is stored in "oi_exp_dt".
// If the claim doesn't exist, try to infer it from the standard "exp" JWT claim.
if (!context.Principal.HasClaim(Claims.Private.ExpirationDate))
{
var date = context.Principal.GetClaim(Claims.ExpiresAt);
if (!string.IsNullOrEmpty(date) &&
long.TryParse(date, NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
context.Principal.SetExpirationDate(DateTimeOffset.FromUnixTimeSeconds(value));
}
}
// In OpenIddict 3.0, the audiences allowed to receive a token are stored in "oi_aud".
// If no such claim exists, try to infer them from the standard "aud" JWT claims.
if (!context.Principal.HasClaim(Claims.Private.Audience))
{
var audiences = context.Principal.GetClaims(Claims.Audience);
if (audiences.Any())
{
context.Principal.SetAudiences(audiences);
}
}
// In OpenIddict 3.0, the presenters allowed to use a token are stored in "oi_prst".
// If no such claim exists, try to infer them from the standard "azp" and "client_id" JWT claims.
//
// Note: in previous OpenIddict versions, the presenters were represented in JWT tokens
// using the "azp" claim (defined by OpenID Connect), for which a single value could be
// specified. To ensure presenters stored in JWT tokens created by OpenIddict 1.x/2.x
// can still be read with OpenIddict 3.0, the presenter is automatically inferred from
// the "azp" or "client_id" claim if no "oi_prst" claim was found in the principal.
if (!context.Principal.HasClaim(Claims.Private.Presenter))
{
var presenter = context.Principal.GetClaim(Claims.AuthorizedParty) ??
context.Principal.GetClaim(Claims.ClientId);
if (!string.IsNullOrEmpty(presenter))
{
context.Principal.SetPresenters(presenter);
}
}
// In OpenIddict 3.0, the scopes granted to an application are stored in "oi_scp".
// If no such claim exists, try to infer them from the standard "scope" JWT claim,
// which is guaranteed to be a unique space-separated claim containing all the values.
if (!context.Principal.HasClaim(Claims.Private.Scope))
{
var scope = context.Principal.GetClaim(Claims.Scope);
if (!string.IsNullOrEmpty(scope))
{
context.Principal.SetScopes(scope.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries));
}
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of restoring the properties associated with a reference token entry.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class RestoreReferenceTokenProperties : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public RestoreReferenceTokenProperties() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139));
public RestoreReferenceTokenProperties(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireLocalValidation>()
.AddFilter<RequireTokenEntryValidationEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
var notification = new ValidateTokenContext(context.Transaction)
{
throw new ArgumentNullException(nameof(context));
}
Token = context.AccessToken,
ValidTokenTypes = { TokenTypeHints.AccessToken }
};
if (context.Principal is null)
{
return;
}
await _dispatcher.DispatchAsync(notification);
var identifier = context.Transaction.GetProperty<string>(Properties.ReferenceTokenIdentifier);
if (string.IsNullOrEmpty(identifier))
if (notification.IsRequestHandled)
{
context.HandleRequest();
return;
}
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0021));
}
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal = context.Principal
.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
/// <summary>
/// Contains the logic responsible of rejecting authentication demands for which no valid principal was resolved.
/// </summary>
public class ValidatePrincipal : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<ValidatePrincipal>()
.SetOrder(RestoreReferenceTokenProperties.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Principal is null)
{
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2004),
uri: SR.FormatID8000(SR.ID2004));
return default;
}
// When using JWT or Data Protection tokens, the correct token type is always enforced by IdentityModel
// (using the "typ" header) or by ASP.NET Core Data Protection (using per-token-type purposes strings).
// To ensure tokens deserialized using a custom routine are of the expected type, a manual check is used,
// which requires that a special claim containing the token type be present in the security principal.
if (!string.IsNullOrEmpty(context.TokenType))
{
var type = context.Principal.GetTokenType();
if (string.IsNullOrEmpty(type))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0004));
}
if (!string.Equals(type, context.TokenType, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(SR.FormatID0005(type, context.TokenType));
}
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authentication demands containing expired access tokens.
/// </summary>
public class ValidateExpirationDate : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<ValidateExpirationDate>()
.SetOrder(ValidatePrincipal.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var date = context.Principal.GetExpirationDate();
if (date.HasValue && date.Value < DateTimeOffset.UtcNow)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6156));
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2019),
uri: SR.FormatID8000(SR.ID2019));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authentication demands containing
/// access tokens that were issued to be used by another audience/resource server.
/// </summary>
public class ValidateAudience : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<ValidateAudience>()
.SetOrder(ValidateExpirationDate.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
// If no explicit audience has been configured,
// skip the default audience validation.
if (context.Options.Audiences.Count == 0)
{
return default;
}
// If the access token doesn't have any audience attached, return an error.
var audiences = context.Principal.GetAudiences();
if (audiences.IsDefaultOrEmpty)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6157));
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2093),
uri: SR.FormatID8000(SR.ID2093));
return default;
}
// If the access token doesn't include any registered audience, return an error.
if (!audiences.Intersect(context.Options.Audiences, StringComparer.Ordinal).Any())
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6158));
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2094),
uri: SR.FormatID8000(SR.ID2094));
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of authentication demands a token whose
/// associated token entry is no longer valid (e.g was revoked).
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateTokenEntry : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
private readonly IOpenIddictTokenManager _tokenManager;
public ValidateTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0139));
public ValidateTokenEntry(IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireLocalValidation>()
.AddFilter<RequireTokenEntryValidationEnabled>()
.UseScopedHandler<ValidateTokenEntry>()
.SetOrder(ValidateAudience.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var identifier = context.Principal.GetTokenId();
if (string.IsNullOrEmpty(identifier))
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
var token = await _tokenManager.FindByIdAsync(identifier);
if (token is null || !await _tokenManager.HasStatusAsync(token, Statuses.Valid))
else if (notification.IsRejected)
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6005), identifier);
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2019),
uri: SR.FormatID8000(SR.ID2019));
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
/// <summary>
/// Contains the logic responsible of authentication demands a token whose
/// associated authorization entry is no longer valid (e.g was revoked).
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateAuthorizationEntry : IOpenIddictValidationHandler<ProcessAuthenticationContext>
{
private readonly IOpenIddictAuthorizationManager _authorizationManager;
public ValidateAuthorizationEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0142));
public ValidateAuthorizationEntry(IOpenIddictAuthorizationManager authorizationManager)
=> _authorizationManager = authorizationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireLocalValidation>()
.AddFilter<RequireAuthorizationEntryValidationEnabled>()
.UseScopedHandler<ValidateAuthorizationEntry>()
.SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var identifier = context.Principal.GetAuthorizationId();
if (string.IsNullOrEmpty(identifier))
{
return;
}
var authorization = await _authorizationManager.FindByIdAsync(identifier);
if (authorization is null || !await _authorizationManager.HasStatusAsync(authorization, Statuses.Valid))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6006), identifier);
context.Reject(
error: Errors.InvalidToken,
description: SR.GetResourceString(SR.ID2023),
uri: SR.FormatID8000(SR.ID2023));
return;
}
context.AccessTokenPrincipal = notification.Principal;
}
}

15
src/OpenIddict.Validation/OpenIddictValidationService.cs

@ -321,18 +321,18 @@ namespace OpenIddict.Validation
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The claims principal created from the claim retrieved from the remote server.</returns>
public ValueTask<ClaimsPrincipal> IntrospectTokenAsync(Uri address, string token, CancellationToken cancellationToken = default)
=> IntrospectTokenAsync(address, token, type: null, cancellationToken);
=> IntrospectTokenAsync(address, token, hint: null, cancellationToken);
/// <summary>
/// Sends an introspection request to the specified address and returns the corresponding principal.
/// </summary>
/// <param name="address">The address of the remote metadata endpoint.</param>
/// <param name="token">The token to introspect.</param>
/// <param name="type">The token type to introspect.</param>
/// <param name="hint">The token type to introspect, used as a hint by the authorization server.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>The claims principal created from the claim retrieved from the remote server.</returns>
public async ValueTask<ClaimsPrincipal> IntrospectTokenAsync(
Uri address, string token, string? type, CancellationToken cancellationToken = default)
Uri address, string token, string? hint, CancellationToken cancellationToken = default)
{
if (address is null)
{
@ -384,7 +384,7 @@ namespace OpenIddict.Validation
Address = address,
Request = request,
Token = token,
TokenType = type
TokenTypeHint = hint
};
await dispatcher.DispatchAsync(context);
@ -445,8 +445,7 @@ namespace OpenIddict.Validation
{
Request = request,
Response = response,
Token = token,
TokenType = type
Token = token
};
await dispatcher.DispatchAsync(context);
@ -506,10 +505,10 @@ namespace OpenIddict.Validation
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictValidationFactory>();
var transaction = await factory.CreateTransactionAsync();
var context = new ProcessAuthenticationContext(transaction)
var context = new ValidateTokenContext(transaction)
{
Token = token,
TokenType = TokenTypeHints.AccessToken
ValidTokenTypes = { TokenTypeHints.AccessToken }
};
await dispatcher.DispatchAsync(context);

9
test/OpenIddict.Server.AspNetCore.IntegrationTests/OpenIddictServerAspNetCoreIntegrationTests.cs

@ -27,6 +27,7 @@ using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlers;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.AspNetCore.IntegrationTests
@ -55,12 +56,12 @@ namespace OpenIddict.Server.AspNetCore.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -106,12 +107,12 @@ namespace OpenIddict.Server.AspNetCore.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)

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

@ -18,6 +18,7 @@ using Xunit;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.IntegrationTests
@ -303,12 +304,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -396,12 +397,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -439,12 +440,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -481,12 +482,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -524,12 +525,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -567,12 +568,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -610,12 +611,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -655,12 +656,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -700,12 +701,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -744,12 +745,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -790,12 +791,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -836,12 +837,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -884,12 +885,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -932,12 +933,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -986,12 +987,12 @@ namespace OpenIddict.Server.IntegrationTests
options.EnableDegradedMode();
options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -1032,12 +1033,12 @@ namespace OpenIddict.Server.IntegrationTests
options.EnableDegradedMode();
options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -1078,12 +1079,12 @@ namespace OpenIddict.Server.IntegrationTests
options.EnableDegradedMode();
options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -1122,12 +1123,12 @@ namespace OpenIddict.Server.IntegrationTests
options.EnableDegradedMode();
options.RegisterScopes(Scopes.Phone, Scopes.Profile);
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -1861,12 +1862,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -1927,12 +1928,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -2087,12 +2088,12 @@ namespace OpenIddict.Server.IntegrationTests
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -2152,12 +2153,12 @@ namespace OpenIddict.Server.IntegrationTests
// Arrange
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2209,12 +2210,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -2281,12 +2282,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2349,12 +2350,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -2437,12 +2438,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.SetRefreshTokenReuseLeeway(leeway: null);
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2511,12 +2512,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.SetRefreshTokenReuseLeeway(TimeSpan.FromSeconds(5));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2588,12 +2589,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.SetRefreshTokenReuseLeeway(TimeSpan.FromMinutes(5));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2670,12 +2671,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -2776,12 +2777,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.SetRefreshTokenReuseLeeway(leeway: null);
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2869,12 +2870,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.SetRefreshTokenReuseLeeway(TimeSpan.FromSeconds(5));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2965,12 +2966,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.SetRefreshTokenReuseLeeway(TimeSpan.FromMinutes(5));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -3049,12 +3050,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -3136,12 +3137,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -3198,12 +3199,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -3302,12 +3303,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.DisableRollingRefreshTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -3385,12 +3386,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -3484,12 +3485,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -3579,12 +3580,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -3664,12 +3665,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -3769,12 +3770,14 @@ namespace OpenIddict.Server.IntegrationTests
{
options.DisableRollingRefreshTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(context.TokenType)
.SetTokenType(context.Request.IsAuthorizationCodeGrantType() ?
TokenTypeHints.AuthorizationCode :
TokenTypeHints.RefreshToken)
.SetPresenters("Fabrikam")
.SetTokenId("0270F515-C5B1-4FBF-B673-D7CAF7CCDABC")
.SetClaim(Claims.Subject, "Bob le Bricoleur");

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

@ -19,6 +19,7 @@ using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Introspection;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.IntegrationTests
@ -190,7 +191,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -237,7 +238,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -278,7 +279,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -322,7 +323,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -591,7 +592,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -637,7 +638,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -686,7 +687,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -730,7 +731,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -787,7 +788,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -842,7 +843,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -902,7 +903,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -969,7 +970,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -1099,7 +1100,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -1191,7 +1192,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -1290,7 +1291,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -1402,7 +1403,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -1474,7 +1475,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -1520,7 +1521,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -1569,7 +1570,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -1613,7 +1614,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{

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

@ -15,6 +15,7 @@ using Xunit;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
using static OpenIddict.Server.OpenIddictServerHandlers.Revocation;
using SR = OpenIddict.Abstractions.OpenIddictResources;
@ -168,7 +169,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -209,7 +210,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -253,7 +254,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -530,7 +531,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -576,7 +577,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -625,7 +626,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -673,7 +674,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -734,7 +735,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -798,7 +799,7 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -849,7 +850,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -895,7 +896,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -944,7 +945,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -988,7 +989,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{

29
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Userinfo.cs

@ -14,6 +14,7 @@ using Xunit;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.IntegrationTests
@ -177,7 +178,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -223,7 +224,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -269,7 +270,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -318,7 +319,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -362,7 +363,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -403,7 +404,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -452,7 +453,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -499,7 +500,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -539,7 +540,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -586,7 +587,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -632,7 +633,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -681,7 +682,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -725,7 +726,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -774,7 +775,7 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{

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

@ -21,6 +21,7 @@ using Xunit.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
using SR = OpenIddict.Abstractions.OpenIddictResources;
namespace OpenIddict.Server.IntegrationTests
@ -190,12 +191,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -237,12 +238,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
var identity = new ClaimsIdentity("Bearer");
identity.AddClaim(new Claim(Claims.IssuedAt, "1577836800", ClaimValueTypes.Integer64));
@ -289,12 +290,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
var identity = new ClaimsIdentity("Bearer");
identity.AddClaim(new Claim(Claims.ExpiresAt, "2524608000", ClaimValueTypes.Integer64));
@ -341,12 +342,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -391,12 +392,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -441,12 +442,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -491,12 +492,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -541,12 +542,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -590,12 +591,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -639,12 +640,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -688,12 +689,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(null)
@ -738,12 +739,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -848,12 +849,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("id_token", context.Token);
Assert.Equal(TokenTypeHints.IdToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.IdToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.IdToken)
@ -959,12 +960,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("authorization_code", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -1071,12 +1072,12 @@ namespace OpenIddict.Server.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("refresh_token", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -1226,7 +1227,7 @@ namespace OpenIddict.Server.IntegrationTests
options.EnableDegradedMode();
options.SetUserinfoEndpointUris("/challenge");
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
@ -1681,7 +1682,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -1741,7 +1742,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -1792,7 +1793,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -1820,12 +1821,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -1848,7 +1849,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -1874,12 +1875,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token);
Assert.Equal(TokenTypeHints.DeviceCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity())
.SetTokenType(TokenTypeHints.DeviceCode)
@ -1914,7 +1915,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -1940,12 +1941,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -1967,7 +1968,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2011,7 +2012,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2056,7 +2057,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2102,7 +2103,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2149,7 +2150,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2195,7 +2196,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2220,12 +2221,12 @@ namespace OpenIddict.Server.IntegrationTests
options.EnableDegradedMode();
options.RegisterScopes(Scopes.Profile);
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2292,7 +2293,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2318,12 +2319,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -2347,7 +2348,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2373,12 +2374,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token);
Assert.Equal(TokenTypeHints.DeviceCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity())
.SetTokenType(TokenTypeHints.DeviceCode)
@ -2414,7 +2415,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2440,12 +2441,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2468,7 +2469,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2503,7 +2504,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
options.AddEventHandler<HandleTokenRequestContext>(builder =>
@ -2549,7 +2550,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
options.AddEventHandler<HandleTokenRequestContext>(builder =>
@ -2596,7 +2597,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
options.AddEventHandler<HandleTokenRequestContext>(builder =>
@ -2649,7 +2650,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2698,7 +2699,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2726,12 +2727,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -2755,7 +2756,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2781,12 +2782,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS", context.Token);
Assert.Equal(TokenTypeHints.DeviceCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.DeviceCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity())
.SetTokenType(TokenTypeHints.DeviceCode)
@ -2822,7 +2823,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2848,12 +2849,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -2876,7 +2877,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -2911,7 +2912,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
options.AddEventHandler<HandleTokenRequestContext>(builder =>
@ -2966,7 +2967,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -3013,7 +3014,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -3056,7 +3057,7 @@ namespace OpenIddict.Server.IntegrationTests
return default;
});
builder.SetOrder(EvaluateTokenTypes.Descriptor.Order + 500);
builder.SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 500);
});
});
@ -3080,12 +3081,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -3149,12 +3150,12 @@ namespace OpenIddict.Server.IntegrationTests
await using var server = await CreateServerAsync(options =>
{
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("SplxlOBeZQQYbYS6WxSbIA", context.Token);
Assert.Equal(TokenTypeHints.AuthorizationCode, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AuthorizationCode }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AuthorizationCode)
@ -3229,12 +3230,12 @@ namespace OpenIddict.Server.IntegrationTests
{
options.DisableAuthorizationStorage();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -3293,12 +3294,12 @@ namespace OpenIddict.Server.IntegrationTests
options.DisableAuthorizationStorage();
options.DisableRollingRefreshTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("8xLOxBtZp8", context.Token);
Assert.Equal(TokenTypeHints.RefreshToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.RefreshToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.RefreshToken)
@ -3852,10 +3853,10 @@ namespace OpenIddict.Server.IntegrationTests
options.AddEventHandler<ValidateVerificationRequestContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
builder.UseInlineHandler(context => default));
options.AddEventHandler<ProcessSignInContext>(builder =>
options.AddEventHandler<GenerateTokenContext>(builder =>
builder.UseInlineHandler(context => default));
});
}

9
test/OpenIddict.Server.Owin.IntegrationTests/OpenIddictServerOwinIntegrationTests.cs

@ -24,6 +24,7 @@ using Xunit.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlers;
using SR = OpenIddict.Abstractions.OpenIddictResources;
@ -53,12 +54,12 @@ namespace OpenIddict.Server.Owin.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
@ -104,12 +105,12 @@ namespace OpenIddict.Server.Owin.IntegrationTests
return default;
}));
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
options.AddEventHandler<ValidateTokenContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("access_token", context.Token);
Assert.Equal(TokenTypeHints.AccessToken, context.TokenType);
Assert.Equal(new[] { TokenTypeHints.AccessToken }, context.ValidTokenTypes);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)

Loading…
Cancel
Save