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.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
@ -128,6 +127,15 @@ namespace Mvc.Server
// Register the ASP.NET Core host.
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>();
@ -146,16 +154,6 @@ namespace Mvc.Server
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.UseAuthentication();

4
samples/Mvc.Server/Worker.cs

@ -15,8 +15,8 @@ namespace Mvc.Server
{
private readonly IServiceProvider _serviceProvider;
public Worker(IServiceProvider serviceScopeFactory)
=> _serviceProvider = serviceScopeFactory;
public Worker(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
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);
return type == typeof(OpenIddictMessage) ? new OpenIddictMessage(document.RootElement.Clone()) :
type == typeof(OpenIddictRequest) ? (OpenIddictMessage) new OpenIddictRequest(document.RootElement.Clone()) :
type == typeof(OpenIddictResponse) ? new OpenIddictResponse(document.RootElement.Clone()) :
return type == typeof(OpenIddictMessage) ? new OpenIddictMessage(document.RootElement.Clone()) :
type == typeof(OpenIddictRequest) ? new OpenIddictRequest(document.RootElement.Clone()) :
type == typeof(OpenIddictResponse) ? (OpenIddictMessage) new OpenIddictResponse(document.RootElement.Clone()) :
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))
{
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
// 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.
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
// 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.
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
=> string.IsNullOrEmpty(value.GetString()),
JsonElement value when value.ValueKind == JsonValueKind.Array => value.GetArrayLength() == 0,
JsonElement value when value.ValueKind == JsonValueKind.Object => IsEmptyNode(value),
JsonElement value when value.ValueKind == JsonValueKind.Array
=> value.GetArrayLength() == 0,
JsonElement value when value.ValueKind == JsonValueKind.Object
=> IsEmptyNode(value),
_ => 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.");
}
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)))
{
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>
/// Ensures that the authentication configuration is in a consistent and valid state.
/// </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>
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>
/// 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.
/// </summary>
/// <returns>The <see cref="OpenIddictServerDataProtectionBuilder"/>.</returns>
public OpenIddictServerDataProtectionBuilder PreferDataProtectionFormat()
=> Configure(options => options.PreferDataProtectionFormat = true);
public OpenIddictServerDataProtectionBuilder PreferDefaultTokenFormat()
=> Configure(options => options.PreferDefaultTokenFormat = true);
/// <summary>
/// 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
/// and ensures that the configuration is in a consistent and valid state.
/// </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>
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
{
/// <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>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <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));
// 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.
builder.Services.TryAddEnumerable(new[]
@ -54,7 +55,8 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <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>
/// <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>

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

@ -22,11 +22,11 @@ namespace OpenIddict.Server.DataProtection
/// <summary>
/// Represents a filter that excludes the associated handlers if OpenIddict was not configured to issue Data Protection tokens.
/// </summary>
public class RequirePreferDataProtectionFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext>
public class RequireDataProtectionFormatEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
private readonly IOptionsMonitor<OpenIddictServerDataProtectionOptions> _options;
public RequirePreferDataProtectionFormatEnabled([NotNull] IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
public RequireDataProtectionFormatEnabled([NotNull] IOptionsMonitor<OpenIddictServerDataProtectionOptions> options)
=> _options = options;
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context)
@ -36,7 +36,7 @@ namespace OpenIddict.Server.DataProtection
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.OpenIddictServerHandlerFilters;
using static OpenIddict.Server.OpenIddictServerHandlers;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
using Schemes = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Purposes.Schemes;
namespace OpenIddict.Server.DataProtection
@ -83,6 +84,14 @@ namespace OpenIddict.Server.DataProtection
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.
var principal = !string.IsNullOrEmpty(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.
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 },
TokenTypeHints.AuthorizationCode when !context.Options.DisableTokenStorage
TokenTypeHints.AuthorizationCode when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> 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 },
TokenTypeHints.RefreshToken when !context.Options.DisableTokenStorage
TokenTypeHints.RefreshToken when context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier)
=> 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 },
TokenTypeHints.AccessToken => new[] { Handlers.Server, Formats.AccessToken, Schemes.Server },
@ -168,7 +177,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireAccessTokenIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>()
.AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionAccessToken>()
.SetOrder(GenerateIdentityModelAccessToken.Descriptor.Order - 500)
.Build();
@ -194,7 +203,7 @@ namespace OpenIddict.Server.DataProtection
}
// 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(
Handlers.Server, Formats.AccessToken, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector(
@ -232,7 +241,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireAuthorizationCodeIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>()
.AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionAuthorizationCode>()
.SetOrder(GenerateIdentityModelAuthorizationCode.Descriptor.Order - 500)
.Build();
@ -258,7 +267,7 @@ namespace OpenIddict.Server.DataProtection
}
// 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(
Handlers.Server, Formats.AuthorizationCode, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector(
@ -296,7 +305,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireDeviceCodeIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>()
.AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionDeviceCode>()
.SetOrder(GenerateIdentityModelDeviceCode.Descriptor.Order - 500)
.Build();
@ -360,7 +369,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireRefreshTokenIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>()
.AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionRefreshToken>()
.SetOrder(GenerateIdentityModelRefreshToken.Descriptor.Order - 500)
.Build();
@ -386,7 +395,7 @@ namespace OpenIddict.Server.DataProtection
}
// 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(
Handlers.Server, Formats.RefreshToken, Features.ReferenceTokens, Schemes.Server) :
_options.CurrentValue.DataProtectionProvider.CreateProtector(
@ -424,7 +433,7 @@ namespace OpenIddict.Server.DataProtection
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessSignInContext>()
.AddFilter<RequireUserCodeIncluded>()
.AddFilter<RequirePreferDataProtectionFormatEnabled>()
.AddFilter<RequireDataProtectionFormatEnabled>()
.UseSingletonHandler<GenerateDataProtectionUserCode>()
.SetOrder(GenerateIdentityModelUserCode.Descriptor.Order - 500)
.Build();

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

@ -21,18 +21,18 @@ namespace OpenIddict.Server.DataProtection
/// </summary>
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>
/// 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.
/// </summary>
public IOpenIddictServerDataProtectionFormatter Formatter { get; set; }
= 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);
/// <summary>
/// Disables token storage, so that authorization code and
/// refresh tokens are never stored and cannot be revoked.
/// Using this option is generally NOT recommended.
/// Disables token storage, so that no database entry is created
/// for the tokens and codes returned by the OpenIddict server.
/// 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>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder DisableTokenStorage()
@ -1790,19 +1793,20 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <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.
/// Configures OpenIddict to use reference tokens, so that the token and code payloads
/// are stored in the database (only an identifier is returned to the client application).
/// Enabling this option is useful when storing a very large number of claims in the tokens,
/// 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>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder UseReferenceAccessTokens()
=> Configure(options => options.UseReferenceAccessTokens = true);
public OpenIddictServerBuilder UseReferenceTokens()
=> Configure(options => options.UseReferenceTokens = true);
/// <summary>
/// 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
/// one is automatically revoked unless token revocation was explicitly disabled).
/// one is automatically revoked unless token storage was explicitly disabled).
/// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder UseRollingTokens()

4
src/OpenIddict.Server/OpenIddictServerConfiguration.cs

@ -26,7 +26,7 @@ namespace OpenIddict.Server
/// Populates the default OpenIddict server options and ensures
/// that the configuration is in a consistent and valid state.
/// </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>
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.");
}
if (options.UseReferenceAccessTokens)
if (options.UseReferenceTokens)
{
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<RequireIdentityTokenIncluded>();
builder.Services.TryAddSingleton<RequirePostLogoutRedirectUriParameter>();
builder.Services.TryAddSingleton<RequireReferenceAccessTokensEnabled>();
builder.Services.TryAddSingleton<RequireReferenceTokensEnabled>();
builder.Services.TryAddSingleton<RequireRefreshTokenIncluded>();
builder.Services.TryAddSingleton<RequireRollingTokensDisabled>();
builder.Services.TryAddSingleton<RequireRollingTokensEnabled>();

6
src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs

@ -176,9 +176,9 @@ namespace OpenIddict.Server
}
/// <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>
public class RequireReferenceAccessTokensEnabled : IOpenIddictServerHandlerFilter<BaseContext>
public class RequireReferenceTokensEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context)
{
@ -187,7 +187,7 @@ namespace OpenIddict.Server
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;
}
// 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;
}
}

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
// that was granted the ept:logout permission, unless endpoint permissions checking was explicitly disabled.
await using var enumerator = _applicationManager.FindByPostLogoutRedirectUriAsync(address).GetAsyncEnumerator();
if (await enumerator.MoveNextAsync())
await foreach (var application in _applicationManager.FindByPostLogoutRedirectUriAsync(address))
{
if (context.Options.IgnoreEndpointPermissions)
if (context.Options.IgnoreEndpointPermissions ||
await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Logout))
{
return true;
}
do
{
if (await _applicationManager.HasPermissionAsync(enumerator.Current, Permissions.Endpoints.Logout))
{
return true;
}
}
while (await enumerator.MoveNextAsync());
}
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>
/// 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>
public IList<EncryptingCredentials> EncryptionCredentials { get; } = new List<EncryptingCredentials>();
/// <summary>
/// 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.
/// 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>
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.
/// 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.
/// Note: disabling encryption has no effect when using ASP.NET Core Data Protection.
/// </summary>
public bool DisableAccessTokenEncryption { get; set; }
@ -234,8 +237,9 @@ namespace OpenIddict.Server
/// <summary>
/// Gets or sets a boolean indicating whether token storage should be disabled.
/// When disabled, authorization code and refresh tokens are not stored
/// and cannot be revoked. Using this option is generally not recommended.
/// When disabled, no database entry is created for the tokens and codes
/// returned by OpenIddict. Using this option is generally NOT recommended
/// as it prevents the tokens and codes from being revoked (if needed).
/// </summary>
public bool DisableTokenStorage { get; set; }
@ -307,14 +311,15 @@ namespace OpenIddict.Server
};
/// <summary>
/// Gets or sets a boolean indicating whether reference access tokens should be used.
/// When set to <c>true</c>, access tokens and are stored as ciphertext in the database
/// Gets or sets a boolean indicating whether reference tokens should be used.
/// 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.
/// Enabling this option is useful to keep track of all the issued access tokens,
/// when storing a very large number of claims in the access tokens
/// or when immediate revocation of reference access tokens is desired.
/// Enabling this option is useful when storing a very large number of claims
/// in the tokens, 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>
public bool UseReferenceAccessTokens { get; set; }
public bool UseReferenceTokens { get; set; }
/// <summary>
/// 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.
/// When this option is enabled, a new refresh token is issued for each
/// 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>
public bool UseRollingTokens { get; set; }
}

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

@ -61,7 +61,7 @@ namespace OpenIddict.Validation.AspNetCore
/// <summary>
/// Ensures that the authentication configuration is in a consistent and valid state.
/// </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>
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
/// and ensures that the configuration is in a consistent and valid state.
/// </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>
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.OpenIddictValidationEvents;
using static OpenIddict.Validation.OpenIddictValidationHandlers;
using Properties = OpenIddict.Validation.OpenIddictValidationConstants.Properties;
namespace OpenIddict.Validation.DataProtection
{
@ -70,8 +71,16 @@ namespace OpenIddict.Validation.DataProtection
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.
var protector = context.Options.UseReferenceAccessTokens ?
var protector = context.Transaction.Properties.ContainsKey(Properties.ReferenceTokenIdentifier) ?
_options.CurrentValue.DataProtectionProvider.CreateProtector(
Purposes.Handlers.Server, Purposes.Formats.AccessToken,
Purposes.Features.ReferenceTokens, Purposes.Schemes.Server) :

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

@ -6,6 +6,7 @@
using System;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.Extensions.Options;
using OpenIddict.Server;
@ -15,7 +16,8 @@ namespace OpenIddict.Validation.ServerIntegration
/// <summary>
/// Contains the methods required to ensure that the OpenIddict validation/server integration configuration is valid.
/// </summary>
public class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions<OpenIddictValidationOptions>
public class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions<OpenIddictValidationOptions>,
IPostConfigureOptions<OpenIddictValidationOptions>
{
private readonly IOptionsMonitor<OpenIddictServerOptions> _options;
@ -53,7 +55,39 @@ namespace OpenIddict.Validation.ServerIntegration
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));
}
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<
IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>());
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
builder.Services.TryAddEnumerable(new[]
{
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>(),
ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>()
});
return new OpenIddictValidationServerIntegrationBuilder(builder.Services);
}

23
src/OpenIddict.Validation/OpenIddictValidationBuilder.cs

@ -407,12 +407,23 @@ namespace Microsoft.Extensions.DependencyInjection
/// <summary>
/// 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.
/// 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>
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
public OpenIddictValidationBuilder EnableAuthorizationValidation()
=> 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>
/// 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.
@ -444,16 +455,6 @@ namespace Microsoft.Extensions.DependencyInjection
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>
/// Determines whether the specified object is equal to the current object.
/// </summary>

2
src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs

@ -22,7 +22,7 @@ namespace OpenIddict.Validation
/// Populates the default OpenIddict validation options and ensures
/// that the configuration is in a consistent and valid state.
/// </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>
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.
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.
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<

6
src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs

@ -32,9 +32,9 @@ namespace OpenIddict.Validation
}
/// <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>
public class RequireReferenceAccessTokensEnabled : IOpenIddictValidationHandlerFilter<BaseContext>
public class RequireTokenValidationEnabled : IOpenIddictValidationHandlerFilter<BaseContext>
{
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context)
{
@ -43,7 +43,7 @@ namespace OpenIddict.Validation
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,
ValidateExpirationDate.Descriptor,
ValidateAudience.Descriptor,
ValidateTokenEntry.Descriptor,
ValidateAuthorizationEntry.Descriptor,
/*
@ -110,7 +111,7 @@ namespace OpenIddict.Validation
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireReferenceAccessTokensEnabled>()
.AddFilter<RequireTokenValidationEnabled>()
.UseScopedHandler<ValidateReferenceTokenIdentifier>()
.SetOrder(ValidateAccessTokenParameter.Descriptor.Order + 1_000)
.Build();
@ -122,7 +123,14 @@ namespace OpenIddict.Validation
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);
if (token == null)
{
@ -313,7 +321,7 @@ namespace OpenIddict.Validation
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireReferenceAccessTokensEnabled>()
.AddFilter<RequireTokenValidationEnabled>()
.UseScopedHandler<RestoreReferenceTokenProperties>()
.SetOrder(MapInternalClaims.Descriptor.Order + 1_000)
.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>
/// Contains the logic responsible of authentication demands a token whose
/// associated authorization entry is no longer valid (e.g was revoked).
@ -548,7 +618,7 @@ namespace OpenIddict.Validation
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthorizationValidationEnabled>()
.UseScopedHandler<ValidateAuthorizationEntry>()
.SetOrder(ValidateAudience.Descriptor.Order + 1_000)
.SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000)
.Build();
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context)

16
src/OpenIddict.Validation/OpenIddictValidationOptions.cs

@ -46,19 +46,19 @@ namespace OpenIddict.Validation
/// <summary>
/// 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>
public bool EnableAuthorizationValidation { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether reference access tokens should be used.
/// When set to <c>true</c>, access tokens and are stored as ciphertext in the database
/// 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,
/// when storing a very large number of claims in the access tokens
/// or when immediate revocation of reference access tokens is desired.
/// Gets or sets a boolean indicating whether a database call is made
/// to validate the token entry associated with the received tokens.
/// Note: enabling this option may have an impact on performance but
/// is required when the OpenIddict server emits reference tokens.
/// </summary>
public bool UseReferenceAccessTokens { get; set; }
public bool EnableTokenValidation { get; set; }
/// <summary>
/// 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 =>
{
options.RegisterScopes("registered_scope");
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.DisableTokenStorage();
options.DisableSlidingExpiration();
options.Services.AddSingleton(CreateApplicationManager(mock =>
{
@ -654,6 +657,9 @@ namespace OpenIddict.Server.FunctionalTests
var scope = new OpenIddictScope();
options.RegisterScopes("scope_registered_in_options");
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.DisableTokenStorage();
options.DisableSlidingExpiration();
options.Services.AddSingleton(CreateApplicationManager(mock =>
{
@ -1347,6 +1353,11 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.DisableAuthorizationStorage();
options.DisableTokenStorage();
options.DisableSlidingExpiration();
options.Services.AddSingleton(manager);
options.AddEventHandler<HandleAuthorizationRequestContext>(builder =>
@ -1398,6 +1409,11 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.SetRevocationEndpointUris(Array.Empty<Uri>());
options.DisableAuthorizationStorage();
options.DisableTokenStorage();
options.DisableSlidingExpiration();
options.Services.AddSingleton(manager);
options.AddEventHandler<HandleAuthorizationRequestContext>(builder =>

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

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

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

@ -1163,7 +1163,7 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.UseReferenceAccessTokens();
options.UseReferenceTokens();
options.Services.AddSingleton(CreateApplicationManager(mock =>
{
@ -1273,7 +1273,7 @@ namespace OpenIddict.Server.FunctionalTests
options.Services.AddSingleton(manager);
options.DisableAuthorizationStorage();
options.UseReferenceAccessTokens();
options.UseReferenceTokens();
});
// Act
@ -1363,7 +1363,7 @@ namespace OpenIddict.Server.FunctionalTests
options.Services.AddSingleton(manager);
options.UseReferenceAccessTokens();
options.UseReferenceTokens();
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
@ -1461,7 +1461,7 @@ namespace OpenIddict.Server.FunctionalTests
options.Services.AddSingleton(manager);
options.UseReferenceAccessTokens();
options.UseReferenceTokens();
options.RemoveEventHandler(NormalizeErrorResponse.Descriptor);
});
@ -1546,7 +1546,7 @@ namespace OpenIddict.Server.FunctionalTests
options.Services.AddSingleton(manager);
options.UseReferenceAccessTokens();
options.UseReferenceTokens();
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);
}
[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]
public async Task ValidateRevocationRequest_IdentityTokenCausesAnUnsupportedTokenTypeError()
{
@ -272,7 +231,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
@ -570,7 +528,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
@ -615,7 +572,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
@ -663,7 +619,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
@ -878,7 +833,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
@ -923,7 +877,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
@ -971,7 +924,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.UseReferenceAccessTokens();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
@ -1014,7 +966,6 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.EnableDegradedMode();
options.UseReferenceAccessTokens();
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>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
var client = CreateClient(options =>
@ -2798,11 +2801,15 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
var client = CreateClient(options =>
{
options.UseRollingTokens();
options.DisableAuthorizationStorage();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
@ -2867,6 +2874,7 @@ namespace OpenIddict.Server.FunctionalTests
var client = CreateClient(options =>
{
options.UseRollingTokens();
options.DisableAuthorizationStorage();
options.AddEventHandler<ProcessAuthenticationContext>(builder =>
{
@ -2921,6 +2929,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
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>()))
.Returns(tokens.ToAsyncEnumerable());
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
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>()))
.Returns(tokens.ToAsyncEnumerable());
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
var client = CreateClient(options =>
@ -3163,6 +3180,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
var client = CreateClient(options =>
@ -3219,6 +3239,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
var client = CreateClient(options =>
@ -3280,6 +3303,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
var client = CreateClient(options =>
@ -3341,6 +3367,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(DateTimeOffset.Now + TimeSpan.FromDays(1));
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
var client = CreateClient(options =>
@ -3404,6 +3433,9 @@ namespace OpenIddict.Server.FunctionalTests
mock.Setup(manager => manager.TryExtendAsync(token, It.IsAny<DateTimeOffset?>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictToken());
});
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>()))
.ReturnsAsync(new OpenIddictAuthorization());
mock.Setup(manager => manager.CreateAsync(It.IsAny<OpenIddictAuthorizationDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization());
});
var client = CreateClient(options =>

Loading…
Cancel
Save