Browse Source

Create a DB entry for all types of tokens, rework reference tokens support and add token entry validation to the validation handler

pull/940/head
Kévin Chalet 6 years ago
parent
commit
414e05eed4
  1. 20
      samples/Mvc.Server/Startup.cs
  2. 4
      samples/Mvc.Server/Worker.cs
  3. 6
      src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs
  4. 17
      src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs
  5. 7
      src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs
  6. 9
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  7. 2
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs
  8. 6
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs
  9. 2
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs
  10. 8
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs
  11. 6
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlerFilters.cs
  12. 35
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs
  13. 14
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs
  14. 24
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  15. 4
      src/OpenIddict.Server/OpenIddictServerConfiguration.cs
  16. 2
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  17. 6
      src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
  18. 12
      src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
  19. 16
      src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
  20. 1193
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  21. 27
      src/OpenIddict.Server/OpenIddictServerOptions.cs
  22. 2
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs
  23. 2
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs
  24. 11
      src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs
  25. 38
      src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs
  26. 9
      src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs
  27. 23
      src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
  28. 2
      src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs
  29. 2
      src/OpenIddict.Validation/OpenIddictValidationExtensions.cs
  30. 6
      src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs
  31. 78
      src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
  32. 16
      src/OpenIddict.Validation/OpenIddictValidationOptions.cs
  33. 16
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
  34. 12
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs
  35. 10
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs
  36. 49
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs
  37. 35
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs

20
samples/Mvc.Server/Startup.cs

@ -1,5 +1,4 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -128,6 +127,15 @@ namespace Mvc.Server
// Register the ASP.NET Core host. // Register the ASP.NET Core host.
options.UseAspNetCore(); options.UseAspNetCore();
// For applications that need immediate access token or authorization
// revocation, the database entry of the received tokens and their
// associated authorizations can be validated for each API call.
// Enabling these options may have a negative impact on performance.
//
// options.EnableAuthorizationValidation();
//
// options.EnableTokenValidation();
}); });
services.AddTransient<IEmailSender, AuthMessageSender>(); services.AddTransient<IEmailSender, AuthMessageSender>();
@ -146,16 +154,6 @@ namespace Mvc.Server
app.UseStatusCodePagesWithReExecute("/error"); app.UseStatusCodePagesWithReExecute("/error");
// Note: ASP.NET Core is impacted by a bug that prevents the status code pages
// from working correctly with endpoint routing. For more information, visit
// https://github.com/aspnet/AspNetCore/issues/13715#issuecomment-528929683.
app.Use((context, next) =>
{
context.SetEndpoint(null);
return next();
});
app.UseRouting(); app.UseRouting();
app.UseAuthentication(); app.UseAuthentication();

4
samples/Mvc.Server/Worker.cs

@ -15,8 +15,8 @@ namespace Mvc.Server
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceScopeFactory) public Worker(IServiceProvider serviceProvider)
=> _serviceProvider = serviceScopeFactory; => _serviceProvider = serviceProvider;
public async Task StartAsync(CancellationToken cancellationToken) public async Task StartAsync(CancellationToken cancellationToken)
{ {

6
src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs

@ -49,9 +49,9 @@ namespace OpenIddict.Abstractions
using var document = JsonDocument.ParseValue(ref reader); using var document = JsonDocument.ParseValue(ref reader);
return type == typeof(OpenIddictMessage) ? new OpenIddictMessage(document.RootElement.Clone()) : return type == typeof(OpenIddictMessage) ? new OpenIddictMessage(document.RootElement.Clone()) :
type == typeof(OpenIddictRequest) ? (OpenIddictMessage) new OpenIddictRequest(document.RootElement.Clone()) : type == typeof(OpenIddictRequest) ? new OpenIddictRequest(document.RootElement.Clone()) :
type == typeof(OpenIddictResponse) ? new OpenIddictResponse(document.RootElement.Clone()) : type == typeof(OpenIddictResponse) ? (OpenIddictMessage) new OpenIddictResponse(document.RootElement.Clone()) :
throw new ArgumentException("The specified type is not supported.", nameof(type)); throw new ArgumentException("The specified type is not supported.", nameof(type));
} }

17
src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs

@ -91,7 +91,18 @@ namespace OpenIddict.Abstractions
foreach (var parameter in parameters.GroupBy(parameter => parameter.Key)) foreach (var parameter in parameters.GroupBy(parameter => parameter.Key))
{ {
AddParameter(parameter.Key, parameter.Select(parameter => parameter.Value).ToArray()); var values = parameter.Select(parameter => parameter.Value).ToArray();
// Note: the core OAuth 2.0 specification requires that request parameters
// not be present more than once but derived specifications like the
// token exchange specification deliberately allow specifying multiple
// parameters with the same name to represent a multi-valued parameter.
AddParameter(parameter.Key, values.Length switch
{
0 => default,
1 => values[0],
_ => values
});
} }
} }
@ -110,7 +121,7 @@ namespace OpenIddict.Abstractions
{ {
// Note: the core OAuth 2.0 specification requires that request parameters // Note: the core OAuth 2.0 specification requires that request parameters
// not be present more than once but derived specifications like the // not be present more than once but derived specifications like the
// token exchange RFC deliberately allow specifying multiple resource // token exchange specification deliberately allow specifying multiple
// parameters with the same name to represent a multi-valued parameter. // parameters with the same name to represent a multi-valued parameter.
AddParameter(parameter.Key, parameter.Value?.Length switch AddParameter(parameter.Key, parameter.Value?.Length switch
{ {
@ -137,7 +148,7 @@ namespace OpenIddict.Abstractions
{ {
// Note: the core OAuth 2.0 specification requires that request parameters // Note: the core OAuth 2.0 specification requires that request parameters
// not be present more than once but derived specifications like the // not be present more than once but derived specifications like the
// token exchange RFC deliberately allow specifying multiple resource // token exchange specification deliberately allow specifying multiple
// parameters with the same name to represent a multi-valued parameter. // parameters with the same name to represent a multi-valued parameter.
AddParameter(parameter.Key, parameter.Value.Count switch AddParameter(parameter.Key, parameter.Value.Count switch
{ {

7
src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs

@ -798,8 +798,11 @@ namespace OpenIddict.Abstractions
JsonElement value when value.ValueKind == JsonValueKind.String JsonElement value when value.ValueKind == JsonValueKind.String
=> string.IsNullOrEmpty(value.GetString()), => string.IsNullOrEmpty(value.GetString()),
JsonElement value when value.ValueKind == JsonValueKind.Array => value.GetArrayLength() == 0, JsonElement value when value.ValueKind == JsonValueKind.Array
JsonElement value when value.ValueKind == JsonValueKind.Object => IsEmptyNode(value), => value.GetArrayLength() == 0,
JsonElement value when value.ValueKind == JsonValueKind.Object
=> IsEmptyNode(value),
_ => false _ => false
}; };

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

@ -1267,15 +1267,6 @@ namespace OpenIddict.Core
yield return new ValidationResult("The token type cannot be null or empty."); yield return new ValidationResult("The token type cannot be null or empty.");
} }
else if (!string.Equals(type, TokenTypeHints.AccessToken, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, TokenTypeHints.AuthorizationCode, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, TokenTypeHints.DeviceCode, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, TokenTypeHints.RefreshToken, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, TokenTypeHints.UserCode, StringComparison.OrdinalIgnoreCase))
{
yield return new ValidationResult("The specified token type is not supported by the default token manager.");
}
if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken))) if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken)))
{ {
yield return new ValidationResult("The status cannot be null or empty."); yield return new ValidationResult("The status cannot be null or empty.");

2
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs

@ -62,7 +62,7 @@ namespace OpenIddict.Server.AspNetCore
/// <summary> /// <summary>
/// Ensures that the authentication configuration is in a consistent and valid state. /// Ensures that the authentication configuration is in a consistent and valid state.
/// </summary> /// </summary>
/// <param name="name">The authentication scheme associated with the handler instance.</param> /// <param name="name">The name of the options instance to configure, if applicable.</param>
/// <param name="options">The options instance to initialize.</param> /// <param name="options">The options instance to initialize.</param>
public void PostConfigure([CanBeNull] string name, [NotNull] AuthenticationOptions options) public void PostConfigure([CanBeNull] string name, [NotNull] AuthenticationOptions options)
{ {

6
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs

@ -81,12 +81,12 @@ namespace Microsoft.Extensions.DependencyInjection
} }
/// <summary> /// <summary>
/// Configures OpenIddict to use the Data Protection format when /// Configures OpenIddict to use the default token format (JWT) when
/// issuing new access tokens, refresh tokens and authorization codes. /// issuing new access tokens, refresh tokens and authorization codes.
/// </summary> /// </summary>
/// <returns>The <see cref="OpenIddictServerDataProtectionBuilder"/>.</returns> /// <returns>The <see cref="OpenIddictServerDataProtectionBuilder"/>.</returns>
public OpenIddictServerDataProtectionBuilder PreferDataProtectionFormat() public OpenIddictServerDataProtectionBuilder PreferDefaultTokenFormat()
=> Configure(options => options.PreferDataProtectionFormat = true); => Configure(options => options.PreferDefaultTokenFormat = true);
/// <summary> /// <summary>
/// Determines whether the specified object is equal to the current object. /// Determines whether the specified object is equal to the current object.

2
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs

@ -44,7 +44,7 @@ namespace OpenIddict.Server.DataProtection
/// Populates the default OpenIddict ASP.NET Core Data Protection server options /// Populates the default OpenIddict ASP.NET Core Data Protection server options
/// and ensures that the configuration is in a consistent and valid state. /// and ensures that the configuration is in a consistent and valid state.
/// </summary> /// </summary>
/// <param name="name">The authentication scheme associated with the handler instance.</param> /// <param name="name">The name of the options instance to configure, if applicable.</param>
/// <param name="options">The options instance to initialize.</param> /// <param name="options">The options instance to initialize.</param>
public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictServerDataProtectionOptions options) public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictServerDataProtectionOptions options)
{ {

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

@ -22,7 +22,8 @@ namespace Microsoft.Extensions.DependencyInjection
public static class OpenIddictServerDataProtectionExtensions public static class OpenIddictServerDataProtectionExtensions
{ {
/// <summary> /// <summary>
/// Registers the OpenIddict ASP.NET Core Data Protection server services in the DI container. /// Registers the OpenIddict ASP.NET Core Data Protection server services in the DI container
/// and configures OpenIddict to validate and issue ASP.NET Data Protection-based tokens.
/// </summary> /// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param> /// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <remarks>This extension can be safely called multiple times.</remarks> /// <remarks>This extension can be safely called multiple times.</remarks>
@ -41,7 +42,7 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor));
// Register the built-in filter used by the default OpenIddict Data Protection event handlers. // Register the built-in filter used by the default OpenIddict Data Protection event handlers.
builder.Services.TryAddSingleton<RequirePreferDataProtectionFormatEnabled>(); builder.Services.TryAddSingleton<RequireDataProtectionFormatEnabled>();
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(new[] builder.Services.TryAddEnumerable(new[]
@ -54,7 +55,8 @@ namespace Microsoft.Extensions.DependencyInjection
} }
/// <summary> /// <summary>
/// Registers the OpenIddict ASP.NET Core Data Protection server services in the DI container. /// Registers the OpenIddict ASP.NET Core Data Protection server services in the DI container
/// and configures OpenIddict to validate and issue ASP.NET Data Protection-based tokens.
/// </summary> /// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param> /// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <param name="configuration">The configuration delegate used to configure the server services.</param> /// <param name="configuration">The configuration delegate used to configure the server services.</param>

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

@ -22,11 +22,11 @@ namespace OpenIddict.Server.DataProtection
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if OpenIddict was not configured to issue Data Protection tokens. /// Represents a filter that excludes the associated handlers if OpenIddict was not configured to issue Data Protection tokens.
/// </summary> /// </summary>
public class RequirePreferDataProtectionFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext> public class RequireDataProtectionFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{ {
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options; private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public RequirePreferDataProtectionFormatEnabled([NotNull] IOptionsMonitor<OpenIddictServerDataProtectionOptions> options) public RequireDataProtectionFormatEnabled([NotNull] IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options; => _options = options;
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context) public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context)
@ -36,7 +36,7 @@ namespace OpenIddict.Server.DataProtection
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
return new ValueTask<bool>(_options.CurrentValue.PreferDataProtectionFormat); return new ValueTask<bool>(!_options.CurrentValue.PreferDefaultTokenFormat);
} }
} }
} }

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

@ -22,6 +22,7 @@ using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHand
using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters; using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using static OpenIddict.Server.OpenIddictServerHandlers; using static OpenIddict.Server.OpenIddictServerHandlers;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
using Schemes = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes.Schemes; using Schemes = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes.Schemes;
namespace OpenIddict.Server.DataProtection namespace OpenIddict.Server.DataProtection
@ -83,6 +84,14 @@ namespace OpenIddict.Server.DataProtection
return default; 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.
// As an optimization, always ignore tokens that don't start with the "CfDJ8" string.
if (string.IsNullOrEmpty(context.Token) || !context.Token.StartsWith("CfDJ8", StringComparison.Ordinal))
{
return default;
}
// If the token cannot be validated, don't return an error to allow another handle to validate it. // If the token cannot be validated, don't return an error to allow another handle to validate it.
var principal = !string.IsNullOrEmpty(context.TokenType) ? var principal = !string.IsNullOrEmpty(context.TokenType) ?
ValidateToken(context.Token, context.TokenType) : ValidateToken(context.Token, context.TokenType) :
@ -108,19 +117,19 @@ namespace OpenIddict.Server.DataProtection
// Create a Data Protection protector using the provider registered in the options. // Create a Data Protection protector using the provider registered in the options.
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(type switch
{ {
TokenTypeHints.AccessToken when context.Options.UseReferenceAccessTokens TokenTypeHints.AccessToken when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AuthorizationCode when !context.Options.DisableTokenStorage TokenTypeHints.AuthorizationCode when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.DeviceCode when !context.Options.DisableTokenStorage TokenTypeHints.DeviceCode when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.DeviceCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.RefreshToken when !context.Options.DisableTokenStorage TokenTypeHints.RefreshToken when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.UserCode when !context.Options.DisableTokenStorage TokenTypeHints.UserCode when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server }, => new[] { Handlers.Server, Formats.UserCode, Features.ReferenceTokens, Schemes.Server },
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server }, TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
@ -168,7 +177,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireAccessTokenIncluded>() .AddFilter<RequireAccessTokenIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>() .AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionAccessToken>() .UseSingletonHandler<GenerateDataProtectionAccessToken>()
.SetOrder(GenerateIdentityModelAccessToken.Descriptor.Order - 500) .SetOrder(GenerateIdentityModelAccessToken.Descriptor.Order - 500)
.Build(); .Build();
@ -194,7 +203,7 @@ namespace OpenIddict.Server.DataProtection
} }
// Create a Data Protection protector using the provider registered in the options. // Create a Data Protection protector using the provider registered in the options.
var protector = context.Options.UseReferenceAccessTokens ? var protector = context.Options.UseReferenceTokens ?
_options.CurrentValue.DataProtectionProvider.CreateProtector( _options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server) : Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector( _options.CurrentValue.DataProtectionProvider.CreateProtector(
@ -232,7 +241,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireAuthorizationCodeIncluded>() .AddFilter<RequireAuthorizationCodeIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>() .AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionAuthorizationCode>() .UseSingletonHandler<GenerateDataProtectionAuthorizationCode>()
.SetOrder(GenerateIdentityModelAuthorizationCode.Descriptor.Order - 500) .SetOrder(GenerateIdentityModelAuthorizationCode.Descriptor.Order - 500)
.Build(); .Build();
@ -258,7 +267,7 @@ namespace OpenIddict.Server.DataProtection
} }
// Create a Data Protection protector using the provider registered in the options. // Create a Data Protection protector using the provider registered in the options.
var protector = !context.Options.DisableTokenStorage ? var protector = context.Options.UseReferenceTokens ?
_options.CurrentValue.DataProtectionProvider.CreateProtector( _options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server) : Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector( _options.CurrentValue.DataProtectionProvider.CreateProtector(
@ -296,7 +305,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireDeviceCodeIncluded>() .AddFilter<RequireDeviceCodeIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>() .AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionDeviceCode>() .UseSingletonHandler<GenerateDataProtectionDeviceCode>()
.SetOrder(GenerateIdentityModelDeviceCode.Descriptor.Order - 500) .SetOrder(GenerateIdentityModelDeviceCode.Descriptor.Order - 500)
.Build(); .Build();
@ -360,7 +369,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireRefreshTokenIncluded>() .AddFilter<RequireRefreshTokenIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>() .AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionRefreshToken>() .UseSingletonHandler<GenerateDataProtectionRefreshToken>()
.SetOrder(GenerateIdentityModelRefreshToken.Descriptor.Order - 500) .SetOrder(GenerateIdentityModelRefreshToken.Descriptor.Order - 500)
.Build(); .Build();
@ -386,7 +395,7 @@ namespace OpenIddict.Server.DataProtection
} }
// Create a Data Protection protector using the provider registered in the options. // Create a Data Protection protector using the provider registered in the options.
var protector = !context.Options.DisableTokenStorage ? var protector = context.Options.UseReferenceTokens ?
_options.CurrentValue.DataProtectionProvider.CreateProtector( _options.CurrentValue.DataProtectionProvider.CreateProtector(
Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server) : Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector( _options.CurrentValue.DataProtectionProvider.CreateProtector(
@ -424,7 +433,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireUserCodeIncluded>() .AddFilter<RequireUserCodeIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>() .AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionUserCode>() .UseSingletonHandler<GenerateDataProtectionUserCode>()
.SetOrder(GenerateIdentityModelUserCode.Descriptor.Order - 500) .SetOrder(GenerateIdentityModelUserCode.Descriptor.Order - 500)
.Build(); .Build();

14
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs

@ -21,18 +21,18 @@ namespace OpenIddict.Server.DataProtection
/// </summary> /// </summary>
public IDataProtectionProvider DataProtectionProvider { get; set; } public IDataProtectionProvider DataProtectionProvider { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the Data Protection format
/// should be preferred when issuing new access tokens, refresh tokens
/// and authorization codes. This property is set to <c>false</c> by default.
/// </summary>
public bool PreferDataProtectionFormat { get; set; }
/// <summary> /// <summary>
/// Gets or sets the formatter used to read and write Data Protection tokens, /// Gets or sets the formatter used to read and write Data Protection tokens,
/// serialized using the same format as the ASP.NET Core authentication tickets. /// serialized using the same format as the ASP.NET Core authentication tickets.
/// </summary> /// </summary>
public IOpenIddictServerDataProtectionFormatter Formatter { get; set; } public IOpenIddictServerDataProtectionFormatter Formatter { get; set; }
= new OpenIddictServerDataProtectionFormatter(); = new OpenIddictServerDataProtectionFormatter();
/// <summary>
/// Gets or sets a boolean indicating whether the default token format
/// should be preferred when issuing new access tokens, refresh tokens
/// and authorization codes. This property is set to <c>false</c> by default.
/// </summary>
public bool PreferDefaultTokenFormat { get; set; }
} }
} }

24
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -1600,9 +1600,12 @@ namespace Microsoft.Extensions.DependencyInjection
=> Configure(options => options.UseSlidingExpiration = false); => Configure(options => options.UseSlidingExpiration = false);
/// <summary> /// <summary>
/// Disables token storage, so that authorization code and /// Disables token storage, so that no database entry is created
/// refresh tokens are never stored and cannot be revoked. /// for the tokens and codes returned by the OpenIddict server.
/// Using this option is generally NOT recommended. /// Using this option is generally NOT recommended as it prevents
/// the tokens and codes from being revoked (if needed).
/// Note: disabling token storage requires disabling sliding
/// expiration or enabling rolling tokens.
/// </summary> /// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns> /// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder DisableTokenStorage() public OpenIddictServerBuilder DisableTokenStorage()
@ -1790,19 +1793,20 @@ namespace Microsoft.Extensions.DependencyInjection
} }
/// <summary> /// <summary>
/// Configures OpenIddict to use reference tokens, so that access tokens are stored /// Configures OpenIddict to use reference tokens, so that the token and code payloads
/// as ciphertext in the database (only an identifier is returned to the client application). /// are stored in the database (only an identifier is returned to the client application).
/// Enabling this option is useful to keep track of all the issued tokens, when storing /// Enabling this option is useful when storing a very large number of claims in the tokens,
/// a very large number of claims in the access tokens or when immediate revocation is desired. /// but it is RECOMMENDED to enable column encryption in the database or use the ASP.NET Core
/// Data Protection integration, that provides additional protection against token leakage.
/// </summary> /// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns> /// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder UseReferenceAccessTokens() public OpenIddictServerBuilder UseReferenceTokens()
=> Configure(options => options.UseReferenceAccessTokens = true); => Configure(options => options.UseReferenceTokens = true);
/// <summary> /// <summary>
/// Configures OpenIddict to use rolling refresh tokens. When this option is enabled, /// Configures OpenIddict to use rolling refresh tokens. When this option is enabled,
/// a new refresh token is always issued for each refresh token request (and the previous /// a new refresh token is always issued for each refresh token request (and the previous
/// one is automatically revoked unless token revocation was explicitly disabled). /// one is automatically revoked unless token storage was explicitly disabled).
/// </summary> /// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns> /// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder UseRollingTokens() public OpenIddictServerBuilder UseRollingTokens()

4
src/OpenIddict.Server/OpenIddictServerConfiguration.cs

@ -26,7 +26,7 @@ namespace OpenIddict.Server
/// Populates the default OpenIddict server options and ensures /// Populates the default OpenIddict server options and ensures
/// that the configuration is in a consistent and valid state. /// that the configuration is in a consistent and valid state.
/// </summary> /// </summary>
/// <param name="name">The authentication scheme associated with the handler instance.</param> /// <param name="name">The name of the options instance to configure, if applicable.</param>
/// <param name="options">The options instance to initialize.</param> /// <param name="options">The options instance to initialize.</param>
public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictServerOptions options) public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictServerOptions options)
{ {
@ -96,7 +96,7 @@ namespace OpenIddict.Server
throw new InvalidOperationException("The revocation endpoint cannot be enabled when token storage is disabled."); throw new InvalidOperationException("The revocation endpoint cannot be enabled when token storage is disabled.");
} }
if (options.UseReferenceAccessTokens) if (options.UseReferenceTokens)
{ {
throw new InvalidOperationException("Reference tokens cannot be used when disabling token storage."); throw new InvalidOperationException("Reference tokens cannot be used when disabling token storage.");
} }

2
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -53,7 +53,7 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.TryAddSingleton<RequireGrantTypePermissionsEnabled>(); builder.Services.TryAddSingleton<RequireGrantTypePermissionsEnabled>();
builder.Services.TryAddSingleton<RequireIdentityTokenIncluded>(); builder.Services.TryAddSingleton<RequireIdentityTokenIncluded>();
builder.Services.TryAddSingleton<RequirePostLogoutRedirectUriParameter>(); builder.Services.TryAddSingleton<RequirePostLogoutRedirectUriParameter>();
builder.Services.TryAddSingleton<RequireReferenceAccessTokensEnabled>(); builder.Services.TryAddSingleton<RequireReferenceTokensEnabled>();
builder.Services.TryAddSingleton<RequireRefreshTokenIncluded>(); builder.Services.TryAddSingleton<RequireRefreshTokenIncluded>();
builder.Services.TryAddSingleton<RequireRollingTokensDisabled>(); builder.Services.TryAddSingleton<RequireRollingTokensDisabled>();
builder.Services.TryAddSingleton<RequireRollingTokensEnabled>(); builder.Services.TryAddSingleton<RequireRollingTokensEnabled>();

6
src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs

@ -176,9 +176,9 @@ namespace OpenIddict.Server
} }
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if reference access tokens are disabled. /// Represents a filter that excludes the associated handlers if reference tokens are disabled.
/// </summary> /// </summary>
public class RequireReferenceAccessTokensEnabled : IOpenIddictServerHandlerFilter<BaseContext> public class RequireReferenceTokensEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{ {
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context) public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context)
{ {
@ -187,7 +187,7 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
return new ValueTask<bool>(context.Options.UseReferenceAccessTokens); return new ValueTask<bool>(context.Options.UseReferenceTokens);
} }
} }

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

@ -797,18 +797,6 @@ namespace OpenIddict.Server
return default; return default;
} }
// If the received token is an access token, return an error if reference tokens are not enabled.
if (!context.Options.UseReferenceAccessTokens && context.Principal.HasTokenType(TokenTypeHints.AccessToken))
{
context.Logger.LogError("The revocation request was rejected because the access token was not revocable.");
context.Reject(
error: Errors.UnsupportedTokenType,
description: "The specified token cannot be revoked.");
return default;
}
return default; return default;
} }
} }

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

@ -472,23 +472,13 @@ namespace OpenIddict.Server
// To be considered valid, a post_logout_redirect_uri must correspond to an existing client application // To be considered valid, a post_logout_redirect_uri must correspond to an existing client application
// that was granted the ept:logout permission, unless endpoint permissions checking was explicitly disabled. // that was granted the ept:logout permission, unless endpoint permissions checking was explicitly disabled.
await using var enumerator = _applicationManager.FindByPostLogoutRedirectUriAsync(address).GetAsyncEnumerator(); await foreach (var application in _applicationManager.FindByPostLogoutRedirectUriAsync(address))
if (await enumerator.MoveNextAsync())
{ {
if (context.Options.IgnoreEndpointPermissions) if (context.Options.IgnoreEndpointPermissions ||
await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Logout))
{ {
return true; return true;
} }
do
{
if (await _applicationManager.HasPermissionAsync(enumerator.Current, Permissions.Endpoints.Logout))
{
return true;
}
}
while (await enumerator.MoveNextAsync());
} }
return false; return false;

1193
src/OpenIddict.Server/OpenIddictServerHandlers.cs

File diff suppressed because it is too large

27
src/OpenIddict.Server/OpenIddictServerOptions.cs

@ -27,14 +27,16 @@ namespace OpenIddict.Server
/// <summary> /// <summary>
/// Gets the list of credentials used to encrypt the tokens issued by the /// Gets the list of credentials used to encrypt the tokens issued by the
/// OpenIddict server services. Note: only symmetric credentials are supported. /// OpenIddict server services. Note: the encryption credentials are not
/// used to protect/unprotect tokens issued by ASP.NET Core Data Protection.
/// </summary> /// </summary>
public IList<EncryptingCredentials> EncryptionCredentials { get; } = new List<EncryptingCredentials>(); public IList<EncryptingCredentials> EncryptionCredentials { get; } = new List<EncryptingCredentials>();
/// <summary> /// <summary>
/// Gets the list of credentials used to sign the tokens issued by the OpenIddict server services. /// Gets the list of credentials used to sign the tokens issued by the OpenIddict server services.
/// Both asymmetric and symmetric keys are supported, but only asymmetric keys can be used to sign identity tokens. /// Both asymmetric and symmetric keys are supported, but only asymmetric keys can be used to sign identity tokens.
/// Note that only asymmetric RSA and ECDSA keys can be exposed by the JWKS metadata endpoint. /// Note that only asymmetric RSA and ECDSA keys can be exposed by the JWKS metadata endpoint and that the
/// signing credentials are not used to protect/unprotect tokens issued by ASP.NET Core Data Protection.
/// </summary> /// </summary>
public IList<SigningCredentials> SigningCredentials { get; } = new List<SigningCredentials>(); public IList<SigningCredentials> SigningCredentials { get; } = new List<SigningCredentials>();
@ -222,6 +224,7 @@ namespace OpenIddict.Server
/// Gets or sets a boolean indicating whether access token encryption should be disabled. /// Gets or sets a boolean indicating whether access token encryption should be disabled.
/// Disabling encryption is NOT recommended and SHOULD only be done when issuing tokens /// Disabling encryption is NOT recommended and SHOULD only be done when issuing tokens
/// to third-party resource servers/APIs you don't control and don't fully trust. /// to third-party resource servers/APIs you don't control and don't fully trust.
/// Note: disabling encryption has no effect when using ASP.NET Core Data Protection.
/// </summary> /// </summary>
public bool DisableAccessTokenEncryption { get; set; } public bool DisableAccessTokenEncryption { get; set; }
@ -234,8 +237,9 @@ namespace OpenIddict.Server
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether token storage should be disabled. /// Gets or sets a boolean indicating whether token storage should be disabled.
/// When disabled, authorization code and refresh tokens are not stored /// When disabled, no database entry is created for the tokens and codes
/// and cannot be revoked. Using this option is generally not recommended. /// returned by OpenIddict. Using this option is generally NOT recommended
/// as it prevents the tokens and codes from being revoked (if needed).
/// </summary> /// </summary>
public bool DisableTokenStorage { get; set; } public bool DisableTokenStorage { get; set; }
@ -307,14 +311,15 @@ namespace OpenIddict.Server
}; };
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether reference access tokens should be used. /// Gets or sets a boolean indicating whether reference tokens should be used.
/// When set to <c>true</c>, access tokens and are stored as ciphertext in the database /// When set to <c>true</c>, token and code payloads are stored in the database
/// and a crypto-secure random identifier is returned to the client application. /// and a crypto-secure random identifier is returned to the client application.
/// Enabling this option is useful to keep track of all the issued access tokens, /// Enabling this option is useful when storing a very large number of claims
/// when storing a very large number of claims in the access tokens /// in the tokens, but it is RECOMMENDED to enable column encryption
/// or when immediate revocation of reference access tokens is desired. /// in the database or use the ASP.NET Core Data Protection integration,
/// that provides additional protection against token leakage.
/// </summary> /// </summary>
public bool UseReferenceAccessTokens { get; set; } public bool UseReferenceTokens { get; set; }
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether rolling tokens should be used. /// Gets or sets a boolean indicating whether rolling tokens should be used.
@ -322,7 +327,7 @@ namespace OpenIddict.Server
/// dynamically managed by updating the token entry in the database. /// dynamically managed by updating the token entry in the database.
/// When this option is enabled, a new refresh token is issued for each /// When this option is enabled, a new refresh token is issued for each
/// refresh token request (and the previous one is automatically revoked /// refresh token request (and the previous one is automatically revoked
/// unless token revocation was explicitly disabled in the options). /// unless token storage was explicitly disabled in the options).
/// </summary> /// </summary>
public bool UseRollingTokens { get; set; } public bool UseRollingTokens { get; set; }
} }

2
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs

@ -61,7 +61,7 @@ namespace OpenIddict.Validation.AspNetCore
/// <summary> /// <summary>
/// Ensures that the authentication configuration is in a consistent and valid state. /// Ensures that the authentication configuration is in a consistent and valid state.
/// </summary> /// </summary>
/// <param name="name">The authentication scheme associated with the handler instance.</param> /// <param name="name">The name of the options instance to configure, if applicable.</param>
/// <param name="options">The options instance to initialize.</param> /// <param name="options">The options instance to initialize.</param>
public void PostConfigure([CanBeNull] string name, [NotNull] AuthenticationOptions options) public void PostConfigure([CanBeNull] string name, [NotNull] AuthenticationOptions options)
{ {

2
src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs

@ -45,7 +45,7 @@ namespace OpenIddict.Validation.DataProtection
/// Populates the default OpenIddict ASP.NET Core Data Protection validation options /// Populates the default OpenIddict ASP.NET Core Data Protection validation options
/// and ensures that the configuration is in a consistent and valid state. /// and ensures that the configuration is in a consistent and valid state.
/// </summary> /// </summary>
/// <param name="name">The authentication scheme associated with the handler instance.</param> /// <param name="name">The name of the options instance to configure, if applicable.</param>
/// <param name="options">The options instance to initialize.</param> /// <param name="options">The options instance to initialize.</param>
public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictValidationDataProtectionOptions options) public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictValidationDataProtectionOptions options)
{ {

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

@ -19,6 +19,7 @@ using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants; using static OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants;
using static OpenIddict.Validation.OpenIddictValidationEvents; using static OpenIddict.Validation.OpenIddictValidationEvents;
using static OpenIddict.Validation.OpenIddictValidationHandlers; using static OpenIddict.Validation.OpenIddictValidationHandlers;
using Properties = OpenIddict.Validation.OpenIddictValidationConstants.Properties;
namespace OpenIddict.Validation.DataProtection namespace OpenIddict.Validation.DataProtection
{ {
@ -70,8 +71,16 @@ namespace OpenIddict.Validation.DataProtection
return default; 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.
// As an optimization, always ignore tokens that don't start with the "CfDJ8" string.
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. // Create a Data Protection protector using the provider registered in the options.
var protector = context.Options.UseReferenceAccessTokens ? var protector = context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier) ?
_options.CurrentValue.DataProtectionProvider.CreateProtector( _options.CurrentValue.DataProtectionProvider.CreateProtector(
Purposes.Handlers.Server, Purposes.Formats.AccessToken, Purposes.Handlers.Server, Purposes.Formats.AccessToken,
Purposes.Features.ReferenceTokens, Purposes.Schemes.Server) : Purposes.Features.ReferenceTokens, Purposes.Schemes.Server) :

38
src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs

@ -6,6 +6,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Text;
using JetBrains.Annotations; using JetBrains.Annotations;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using OpenIddict.Server; using OpenIddict.Server;
@ -15,7 +16,8 @@ namespace OpenIddict.Validation.ServerIntegration
/// <summary> /// <summary>
/// Contains the methods required to ensure that the OpenIddict validation/server integration configuration is valid. /// Contains the methods required to ensure that the OpenIddict validation/server integration configuration is valid.
/// </summary> /// </summary>
public class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions<OpenIddictValidationOptions> public class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions<OpenIddictValidationOptions>,
IPostConfigureOptions<OpenIddictValidationOptions>
{ {
private readonly IOptionsMonitor<OpenIddictServerOptions> _options; private readonly IOptionsMonitor<OpenIddictServerOptions> _options;
@ -53,7 +55,39 @@ namespace OpenIddict.Validation.ServerIntegration
options.EncryptionCredentials.Add(credentials); options.EncryptionCredentials.Add(credentials);
} }
options.UseReferenceAccessTokens = _options.CurrentValue.UseReferenceAccessTokens; // Note: token validation must be enabled to be able to validate reference tokens.
options.EnableTokenValidation = _options.CurrentValue.UseReferenceTokens;
}
/// <summary>
/// Populates the default OpenIddict validation/server integration options
/// and ensures that the configuration is in a consistent and valid state.
/// </summary>
/// <param name="name">The name of the options instance to configure, if applicable.</param>
/// <param name="options">The options instance to initialize.</param>
public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictValidationOptions options)
{
// Note: authorization validation requires that authorizations have an entry
// in the database (containing at least the authorization metadata), which is
// not created if the authorization storage is disabled in the server options.
if (options.EnableAuthorizationValidation && _options.CurrentValue.DisableAuthorizationStorage)
{
throw new InvalidOperationException(new StringBuilder()
.Append("Authorization validation cannot be enabled when authorization ")
.Append("storage is disabled in the OpenIddict server options.")
.ToString());
}
// Note: token validation requires that tokens have an entry in the database
// (containing at least the token metadata), which is not created if the
// token storage is disabled in the OpenIddict server options.
if (options.EnableTokenValidation && _options.CurrentValue.DisableTokenStorage)
{
throw new InvalidOperationException(new StringBuilder()
.Append("Token validation cannot be enabled when token storage ")
.Append("is disabled in the OpenIddict server options.")
.ToString());
}
} }
} }
} }

9
src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs

@ -32,9 +32,12 @@ namespace Microsoft.Extensions.DependencyInjection
throw new ArgumentNullException(nameof(builder)); throw new ArgumentNullException(nameof(builder));
} }
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< builder.Services.TryAddEnumerable(new[]
IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>()); {
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>(),
ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>()
});
return new OpenIddictValidationServerIntegrationBuilder(builder.Services); return new OpenIddictValidationServerIntegrationBuilder(builder.Services);
} }

23
src/OpenIddict.Validation/OpenIddictValidationBuilder.cs

@ -407,12 +407,23 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary> /// <summary>
/// Enables authorization validation so that a database call is made for each API request /// Enables authorization validation so that a database call is made for each API request
/// to ensure the authorization associated with the access token is still valid. /// to ensure the authorization associated with the access token is still valid.
/// Note: enabling this option may have an impact on performance. /// Note: enabling this option may have an impact on performance and
/// can only be used with an OpenIddict-based authorization server.
/// </summary> /// </summary>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns> /// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
public OpenIddictValidationBuilder EnableAuthorizationValidation() public OpenIddictValidationBuilder EnableAuthorizationValidation()
=> Configure(options => options.EnableAuthorizationValidation = true); => Configure(options => options.EnableAuthorizationValidation = true);
/// <summary>
/// Enables token validation so that a database call is made for each API request
/// to ensure the token entry associated with the access token is still valid.
/// Note: enabling this option may have an impact on performance but is required
/// when the OpenIddict server is configured to use reference tokens.
/// </summary>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
public OpenIddictValidationBuilder EnableTokenValidation()
=> Configure(options => options.EnableTokenValidation = true);
/// <summary> /// <summary>
/// Sets the issuer address, which is used to determine the actual location of the /// Sets the issuer address, which is used to determine the actual location of the
/// OAuth 2.0/OpenID Connect configuration document when using provider discovery. /// OAuth 2.0/OpenID Connect configuration document when using provider discovery.
@ -444,16 +455,6 @@ namespace Microsoft.Extensions.DependencyInjection
return Configure(options => configuration(options.TokenValidationParameters)); return Configure(options => configuration(options.TokenValidationParameters));
} }
/// <summary>
/// Configures OpenIddict to use reference tokens, so that access tokens are stored
/// as ciphertext in the database (only an identifier is returned to the client application).
/// Enabling this option is useful to keep track of all the issued tokens, when storing
/// a very large number of claims in the access tokens or when immediate revocation is desired.
/// </summary>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
public OpenIddictValidationBuilder UseReferenceAccessTokens()
=> Configure(options => options.UseReferenceAccessTokens = true);
/// <summary> /// <summary>
/// Determines whether the specified object is equal to the current object. /// Determines whether the specified object is equal to the current object.
/// </summary> /// </summary>

2
src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs

@ -22,7 +22,7 @@ namespace OpenIddict.Validation
/// Populates the default OpenIddict validation options and ensures /// Populates the default OpenIddict validation options and ensures
/// that the configuration is in a consistent and valid state. /// that the configuration is in a consistent and valid state.
/// </summary> /// </summary>
/// <param name="name">The authentication scheme associated with the handler instance.</param> /// <param name="name">The name of the options instance to configure, if applicable.</param>
/// <param name="options">The options instance to initialize.</param> /// <param name="options">The options instance to initialize.</param>
public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictValidationOptions options) public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictValidationOptions options)
{ {

2
src/OpenIddict.Validation/OpenIddictValidationExtensions.cs

@ -44,7 +44,7 @@ namespace Microsoft.Extensions.DependencyInjection
// Register the built-in filters used by the default OpenIddict validation event handlers. // Register the built-in filters used by the default OpenIddict validation event handlers.
builder.Services.TryAddSingleton<RequireAuthorizationValidationEnabled>(); builder.Services.TryAddSingleton<RequireAuthorizationValidationEnabled>();
builder.Services.TryAddSingleton<RequireReferenceAccessTokensEnabled>(); builder.Services.TryAddSingleton<RequireTokenValidationEnabled>();
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<

6
src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs

@ -32,9 +32,9 @@ namespace OpenIddict.Validation
} }
/// <summary> /// <summary>
/// Represents a filter that excludes the associated handlers if reference access tokens are disabled. /// Represents a filter that excludes the associated handlers if authorization validation was not enabled.
/// </summary> /// </summary>
public class RequireReferenceAccessTokensEnabled : IOpenIddictValidationHandlerFilter<BaseContext> public class RequireTokenValidationEnabled : IOpenIddictValidationHandlerFilter<BaseContext>
{ {
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context) public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context)
{ {
@ -43,7 +43,7 @@ namespace OpenIddict.Validation
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
return new ValueTask<bool>(context.Options.UseReferenceAccessTokens); return new ValueTask<bool>(context.Options.EnableTokenValidation);
} }
} }
} }

78
src/OpenIddict.Validation/OpenIddictValidationHandlers.cs

@ -36,6 +36,7 @@ namespace OpenIddict.Validation
ValidatePrincipal.Descriptor, ValidatePrincipal.Descriptor,
ValidateExpirationDate.Descriptor, ValidateExpirationDate.Descriptor,
ValidateAudience.Descriptor, ValidateAudience.Descriptor,
ValidateTokenEntry.Descriptor,
ValidateAuthorizationEntry.Descriptor, ValidateAuthorizationEntry.Descriptor,
/* /*
@ -110,7 +111,7 @@ namespace OpenIddict.Validation
/// </summary> /// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireReferenceAccessTokensEnabled>() .AddFilter<RequireTokenValidationEnabled>()
.UseScopedHandler<ValidateReferenceTokenIdentifier>() .UseScopedHandler<ValidateReferenceTokenIdentifier>()
.SetOrder(ValidateAccessTokenParameter.Descriptor.Order + 1_000) .SetOrder(ValidateAccessTokenParameter.Descriptor.Order + 1_000)
.Build(); .Build();
@ -122,7 +123,14 @@ namespace OpenIddict.Validation
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
// If the reference token cannot be found, don't return an error to allow another handle to validate it. // 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); var token = await _tokenManager.FindByReferenceIdAsync(context.Token);
if (token == null) if (token == null)
{ {
@ -313,7 +321,7 @@ namespace OpenIddict.Validation
/// </summary> /// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireReferenceAccessTokensEnabled>() .AddFilter<RequireTokenValidationEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>() .UseScopedHandler<RestoreReferenceTokenProperties>()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000) .SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.Build(); .Build();
@ -523,6 +531,68 @@ namespace OpenIddict.Validation
} }
} }
/// <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(new StringBuilder()
.AppendLine("The core services must be registered when enabling reference tokens support.")
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ")
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.")
.ToString());
public ValidateTokenEntry([NotNull] IOpenIddictTokenManager tokenManager)
=> _tokenManager = tokenManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireTokenValidationEnabled>()
.UseScopedHandler<ValidateTokenEntry>()
.SetOrder(ValidateAudience.Descriptor.Order + 1_000)
.Build();
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var identifier = context.Principal.GetInternalTokenId();
if (string.IsNullOrEmpty(identifier))
{
return;
}
var token = await _tokenManager.FindByIdAsync(identifier);
if (token == null || !await _tokenManager.HasStatusAsync(token, Statuses.Valid))
{
context.Logger.LogError("The token '{Identifier}' was no longer valid.", identifier);
context.Reject(
error: Errors.InvalidToken,
description: "The token is no longer valid.");
return;
}
// Restore the creation/expiration dates/identifiers from the token entry metadata.
context.Principal.SetCreationDate(await _tokenManager.GetCreationDateAsync(token))
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token))
.SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token))
.SetInternalTokenId(await _tokenManager.GetIdAsync(token))
.SetTokenType(await _tokenManager.GetTypeAsync(token));
}
}
/// <summary> /// <summary>
/// Contains the logic responsible of authentication demands a token whose /// Contains the logic responsible of authentication demands a token whose
/// associated authorization entry is no longer valid (e.g was revoked). /// associated authorization entry is no longer valid (e.g was revoked).
@ -548,7 +618,7 @@ namespace OpenIddict.Validation
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthorizationValidationEnabled>() .AddFilter<RequireAuthorizationValidationEnabled>()
.UseScopedHandler<ValidateAuthorizationEntry>() .UseScopedHandler<ValidateAuthorizationEntry>()
.SetOrder(ValidateAudience.Descriptor.Order + 1_000) .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
.Build(); .Build();
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context) public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)

16
src/OpenIddict.Validation/OpenIddictValidationOptions.cs

@ -46,19 +46,19 @@ namespace OpenIddict.Validation
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether a database call is made /// Gets or sets a boolean indicating whether a database call is made
/// to validate the authorization associated with the received tokens. /// to validate the authorization entry associated with the received tokens.
/// Note: enabling this option may have an impact on performance and
/// can only be used with an OpenIddict-based authorization server.
/// </summary> /// </summary>
public bool EnableAuthorizationValidation { get; set; } public bool EnableAuthorizationValidation { get; set; }
/// <summary> /// <summary>
/// Gets or sets a boolean indicating whether reference access tokens should be used. /// Gets or sets a boolean indicating whether a database call is made
/// When set to <c>true</c>, access tokens and are stored as ciphertext in the database /// to validate the token entry associated with the received tokens.
/// and a crypto-secure random identifier is returned to the client application. /// Note: enabling this option may have an impact on performance but
/// Enabling this option is useful to keep track of all the issued access tokens, /// is required when the OpenIddict server emits reference tokens.
/// when storing a very large number of claims in the access tokens
/// or when immediate revocation of reference access tokens is desired.
/// </summary> /// </summary>
public bool UseReferenceAccessTokens { get; set; } public bool EnableTokenValidation { get; set; }
/// <summary> /// <summary>
/// Gets or sets the absolute URL of the OAuth 2.0/OpenID Connect server. /// Gets or sets the absolute URL of the OAuth 2.0/OpenID Connect server.

16
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs

@ -589,6 +589,9 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.RegisterScopes("registered_scope"); options.RegisterScopes("registered_scope");
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.DisableTokenStorage();
options.DisableSlidingExpiration();
options.Services.AddSingleton(CreateApplicationManager(mock => options.Services.AddSingleton(CreateApplicationManager(mock =>
{ {
@ -654,6 +657,9 @@ namespace OpenIddict.Server.FunctionalTests
var scope = new OpenIddictScope(); var scope = new OpenIddictScope();
options.RegisterScopes("scope_registered_in_options"); options.RegisterScopes("scope_registered_in_options");
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.DisableTokenStorage();
options.DisableSlidingExpiration();
options.Services.AddSingleton(CreateApplicationManager(mock => options.Services.AddSingleton(CreateApplicationManager(mock =>
{ {
@ -1347,6 +1353,11 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.DisableAuthorizationStorage();
options.DisableTokenStorage();
options.DisableSlidingExpiration();
options.Services.AddSingleton(manager); options.Services.AddSingleton(manager);
options.AddEventHandler<HandleAuthorizationRequestContext>(builder => options.AddEventHandler<HandleAuthorizationRequestContext>(builder =>
@ -1398,6 +1409,11 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.DisableAuthorizationStorage();
options.DisableTokenStorage();
options.DisableSlidingExpiration();
options.Services.AddSingleton(manager); options.Services.AddSingleton(manager);
options.AddEventHandler<HandleAuthorizationRequestContext>(builder => options.AddEventHandler<HandleAuthorizationRequestContext>(builder =>

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

@ -1142,6 +1142,9 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.RegisterScopes("scope_registered_in_options"); options.RegisterScopes("scope_registered_in_options");
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.DisableTokenStorage();
options.DisableSlidingExpiration();
options.Services.AddSingleton(manager); options.Services.AddSingleton(manager);
@ -2832,6 +2835,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
})); }));
options.Services.AddSingleton(manager); options.Services.AddSingleton(manager);
@ -2914,6 +2920,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
})); }));
options.Services.AddSingleton(manager); options.Services.AddSingleton(manager);
@ -3309,6 +3318,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>

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

@ -1163,7 +1163,7 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.UseReferenceAccessTokens(); options.UseReferenceTokens();
options.Services.AddSingleton(CreateApplicationManager(mock => options.Services.AddSingleton(CreateApplicationManager(mock =>
{ {
@ -1273,7 +1273,7 @@ namespace OpenIddict.Server.FunctionalTests
options.Services.AddSingleton(manager); options.Services.AddSingleton(manager);
options.DisableAuthorizationStorage(); options.DisableAuthorizationStorage();
options.UseReferenceAccessTokens(); options.UseReferenceTokens();
}); });
// Act // Act
@ -1363,7 +1363,7 @@ namespace OpenIddict.Server.FunctionalTests
options.Services.AddSingleton(manager); options.Services.AddSingleton(manager);
options.UseReferenceAccessTokens(); options.UseReferenceTokens();
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
}); });
@ -1461,7 +1461,7 @@ namespace OpenIddict.Server.FunctionalTests
options.Services.AddSingleton(manager); options.Services.AddSingleton(manager);
options.UseReferenceAccessTokens(); options.UseReferenceTokens();
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
}); });
@ -1546,7 +1546,7 @@ namespace OpenIddict.Server.FunctionalTests
options.Services.AddSingleton(manager); options.Services.AddSingleton(manager);
options.UseReferenceAccessTokens(); options.UseReferenceTokens();
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor); options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
}); });

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

@ -144,47 +144,6 @@ namespace OpenIddict.Server.FunctionalTests
Assert.Equal("The mandatory 'token' parameter is missing.", response.ErrorDescription); Assert.Equal("The mandatory 'token' parameter is missing.", response.ErrorDescription);
} }
[Fact]
public async Task ValidateRevocationRequest_AccessTokenCausesAnUnsupportedTokenTypeErrorWhenReferenceTokensAreDisabled()
{
// Arrange
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
Assert.Equal("2YotnFZFEjr1zCsicMWpAA", context.Token);
context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
.SetTokenType(TokenTypeHints.AccessToken)
.SetAudiences("AdventureWorks")
.SetPresenters("Contoso");
return default;
});
builder.SetOrder(ValidateIdentityModelToken.Descriptor.Order - 500);
});
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
// Act
var response = await client.PostAsync("/connect/revoke", new OpenIddictRequest
{
ClientId = "Fabrikam",
Token = "2YotnFZFEjr1zCsicMWpAA",
TokenTypeHint = TokenTypeHints.AccessToken
});
// Assert
Assert.Equal(Errors.UnsupportedTokenType, response.Error);
Assert.Equal("The specified token cannot be revoked.", response.ErrorDescription);
}
[Fact] [Fact]
public async Task ValidateRevocationRequest_IdentityTokenCausesAnUnsupportedTokenTypeError() public async Task ValidateRevocationRequest_IdentityTokenCausesAnUnsupportedTokenTypeError()
{ {
@ -272,7 +231,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.EnableDegradedMode(); options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {
@ -570,7 +528,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.EnableDegradedMode(); options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {
@ -615,7 +572,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.EnableDegradedMode(); options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {
@ -663,7 +619,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.EnableDegradedMode(); options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {
@ -878,7 +833,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.EnableDegradedMode(); options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {
@ -923,7 +877,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.EnableDegradedMode(); options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {
@ -971,7 +924,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.EnableDegradedMode(); options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {
@ -1014,7 +966,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.EnableDegradedMode(); options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {

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

@ -2649,6 +2649,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
@ -2798,11 +2801,15 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.UseRollingTokens(); options.UseRollingTokens();
options.DisableAuthorizationStorage();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {
@ -2867,6 +2874,7 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options => var client = CreateClient(options =>
{ {
options.UseRollingTokens(); options.UseRollingTokens();
options.DisableAuthorizationStorage();
options.AddEventHandler<ProcessAuthenticationContext>(builder => options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{ {
@ -2921,6 +2929,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
@ -3000,6 +3011,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>())) mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.Returns(tokens.ToAsyncEnumerable()); .Returns(tokens.ToAsyncEnumerable());
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
@ -3089,6 +3103,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>())) mock.Setup(manager => manager.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.Returns(tokens.ToAsyncEnumerable()); .Returns(tokens.ToAsyncEnumerable());
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
@ -3163,6 +3180,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
@ -3219,6 +3239,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>()))
.ReturnsAsync(true); .ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
@ -3280,6 +3303,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null); .ReturnsAsync(value: null);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
@ -3341,6 +3367,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny<CancellationToken>())) mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(DateTimeOffset.Now + TimeSpan.FromDays(1)); .ReturnsAsync(DateTimeOffset.Now + TimeSpan.FromDays(1));
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
@ -3404,6 +3433,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.TryExtendAsync(token, It.IsAny<DateTimeOffset?>(), It.IsAny<CancellationToken>())) mock.Setup(manager => manager.TryExtendAsync(token, It.IsAny<DateTimeOffset?>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false); .ReturnsAsync(false);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
}); });
var client = CreateClient(options => var client = CreateClient(options =>
@ -3454,6 +3486,9 @@ namespace OpenIddict.Server.FunctionalTests
{ {
mock.Setup(manager => manager.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>())) mock.Setup(manager => manager.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization()); .ReturnsAsync(new OpenIddictAuthorization());
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictAuthorizationDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization());
}); });
var client = CreateClient(options => var client = CreateClient(options =>

Loading…
Cancel
Save