From 414e05eed42f018af23221d0b536f268c2f6f039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Thu, 23 Apr 2020 19:09:59 +0200 Subject: [PATCH] Create a DB entry for all types of tokens, rework reference tokens support and add token entry validation to the validation handler --- samples/Mvc.Server/Startup.cs | 20 +- samples/Mvc.Server/Worker.cs | 4 +- .../Primitives/OpenIddictConverter.cs | 6 +- .../Primitives/OpenIddictMessage.cs | 17 +- .../Primitives/OpenIddictParameter.cs | 7 +- .../Managers/OpenIddictTokenManager.cs | 9 - ...OpenIddictServerAspNetCoreConfiguration.cs | 2 +- .../OpenIddictServerDataProtectionBuilder.cs | 6 +- ...IddictServerDataProtectionConfiguration.cs | 2 +- ...penIddictServerDataProtectionExtensions.cs | 8 +- ...ddictServerDataProtectionHandlerFilters.cs | 6 +- .../OpenIddictServerDataProtectionHandlers.cs | 35 +- .../OpenIddictServerDataProtectionOptions.cs | 14 +- .../OpenIddictServerBuilder.cs | 24 +- .../OpenIddictServerConfiguration.cs | 4 +- .../OpenIddictServerExtensions.cs | 2 +- .../OpenIddictServerHandlerFilters.cs | 6 +- .../OpenIddictServerHandlers.Revocation.cs | 12 - .../OpenIddictServerHandlers.Session.cs | 16 +- .../OpenIddictServerHandlers.cs | 1193 +++++++++++------ .../OpenIddictServerOptions.cs | 27 +- ...IddictValidationAspNetCoreConfiguration.cs | 2 +- ...ctValidationDataProtectionConfiguration.cs | 2 +- ...nIddictValidationDataProtectionHandlers.cs | 11 +- ...alidationServerIntegrationConfiguration.cs | 38 +- ...ctValidationServerIntegrationExtensions.cs | 9 +- .../OpenIddictValidationBuilder.cs | 23 +- .../OpenIddictValidationConfiguration.cs | 2 +- .../OpenIddictValidationExtensions.cs | 2 +- .../OpenIddictValidationHandlerFilters.cs | 6 +- .../OpenIddictValidationHandlers.cs | 78 +- .../OpenIddictValidationOptions.cs | 16 +- ...ctServerIntegrationTests.Authentication.cs | 16 + ...enIddictServerIntegrationTests.Exchange.cs | 12 + ...ictServerIntegrationTests.Introspection.cs | 10 +- ...IddictServerIntegrationTests.Revocation.cs | 49 - .../OpenIddictServerIntegrationTests.cs | 35 + 37 files changed, 1152 insertions(+), 579 deletions(-) diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index 664310c7..b6a51c29 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/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(); @@ -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(); diff --git a/samples/Mvc.Server/Worker.cs b/samples/Mvc.Server/Worker.cs index cafedbab..f55d6d38 100644 --- a/samples/Mvc.Server/Worker.cs +++ b/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) { diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs index 3b778084..13c7af4c 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictConverter.cs +++ b/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)); } diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs index 3b92fc66..0162596e 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs +++ b/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 { diff --git a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs b/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs index 121bcd67..e8a89d4c 100644 --- a/src/OpenIddict.Abstractions/Primitives/OpenIddictParameter.cs +++ b/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 }; diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index 15b586c8..0c4f5b42 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/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."); diff --git a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs index 15b98f1f..e7684125 100644 --- a/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs +++ b/src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreConfiguration.cs @@ -62,7 +62,7 @@ namespace OpenIddict.Server.AspNetCore /// /// Ensures that the authentication configuration is in a consistent and valid state. /// - /// The authentication scheme associated with the handler instance. + /// The name of the options instance to configure, if applicable. /// The options instance to initialize. public void PostConfigure([CanBeNull] string name, [NotNull] AuthenticationOptions options) { diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs index c402bec9..3a25db98 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionBuilder.cs @@ -81,12 +81,12 @@ namespace Microsoft.Extensions.DependencyInjection } /// - /// 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. /// /// The . - public OpenIddictServerDataProtectionBuilder PreferDataProtectionFormat() - => Configure(options => options.PreferDataProtectionFormat = true); + public OpenIddictServerDataProtectionBuilder PreferDefaultTokenFormat() + => Configure(options => options.PreferDefaultTokenFormat = true); /// /// Determines whether the specified object is equal to the current object. diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs index 829c3c10..30c1edda 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConfiguration.cs +++ b/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. /// - /// The authentication scheme associated with the handler instance. + /// The name of the options instance to configure, if applicable. /// The options instance to initialize. public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictServerDataProtectionOptions options) { diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs index 79c7bb8a..b90c3575 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionExtensions.cs @@ -22,7 +22,8 @@ namespace Microsoft.Extensions.DependencyInjection public static class OpenIddictServerDataProtectionExtensions { /// - /// 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. /// /// The services builder used by OpenIddict to register new services. /// This extension can be safely called multiple times. @@ -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(); + builder.Services.TryAddSingleton(); // 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 } /// - /// 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. /// /// The services builder used by OpenIddict to register new services. /// The configuration delegate used to configure the server services. diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlerFilters.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlerFilters.cs index fcbfb278..f15ce0dd 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlerFilters.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlerFilters.cs @@ -22,11 +22,11 @@ namespace OpenIddict.Server.DataProtection /// /// Represents a filter that excludes the associated handlers if OpenIddict was not configured to issue Data Protection tokens. /// - public class RequirePreferDataProtectionFormatEnabled : IOpenIddictServerHandlerFilter + public class RequireDataProtectionFormatEnabled : IOpenIddictServerHandlerFilter { private readonly IOptionsMonitor _options; - public RequirePreferDataProtectionFormatEnabled([NotNull] IOptionsMonitor options) + public RequireDataProtectionFormatEnabled([NotNull] IOptionsMonitor options) => _options = options; public ValueTask IsActiveAsync([NotNull] BaseContext context) @@ -36,7 +36,7 @@ namespace OpenIddict.Server.DataProtection throw new ArgumentNullException(nameof(context)); } - return new ValueTask(_options.CurrentValue.PreferDataProtectionFormat); + return new ValueTask(!_options.CurrentValue.PreferDefaultTokenFormat); } } } diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs index dd6879ad..f45edfd9 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.cs +++ b/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() .AddFilter() - .AddFilter() + .AddFilter() .UseSingletonHandler() .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() .AddFilter() - .AddFilter() + .AddFilter() .UseSingletonHandler() .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() .AddFilter() - .AddFilter() + .AddFilter() .UseSingletonHandler() .SetOrder(GenerateIdentityModelDeviceCode.Descriptor.Order - 500) .Build(); @@ -360,7 +369,7 @@ namespace OpenIddict.Server.DataProtection public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() - .AddFilter() + .AddFilter() .UseSingletonHandler() .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() .AddFilter() - .AddFilter() + .AddFilter() .UseSingletonHandler() .SetOrder(GenerateIdentityModelUserCode.Descriptor.Order - 500) .Build(); diff --git a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs index 87b52828..43f581d7 100644 --- a/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs +++ b/src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionOptions.cs @@ -21,18 +21,18 @@ namespace OpenIddict.Server.DataProtection /// public IDataProtectionProvider DataProtectionProvider { get; set; } - /// - /// 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 false by default. - /// - public bool PreferDataProtectionFormat { get; set; } - /// /// 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. /// public IOpenIddictServerDataProtectionFormatter Formatter { get; set; } = new OpenIddictServerDataProtectionFormatter(); + + /// + /// 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 false by default. + /// + public bool PreferDefaultTokenFormat { get; set; } } } diff --git a/src/OpenIddict.Server/OpenIddictServerBuilder.cs b/src/OpenIddict.Server/OpenIddictServerBuilder.cs index 778f2a2c..1ef78b0a 100644 --- a/src/OpenIddict.Server/OpenIddictServerBuilder.cs +++ b/src/OpenIddict.Server/OpenIddictServerBuilder.cs @@ -1600,9 +1600,12 @@ namespace Microsoft.Extensions.DependencyInjection => Configure(options => options.UseSlidingExpiration = false); /// - /// 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. /// /// The . public OpenIddictServerBuilder DisableTokenStorage() @@ -1790,19 +1793,20 @@ namespace Microsoft.Extensions.DependencyInjection } /// - /// 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. /// /// The . - public OpenIddictServerBuilder UseReferenceAccessTokens() - => Configure(options => options.UseReferenceAccessTokens = true); + public OpenIddictServerBuilder UseReferenceTokens() + => Configure(options => options.UseReferenceTokens = true); /// /// 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). /// /// The . public OpenIddictServerBuilder UseRollingTokens() diff --git a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs b/src/OpenIddict.Server/OpenIddictServerConfiguration.cs index 0cdfb1f5..5fd7bc2f 100644 --- a/src/OpenIddict.Server/OpenIddictServerConfiguration.cs +++ b/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. /// - /// The authentication scheme associated with the handler instance. + /// The name of the options instance to configure, if applicable. /// The options instance to initialize. 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."); } diff --git a/src/OpenIddict.Server/OpenIddictServerExtensions.cs b/src/OpenIddict.Server/OpenIddictServerExtensions.cs index 202a6f53..b1ef97ae 100644 --- a/src/OpenIddict.Server/OpenIddictServerExtensions.cs +++ b/src/OpenIddict.Server/OpenIddictServerExtensions.cs @@ -53,7 +53,7 @@ namespace Microsoft.Extensions.DependencyInjection builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); builder.Services.TryAddSingleton(); diff --git a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs index f2135d19..6b239cc8 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs @@ -176,9 +176,9 @@ namespace OpenIddict.Server } /// - /// 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. /// - public class RequireReferenceAccessTokensEnabled : IOpenIddictServerHandlerFilter + public class RequireReferenceTokensEnabled : IOpenIddictServerHandlerFilter { public ValueTask IsActiveAsync([NotNull] BaseContext context) { @@ -187,7 +187,7 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - return new ValueTask(context.Options.UseReferenceAccessTokens); + return new ValueTask(context.Options.UseReferenceTokens); } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs index 9814ecbd..4cc3acc5 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs +++ b/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; } } diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs index a5b83807..2f1302da 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs +++ b/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; diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs index 14ee5542..4d17d3c5 100644 --- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs +++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs @@ -72,29 +72,33 @@ namespace OpenIddict.Server PrepareUserCodePrincipal.Descriptor, RedeemTokenEntry.Descriptor, - RedeemDeviceCodeEntry.Descriptor, - RedeemUserCodeEntry.Descriptor, RevokeExistingTokenEntries.Descriptor, ExtendRefreshTokenEntry.Descriptor, + CreateAccessTokenEntry.Descriptor, GenerateIdentityModelAccessToken.Descriptor, - CreateReferenceAccessTokenEntry.Descriptor, + ConvertReferenceAccessToken.Descriptor, + CreateAuthorizationCodeEntry.Descriptor, GenerateIdentityModelAuthorizationCode.Descriptor, - CreateReferenceAuthorizationCodeEntry.Descriptor, + ConvertReferenceAuthorizationCode.Descriptor, + CreateDeviceCodeEntry.Descriptor, GenerateIdentityModelDeviceCode.Descriptor, - CreateReferenceDeviceCodeEntry.Descriptor, + ConvertReferenceDeviceCode.Descriptor, UpdateReferenceDeviceCodeEntry.Descriptor, + CreateRefreshTokenEntry.Descriptor, GenerateIdentityModelRefreshToken.Descriptor, - CreateReferenceRefreshTokenEntry.Descriptor, + ConvertReferenceRefreshToken.Descriptor, + CreateUserCodeEntry.Descriptor, AttachDeviceCodeIdentifier.Descriptor, GenerateIdentityModelUserCode.Descriptor, - CreateReferenceUserCodeEntry.Descriptor, + ConvertReferenceUserCode.Descriptor, AttachTokenDigests.Descriptor, + CreateIdentityTokenEntry.Descriptor, GenerateIdentityModelIdentityToken.Descriptor, BeautifyUserCode.Descriptor, @@ -337,7 +341,15 @@ namespace OpenIddict.Server 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, + // except reference user codes, whose length is exactly 12 characters. + // If the token length differs, the token cannot be a reference token. + if (string.IsNullOrEmpty(context.Token) || (context.Token.Length != 12 && 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) { @@ -607,12 +619,13 @@ namespace OpenIddict.Server return; } - if (!context.Transaction.Properties.TryGetValue(Properties.ReferenceTokenIdentifier, out var identifier)) + var identifier = context.Transaction.GetProperty(Properties.ReferenceTokenIdentifier); + if (string.IsNullOrEmpty(identifier)) { return; } - var token = await _tokenManager.FindByIdAsync((string) identifier); + var token = await _tokenManager.FindByIdAsync(identifier); if (token == null) { throw new InvalidOperationException("The token entry cannot be found in the database."); @@ -1211,7 +1224,7 @@ namespace OpenIddict.Server typeof(ProcessAuthenticationContext).FullName) ?? throw new InvalidOperationException("The authentication context cannot be found."); - // Extract the device code identifier from the authentication principal. + // Extract the device code identifier from the user code principal. var identifier = notification.Principal.GetClaim(Claims.Private.DeviceCodeId); if (string.IsNullOrEmpty(identifier)) { @@ -1766,7 +1779,7 @@ namespace OpenIddict.Server var authorization = await _authorizationManager.CreateAsync(descriptor); if (authorization == null) { - return; + throw new InvalidOperationException("An unknown error occurred while creating an authorization entry."); } var identifier = await _authorizationManager.GetIdAsync(authorization); @@ -2072,6 +2085,13 @@ namespace OpenIddict.Server principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); } + // Restore the device code internal token identifier from the principal + // resolved from the user code used in the user code verification request. + if (context.EndpointType == OpenIddictServerEndpointType.Verification) + { + principal.SetClaim(Claims.Private.TokenId, context.Principal.GetClaim(Claims.Private.DeviceCodeId)); + } + context.DeviceCodePrincipal = principal; return default; @@ -2197,8 +2217,9 @@ namespace OpenIddict.Server // Actors identities are also filtered (delegation scenarios). var principal = context.Principal.Clone(claim => { - // Never exclude the subject claim. - if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase)) + // Never exclude the subject and authorization identifier claims. + if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || + string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase)) { return true; } @@ -2348,8 +2369,8 @@ namespace OpenIddict.Server } /// - /// Contains the logic responsible of redeeming the token entry - /// corresponding to the received authorization code or refresh token. + /// Contains the logic responsible of redeeming the token entry corresponding to + /// the received authorization code, device code, user code or refresh token. /// Note: this handler is not used when the degraded mode is enabled. /// public class RedeemTokenEntry : IOpenIddictServerHandler @@ -2392,21 +2413,25 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - if (context.EndpointType != OpenIddictServerEndpointType.Token) + if (context.EndpointType != OpenIddictServerEndpointType.Token && + context.EndpointType != OpenIddictServerEndpointType.Verification) { return; } - if (!context.Request.IsAuthorizationCodeGrantType() && - !context.Request.IsDeviceCodeGrantType() && - !context.Request.IsRefreshTokenGrantType()) + if (context.EndpointType == OpenIddictServerEndpointType.Token) { - return; - } + if (!context.Request.IsAuthorizationCodeGrantType() && + !context.Request.IsDeviceCodeGrantType() && + !context.Request.IsRefreshTokenGrantType()) + { + return; + } - if (context.Request.IsRefreshTokenGrantType() && !context.Options.UseRollingTokens) - { - return; + if (context.Request.IsRefreshTokenGrantType() && !context.Options.UseRollingTokens) + { + return; + } } // Extract the token identifier from the authentication principal. @@ -2417,26 +2442,28 @@ namespace OpenIddict.Server return; } - var token = await _tokenManager.FindByIdAsync(identifier); - if (token == null) - { - throw new InvalidOperationException("The token details cannot be found in the database."); - } - - // If rolling tokens are enabled or if the request is an authorization_code or device_code request, - // mark the authorization/device code or the refresh token as redeemed to prevent future reuses. + // If rolling tokens are enabled or if the request is a a code or device code token request + // or a user code verification request, mark the token as redeemed to prevent future reuses. // If the operation fails, return an error indicating the code/token is no longer valid. // See https://tools.ietf.org/html/rfc6749#section-6 for more information. - if (!await _tokenManager.TryRedeemAsync(token)) + var token = await _tokenManager.FindByIdAsync(identifier); + if (token == null || !await _tokenManager.TryRedeemAsync(token)) { context.Reject( error: Errors.InvalidGrant, - description: - context.Request.IsAuthorizationCodeGrantType() ? - "The specified authorization code is no longer valid." : - context.Request.IsDeviceCodeGrantType() ? - "The specified device code is no longer valid." : - "The specified refresh token is no longer valid."); + description: context.EndpointType switch + { + OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() + => "The specified authorization code is no longer valid.", + OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() + => "The specified device code is no longer valid.", + OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() + => "The specified refresh token is no longer valid.", + + OpenIddictServerEndpointType.Verification => "The specified user code is no longer valid.", + + _ => "The specified token is no longer valid." + }); return; } @@ -2444,14 +2471,14 @@ namespace OpenIddict.Server } /// - /// Contains the logic responsible of redeeming the device code entry associated with the user code. + /// Contains the logic responsible of revoking all the tokens that were previously issued. /// Note: this handler is not used when the degraded mode is enabled. /// - public class RedeemDeviceCodeEntry : IOpenIddictServerHandler + public class RevokeExistingTokenEntries : IOpenIddictServerHandler { private readonly IOpenIddictTokenManager _tokenManager; - public RedeemDeviceCodeEntry() => throw new InvalidOperationException(new StringBuilder() + public RevokeExistingTokenEntries() => throw new InvalidOperationException(new StringBuilder() .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") @@ -2459,7 +2486,7 @@ namespace OpenIddict.Server .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") .ToString()); - public RedeemDeviceCodeEntry([NotNull] IOpenIddictTokenManager tokenManager) + public RevokeExistingTokenEntries([NotNull] IOpenIddictTokenManager tokenManager) => _tokenManager = tokenManager; /// @@ -2469,7 +2496,8 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() - .UseScopedHandler() + .AddFilter() + .UseScopedHandler() .SetOrder(RedeemTokenEntry.Descriptor.Order + 1_000) .Build(); @@ -2487,44 +2515,45 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - if (context.EndpointType != OpenIddictServerEndpointType.Token) + if (context.EndpointType != OpenIddictServerEndpointType.Token || !context.Request.IsRefreshTokenGrantType()) { return; } - if (!context.Request.IsDeviceCodeGrantType()) - { - return; - } + // When rolling tokens are enabled, try to revoke all the previously issued tokens + // associated with the authorization if the request is a refresh_token request. + // If the operation fails, silently ignore the error and keep processing the request: + // this may indicate that one of the revoked tokens was modified by a concurrent request. - // Extract the device code identifier from the authentication principal. - var identifier = context.Principal.GetClaim(Claims.Private.DeviceCodeId); + var identifier = context.Principal.GetInternalAuthorizationId(); if (string.IsNullOrEmpty(identifier)) { - throw new InvalidOperationException("The device code identifier cannot be extracted from the principal."); + return; } - var token = await _tokenManager.FindByIdAsync(identifier); - if (token == null || !await _tokenManager.TryRedeemAsync(token)) + await foreach (var token in _tokenManager.FindByAuthorizationIdAsync(identifier)) { - context.Reject( - error: Errors.InvalidGrant, - description: "The specified device code is no longer valid."); + // Don't change the status of the token used in the token request. + if (string.Equals(context.Principal.GetInternalTokenId(), + await _tokenManager.GetIdAsync(token), StringComparison.Ordinal)) + { + continue; + } - return; + await _tokenManager.TryRevokeAsync(token); } } } /// - /// Contains the logic responsible of redeeming the user code entry, if applicable. + /// Contains the logic responsible of extending the lifetime of the refresh token entry. /// Note: this handler is not used when the degraded mode is enabled. /// - public class RedeemUserCodeEntry : IOpenIddictServerHandler + public class ExtendRefreshTokenEntry : IOpenIddictServerHandler { private readonly IOpenIddictTokenManager _tokenManager; - public RedeemUserCodeEntry() => throw new InvalidOperationException(new StringBuilder() + public ExtendRefreshTokenEntry() => throw new InvalidOperationException(new StringBuilder() .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") @@ -2532,7 +2561,7 @@ namespace OpenIddict.Server .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") .ToString()); - public RedeemUserCodeEntry([NotNull] IOpenIddictTokenManager tokenManager) + public ExtendRefreshTokenEntry([NotNull] IOpenIddictTokenManager tokenManager) => _tokenManager = tokenManager; /// @@ -2542,8 +2571,10 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() - .UseScopedHandler() - .SetOrder(RedeemDeviceCodeEntry.Descriptor.Order + 1_000) + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(RevokeExistingTokenEntries.Descriptor.Order + 1_000) .Build(); /// @@ -2560,12 +2591,13 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - if (context.EndpointType != OpenIddictServerEndpointType.Verification) + if (context.EndpointType != OpenIddictServerEndpointType.Token || !context.Request.IsRefreshTokenGrantType()) { return; } - // Extract the device code identifier from the authentication principal. + // Extract the token identifier from the authentication principal. + // If no token identifier can be found, this indicates that the token has no backing database entry. var identifier = context.Principal.GetInternalTokenId(); if (string.IsNullOrEmpty(identifier)) { @@ -2573,101 +2605,35 @@ namespace OpenIddict.Server } var token = await _tokenManager.FindByIdAsync(identifier); - if (token == null || !await _tokenManager.TryRedeemAsync(token)) - { - context.Reject( - error: Errors.InvalidGrant, - description: "The specified user code is no longer valid."); - - return; - } - } - } - - /// - /// Contains the logic responsible of revoking all the tokens that were previously issued. - /// Note: this handler is not used when the degraded mode is enabled. - /// - public class RevokeExistingTokenEntries : IOpenIddictServerHandler - { - private readonly IOpenIddictTokenManager _tokenManager; - - public RevokeExistingTokenEntries() => throw new InvalidOperationException(new StringBuilder() - .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") - .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") - .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") - .Append("Alternatively, you can disable the built-in database-based server features by enabling ") - .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") - .ToString()); - - public RevokeExistingTokenEntries([NotNull] IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; - - /// - /// Gets the default descriptor definition assigned to this handler. - /// - public static OpenIddictServerHandlerDescriptor Descriptor { get; } - = OpenIddictServerHandlerDescriptor.CreateBuilder() - .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(RedeemUserCodeEntry.Descriptor.Order + 1_000) - .Build(); - - /// - /// Processes the event. - /// - /// The context associated with the event to process. - /// - /// A that can be used to monitor the asynchronous operation. - /// - public async ValueTask HandleAsync([NotNull] ProcessSignInContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (context.EndpointType != OpenIddictServerEndpointType.Token || !context.Request.IsRefreshTokenGrantType()) + if (token == null) { - return; + throw new InvalidOperationException("The token details cannot be found in the database."); } - // When rolling tokens are enabled, try to revoke all the previously issued tokens - // associated with the authorization if the request is a refresh_token request. - // If the operation fails, silently ignore the error and keep processing the request: - // this may indicate that one of the revoked tokens was modified by a concurrent request. - - var identifier = context.Principal.GetInternalAuthorizationId(); - if (string.IsNullOrEmpty(identifier)) + // Compute the new expiration date of the refresh token and update the token entry. + var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime; + if (lifetime.HasValue) { - return; + await _tokenManager.TryExtendAsync(token, DateTimeOffset.UtcNow + lifetime.Value); } - await foreach (var token in _tokenManager.FindByAuthorizationIdAsync(identifier)) + else { - // Don't change the status of the token used in the token request. - if (string.Equals(context.Principal.GetInternalTokenId(), - await _tokenManager.GetIdAsync(token), StringComparison.Ordinal)) - { - continue; - } - - await _tokenManager.TryRevokeAsync(token); + await _tokenManager.TryExtendAsync(token, date: null); } } } /// - /// Contains the logic responsible of extending the lifetime of the refresh token entry. + /// Contains the logic responsible of creating an access token entry. /// Note: this handler is not used when the degraded mode is enabled. /// - public class ExtendRefreshTokenEntry : IOpenIddictServerHandler + public class CreateAccessTokenEntry : IOpenIddictServerHandler { + private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictTokenManager _tokenManager; - public ExtendRefreshTokenEntry() => throw new InvalidOperationException(new StringBuilder() + public CreateAccessTokenEntry() => throw new InvalidOperationException(new StringBuilder() .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") @@ -2675,8 +2641,13 @@ namespace OpenIddict.Server .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") .ToString()); - public ExtendRefreshTokenEntry([NotNull] IOpenIddictTokenManager tokenManager) - => _tokenManager = tokenManager; + public CreateAccessTokenEntry( + [NotNull] IOpenIddictApplicationManager applicationManager, + [NotNull] IOpenIddictTokenManager tokenManager) + { + _applicationManager = applicationManager; + _tokenManager = tokenManager; + } /// /// Gets the default descriptor definition assigned to this handler. @@ -2685,10 +2656,9 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() - .AddFilter() - .AddFilter() - .UseScopedHandler() - .SetOrder(RevokeExistingTokenEntries.Descriptor.Order + 1_000) + .AddFilter() + .UseScopedHandler() + .SetOrder(ExtendRefreshTokenEntry.Descriptor.Order + 1_000) .Build(); /// @@ -2705,36 +2675,47 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } - if (context.EndpointType != OpenIddictServerEndpointType.Token || !context.Request.IsRefreshTokenGrantType()) + var principal = context.AccessTokenPrincipal; + if (principal == null) { - return; + throw new InvalidOperationException("A token entry cannot be created from a null principal."); } - // Extract the token identifier from the authentication principal. - // If no token identifier can be found, this indicates that the token has no backing database entry. - var identifier = context.Principal.GetInternalTokenId(); - if (string.IsNullOrEmpty(identifier)) + var descriptor = new OpenIddictTokenDescriptor { - return; - } + AuthorizationId = principal.GetInternalAuthorizationId(), + CreationDate = principal.GetCreationDate(), + ExpirationDate = principal.GetExpirationDate(), + Principal = principal, + Status = Statuses.Valid, + Subject = principal.GetClaim(Claims.Subject), + Type = TokenTypeHints.AccessToken + }; - var token = await _tokenManager.FindByIdAsync(identifier); - if (token == null) + // If the client application is known, associate it with the token. + if (!string.IsNullOrEmpty(context.Request.ClientId)) { - throw new InvalidOperationException("The token details cannot be found in the database."); - } + var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); + if (application == null) + { + throw new InvalidOperationException("The application entry cannot be found in the database."); + } - // Compute the new expiration date of the refresh token and update the token entry. - var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime; - if (lifetime.HasValue) - { - await _tokenManager.TryExtendAsync(token, DateTimeOffset.UtcNow + lifetime.Value); + descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); } - else + var token = await _tokenManager.CreateAsync(descriptor); + if (token == null) { - await _tokenManager.TryExtendAsync(token, date: null); + throw new InvalidOperationException("An unknown error occurred while creating a token entry."); } + + var identifier = await _tokenManager.GetIdAsync(token); + + // Attach the token identifier to the principal so that it can be stored in the token. + principal.SetInternalTokenId(identifier); + + context.Logger.LogTrace("The token entry for access token '{Identifier}' was successfully created.", identifier); } } @@ -2750,7 +2731,7 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(ExtendRefreshTokenEntry.Descriptor.Order + 1_000) + .SetOrder(CreateAccessTokenEntry.Descriptor.Order + 1_000) .Build(); /// @@ -2829,15 +2810,14 @@ namespace OpenIddict.Server } /// - /// Contains the logic responsible of creating a reference access token entry. + /// Contains the logic responsible of converting the access token to a reference token. /// Note: this handler is not used when the degraded mode is enabled. /// - public class CreateReferenceAccessTokenEntry : IOpenIddictServerHandler + public class ConvertReferenceAccessToken : IOpenIddictServerHandler { - private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictTokenManager _tokenManager; - public CreateReferenceAccessTokenEntry() => throw new InvalidOperationException(new StringBuilder() + public ConvertReferenceAccessToken() => throw new InvalidOperationException(new StringBuilder() .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") @@ -2845,13 +2825,8 @@ namespace OpenIddict.Server .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") .ToString()); - public CreateReferenceAccessTokenEntry( - [NotNull] IOpenIddictApplicationManager applicationManager, - [NotNull] IOpenIddictTokenManager tokenManager) - { - _applicationManager = applicationManager; - _tokenManager = tokenManager; - } + public ConvertReferenceAccessToken([NotNull] IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. @@ -2860,9 +2835,9 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() - .AddFilter() + .AddFilter() .AddFilter() - .UseScopedHandler() + .UseScopedHandler() .SetOrder(GenerateIdentityModelAccessToken.Descriptor.Order + 1_000) .Build(); @@ -2885,6 +2860,24 @@ namespace OpenIddict.Server return; } + var principal = context.AccessTokenPrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + + var identifier = principal.GetInternalTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException("The token identifier cannot be extracted from the principal."); + } + + var token = await _tokenManager.FindByIdAsync(identifier); + if (token == null) + { + throw new InvalidOperationException("The token entry cannot be found in the database."); + } + // Generate a new crypto-secure random identifier that will be substituted to the token. var data = new byte[256 / 8]; #if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS @@ -2893,20 +2886,93 @@ namespace OpenIddict.Server using var generator = RandomNumberGenerator.Create(); generator.GetBytes(data); #endif - var descriptor = new OpenIddictTokenDescriptor - { - AuthorizationId = context.AccessTokenPrincipal.GetInternalAuthorizationId(), - CreationDate = context.AccessTokenPrincipal.GetCreationDate(), - ExpirationDate = context.AccessTokenPrincipal.GetExpirationDate(), - Payload = context.Response.AccessToken, - Principal = context.AccessTokenPrincipal, - ReferenceId = Base64UrlEncoder.Encode(data), - Status = Statuses.Valid, - Subject = context.AccessTokenPrincipal.GetClaim(Claims.Subject), - Type = TokenTypeHints.AccessToken - }; + var descriptor = new OpenIddictTokenDescriptor(); + await _tokenManager.PopulateAsync(descriptor, token); - // If the client application is known, associate it with the token. + // Attach the generated token to the token entry, persist the change + // and replace the returned token by the reference identifier. + descriptor.Payload = context.Response.AccessToken; + descriptor.Principal = principal; + descriptor.ReferenceId = Base64UrlEncoder.Encode(data); + + await _tokenManager.UpdateAsync(token, descriptor); + + context.Response.AccessToken = descriptor.ReferenceId; + + context.Logger.LogTrace("The token entry for access token '{Identifier}' was successfully converted to a " + + "reference token with the identifier '{ReferenceId}'.", identifier, descriptor.ReferenceId); + } + } + + /// + /// Contains the logic responsible of creating an authorization code entry. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class CreateAuthorizationCodeEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictTokenManager _tokenManager; + + public CreateAuthorizationCodeEntry() => throw new InvalidOperationException(new StringBuilder() + .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") + .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") + .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") + .Append("Alternatively, you can disable the built-in database-based server features by enabling ") + .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") + .ToString()); + + public CreateAuthorizationCodeEntry( + [NotNull] IOpenIddictApplicationManager applicationManager, + [NotNull] IOpenIddictTokenManager tokenManager) + { + _applicationManager = applicationManager; + _tokenManager = tokenManager; + } + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ConvertReferenceAccessToken.Descriptor.Order + 1_000) + .Build(); + + /// + /// Processes the event. + /// + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public async ValueTask HandleAsync([NotNull] ProcessSignInContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var principal = context.AuthorizationCodePrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + + var descriptor = new OpenIddictTokenDescriptor + { + AuthorizationId = principal.GetInternalAuthorizationId(), + CreationDate = principal.GetCreationDate(), + ExpirationDate = principal.GetExpirationDate(), + Principal = principal, + Status = Statuses.Valid, + Subject = principal.GetClaim(Claims.Subject), + Type = TokenTypeHints.AuthorizationCode + }; + + // If the client application is known, associate it with the token. if (!string.IsNullOrEmpty(context.Request.ClientId)) { var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); @@ -2919,13 +2985,17 @@ namespace OpenIddict.Server } var token = await _tokenManager.CreateAsync(descriptor); + if (token == null) + { + throw new InvalidOperationException("An unknown error occurred while creating a token entry."); + } - context.AccessTokenPrincipal.SetInternalTokenId(await _tokenManager.GetIdAsync(token)); - context.Response.AccessToken = descriptor.ReferenceId; + var identifier = await _tokenManager.GetIdAsync(token); + + // Attach the token identifier to the principal so that it can be stored in the token. + principal.SetInternalTokenId(identifier); - context.Logger.LogTrace("The reference token entry for access token '{Identifier}' was successfully " + - "created with the reference identifier '{ReferenceId}'.", - await _tokenManager.GetIdAsync(token), descriptor.ReferenceId); + context.Logger.LogTrace("The token entry for authorization code '{Identifier}' was successfully created.", identifier); } } @@ -2941,7 +3011,7 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(CreateReferenceAccessTokenEntry.Descriptor.Order + 1_000) + .SetOrder(CreateAuthorizationCodeEntry.Descriptor.Order + 1_000) .Build(); /// @@ -3009,15 +3079,14 @@ namespace OpenIddict.Server } /// - /// Contains the logic responsible of creating a reference authorization code entry. + /// Contains the logic responsible of converting the authorization code to a reference token. /// Note: this handler is not used when the degraded mode is enabled. /// - public class CreateReferenceAuthorizationCodeEntry : IOpenIddictServerHandler + public class ConvertReferenceAuthorizationCode : IOpenIddictServerHandler { - private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictTokenManager _tokenManager; - public CreateReferenceAuthorizationCodeEntry() => throw new InvalidOperationException(new StringBuilder() + public ConvertReferenceAuthorizationCode() => throw new InvalidOperationException(new StringBuilder() .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") @@ -3025,13 +3094,8 @@ namespace OpenIddict.Server .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") .ToString()); - public CreateReferenceAuthorizationCodeEntry( - [NotNull] IOpenIddictApplicationManager applicationManager, - [NotNull] IOpenIddictTokenManager tokenManager) - { - _applicationManager = applicationManager; - _tokenManager = tokenManager; - } + public ConvertReferenceAuthorizationCode([NotNull] IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. @@ -3040,8 +3104,9 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() .AddFilter() - .UseScopedHandler() + .UseScopedHandler() .SetOrder(GenerateIdentityModelAuthorizationCode.Descriptor.Order + 1_000) .Build(); @@ -3064,6 +3129,24 @@ namespace OpenIddict.Server return; } + var principal = context.AuthorizationCodePrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + + var identifier = principal.GetInternalTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException("The token identifier cannot be extracted from the principal."); + } + + var token = await _tokenManager.FindByIdAsync(identifier); + if (token == null) + { + throw new InvalidOperationException("The token entry cannot be found in the database."); + } + // Generate a new crypto-secure random identifier that will be substituted to the token. var data = new byte[256 / 8]; #if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS @@ -3072,17 +3155,95 @@ namespace OpenIddict.Server using var generator = RandomNumberGenerator.Create(); generator.GetBytes(data); #endif + var descriptor = new OpenIddictTokenDescriptor(); + await _tokenManager.PopulateAsync(descriptor, token); + + // Attach the generated token to the token entry, persist the change + // and replace the returned token by the reference identifier. + descriptor.Payload = context.Response.Code; + descriptor.Principal = principal; + descriptor.ReferenceId = Base64UrlEncoder.Encode(data); + + await _tokenManager.UpdateAsync(token, descriptor); + + context.Response.Code = descriptor.ReferenceId; + + context.Logger.LogTrace("The token entry for authorization code '{Identifier}' was successfully converted to a " + + "reference token with the identifier '{ReferenceId}'.", identifier, descriptor.ReferenceId); + } + } + + /// + /// Contains the logic responsible of creating an access token entry. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class CreateDeviceCodeEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictTokenManager _tokenManager; + + public CreateDeviceCodeEntry() => throw new InvalidOperationException(new StringBuilder() + .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") + .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") + .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") + .Append("Alternatively, you can disable the built-in database-based server features by enabling ") + .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") + .ToString()); + + public CreateDeviceCodeEntry( + [NotNull] IOpenIddictApplicationManager applicationManager, + [NotNull] IOpenIddictTokenManager tokenManager) + { + _applicationManager = applicationManager; + _tokenManager = tokenManager; + } + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(ConvertReferenceAuthorizationCode.Descriptor.Order + 1_000) + .Build(); + + /// + /// Processes the event. + /// + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public async ValueTask HandleAsync([NotNull] ProcessSignInContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.EndpointType == OpenIddictServerEndpointType.Verification) + { + return; + } + + var principal = context.DeviceCodePrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + var descriptor = new OpenIddictTokenDescriptor { - AuthorizationId = context.AuthorizationCodePrincipal.GetInternalAuthorizationId(), - CreationDate = context.AuthorizationCodePrincipal.GetCreationDate(), - ExpirationDate = context.AuthorizationCodePrincipal.GetExpirationDate(), - Payload = context.Response.Code, - Principal = context.AuthorizationCodePrincipal, - ReferenceId = Base64UrlEncoder.Encode(data), - Status = Statuses.Valid, - Subject = context.AuthorizationCodePrincipal.GetClaim(Claims.Subject), - Type = TokenTypeHints.AuthorizationCode + AuthorizationId = principal.GetInternalAuthorizationId(), + CreationDate = principal.GetCreationDate(), + ExpirationDate = principal.GetExpirationDate(), + Principal = principal, + Status = Statuses.Inactive, + Subject = null, // Device codes are not bound to a user, which is not known until the user code is populated. + Type = TokenTypeHints.DeviceCode }; // If the client application is known, associate it with the token. @@ -3098,13 +3259,17 @@ namespace OpenIddict.Server } var token = await _tokenManager.CreateAsync(descriptor); + if (token == null) + { + throw new InvalidOperationException("An unknown error occurred while creating a token entry."); + } - context.AuthorizationCodePrincipal.SetInternalTokenId(await _tokenManager.GetIdAsync(token)); - context.Response.Code = descriptor.ReferenceId; + var identifier = await _tokenManager.GetIdAsync(token); - context.Logger.LogTrace("The reference token entry for authorization code '{Identifier}' was successfully " + - "created with the reference identifier '{ReferenceId}'.", - await _tokenManager.GetIdAsync(token), descriptor.ReferenceId); + // Attach the token identifier to the principal so that it can be stored in the token. + principal.SetInternalTokenId(identifier); + + context.Logger.LogTrace("The token entry for device code '{Identifier}' was successfully created.", identifier); } } @@ -3120,7 +3285,7 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(CreateReferenceAuthorizationCodeEntry.Descriptor.Order + 1_000) + .SetOrder(CreateDeviceCodeEntry.Descriptor.Order + 1_000) .Build(); /// @@ -3191,12 +3356,11 @@ namespace OpenIddict.Server /// Contains the logic responsible of creating a reference device code entry. /// Note: this handler is not used when the degraded mode is enabled. /// - public class CreateReferenceDeviceCodeEntry : IOpenIddictServerHandler + public class ConvertReferenceDeviceCode : IOpenIddictServerHandler { - private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictTokenManager _tokenManager; - public CreateReferenceDeviceCodeEntry() => throw new InvalidOperationException(new StringBuilder() + public ConvertReferenceDeviceCode() => throw new InvalidOperationException(new StringBuilder() .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") @@ -3204,13 +3368,8 @@ namespace OpenIddict.Server .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") .ToString()); - public CreateReferenceDeviceCodeEntry( - [NotNull] IOpenIddictApplicationManager applicationManager, - [NotNull] IOpenIddictTokenManager tokenManager) - { - _applicationManager = applicationManager; - _tokenManager = tokenManager; - } + public ConvertReferenceDeviceCode([NotNull] IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. @@ -3219,8 +3378,9 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + // Note: device codes are always reference tokens. .AddFilter() - .UseScopedHandler() + .UseScopedHandler() .SetOrder(GenerateIdentityModelDeviceCode.Descriptor.Order + 1_000) .Build(); @@ -3248,6 +3408,24 @@ namespace OpenIddict.Server return; } + var principal = context.DeviceCodePrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + + var identifier = principal.GetInternalTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException("The token identifier cannot be extracted from the principal."); + } + + var token = await _tokenManager.FindByIdAsync(identifier); + if (token == null) + { + throw new InvalidOperationException("The token entry cannot be found in the database."); + } + // Generate a new crypto-secure random identifier that will be substituted to the token. var data = new byte[256 / 8]; #if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS @@ -3256,39 +3434,21 @@ namespace OpenIddict.Server using var generator = RandomNumberGenerator.Create(); generator.GetBytes(data); #endif - var descriptor = new OpenIddictTokenDescriptor - { - AuthorizationId = context.DeviceCodePrincipal.GetInternalAuthorizationId(), - CreationDate = context.DeviceCodePrincipal.GetCreationDate(), - ExpirationDate = context.DeviceCodePrincipal.GetExpirationDate(), - Payload = context.Response.DeviceCode, - Principal = context.DeviceCodePrincipal, - ReferenceId = Base64UrlEncoder.Encode(data), - Status = Statuses.Inactive, - Subject = null, // Device codes are not bound to a user, which is not known until the user code is populated. - Type = TokenTypeHints.DeviceCode - }; + var descriptor = new OpenIddictTokenDescriptor(); + await _tokenManager.PopulateAsync(descriptor, token); - // If the client application is known, associate it with the token. - if (!string.IsNullOrEmpty(context.Request.ClientId)) - { - var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); - if (application == null) - { - throw new InvalidOperationException("The application entry cannot be found in the database."); - } + // Attach the generated token to the token entry, persist the change + // and replace the returned token by the reference identifier. + descriptor.Payload = context.Response.DeviceCode; + descriptor.Principal = principal; + descriptor.ReferenceId = Base64UrlEncoder.Encode(data); - descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); - } + await _tokenManager.UpdateAsync(token, descriptor); - var token = await _tokenManager.CreateAsync(descriptor); - - context.DeviceCodePrincipal.SetInternalTokenId(await _tokenManager.GetIdAsync(token)); context.Response.DeviceCode = descriptor.ReferenceId; - context.Logger.LogTrace("The reference token entry for device code '{Identifier}' was successfully " + - "created with the reference identifier '{ReferenceId}'.", - await _tokenManager.GetIdAsync(token), descriptor.ReferenceId); + context.Logger.LogTrace("The token entry for device code '{Identifier}' was successfully converted to a " + + "reference token with the identifier '{ReferenceId}'.", identifier, descriptor.ReferenceId); } } @@ -3298,7 +3458,6 @@ namespace OpenIddict.Server /// public class UpdateReferenceDeviceCodeEntry : IOpenIddictServerHandler { - private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictTokenManager _tokenManager; public UpdateReferenceDeviceCodeEntry() => throw new InvalidOperationException(new StringBuilder() @@ -3309,13 +3468,8 @@ namespace OpenIddict.Server .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") .ToString()); - public UpdateReferenceDeviceCodeEntry( - [NotNull] IOpenIddictApplicationManager applicationManager, - [NotNull] IOpenIddictTokenManager tokenManager) - { - _applicationManager = applicationManager; - _tokenManager = tokenManager; - } + public UpdateReferenceDeviceCodeEntry([NotNull] IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. @@ -3326,7 +3480,7 @@ namespace OpenIddict.Server .AddFilter() .AddFilter() .UseScopedHandler() - .SetOrder(CreateReferenceDeviceCodeEntry.Descriptor.Order + 1_000) + .SetOrder(ConvertReferenceDeviceCode.Descriptor.Order + 1_000) .Build(); /// @@ -3353,6 +3507,12 @@ namespace OpenIddict.Server return; } + var principal = context.DeviceCodePrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + // Extract the token identifier from the authentication principal. var identifier = context.Principal.GetClaim(Claims.Private.DeviceCodeId); if (string.IsNullOrEmpty(identifier)) @@ -3371,20 +3531,115 @@ namespace OpenIddict.Server var descriptor = new OpenIddictTokenDescriptor(); await _tokenManager.PopulateAsync(descriptor, token); - // Note: the lifetime is deliberately extended to give more time to the client to redeem the code. - descriptor.ExpirationDate = context.DeviceCodePrincipal.GetExpirationDate(); - descriptor.Payload = context.Response.DeviceCode; - descriptor.Status = Statuses.Valid; - descriptor.Subject = context.DeviceCodePrincipal.GetClaim(Claims.Subject); + // Note: the lifetime is deliberately extended to give more time to the client to redeem the code. + descriptor.ExpirationDate = principal.GetExpirationDate(); + descriptor.Payload = context.Response.DeviceCode; + descriptor.Principal = principal; + descriptor.Status = Statuses.Valid; + descriptor.Subject = principal.GetClaim(Claims.Subject); + + await _tokenManager.UpdateAsync(token, descriptor); + + // Don't return the prepared device code directly from the verification endpoint. + context.Response.DeviceCode = null; + + context.Logger.LogTrace("The reference token entry for device code '{Identifier}' was successfully updated'.", + await _tokenManager.GetIdAsync(token)); + } + } + + /// + /// Contains the logic responsible of creating a refresh token entry. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class CreateRefreshTokenEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictTokenManager _tokenManager; + + public CreateRefreshTokenEntry() => throw new InvalidOperationException(new StringBuilder() + .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") + .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") + .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") + .Append("Alternatively, you can disable the built-in database-based server features by enabling ") + .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") + .ToString()); + + public CreateRefreshTokenEntry( + [NotNull] IOpenIddictApplicationManager applicationManager, + [NotNull] IOpenIddictTokenManager tokenManager) + { + _applicationManager = applicationManager; + _tokenManager = tokenManager; + } + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(UpdateReferenceDeviceCodeEntry.Descriptor.Order + 1_000) + .Build(); + + /// + /// Processes the event. + /// + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public async ValueTask HandleAsync([NotNull] ProcessSignInContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var principal = context.RefreshTokenPrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + + var descriptor = new OpenIddictTokenDescriptor + { + AuthorizationId = principal.GetInternalAuthorizationId(), + CreationDate = principal.GetCreationDate(), + ExpirationDate = principal.GetExpirationDate(), + Principal = principal, + Status = Statuses.Valid, + Subject = principal.GetClaim(Claims.Subject), + Type = TokenTypeHints.RefreshToken + }; + + // If the client application is known, associate it with the token. + if (!string.IsNullOrEmpty(context.Request.ClientId)) + { + var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); + if (application == null) + { + throw new InvalidOperationException("The application entry cannot be found in the database."); + } + + descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); + } + + var token = await _tokenManager.CreateAsync(descriptor); + if (token == null) + { + throw new InvalidOperationException("An unknown error occurred while creating a token entry."); + } - await _tokenManager.PopulateAsync(token, descriptor); - await _tokenManager.UpdateAsync(token); + var identifier = await _tokenManager.GetIdAsync(token); - // Don't return the prepared device code directly from the verification endpoint. - context.Response.DeviceCode = null; + // Attach the token identifier to the principal so that it can be stored in the token. + principal.SetInternalTokenId(identifier); - context.Logger.LogTrace("The reference token entry for device code '{Identifier}' was successfully updated'.", - await _tokenManager.GetIdAsync(token)); + context.Logger.LogTrace("The token entry for refresh token '{Identifier}' was successfully created.", identifier); } } @@ -3400,7 +3655,7 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(UpdateReferenceDeviceCodeEntry.Descriptor.Order + 1_000) + .SetOrder(CreateRefreshTokenEntry.Descriptor.Order + 1_000) .Build(); /// @@ -3468,15 +3723,14 @@ namespace OpenIddict.Server } /// - /// Contains the logic responsible of creating a reference refresh token entry. + /// Contains the logic responsible of converting the refresh token to a reference token. /// Note: this handler is not used when the degraded mode is enabled. /// - public class CreateReferenceRefreshTokenEntry : IOpenIddictServerHandler + public class ConvertReferenceRefreshToken : IOpenIddictServerHandler { - private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictTokenManager _tokenManager; - public CreateReferenceRefreshTokenEntry() => throw new InvalidOperationException(new StringBuilder() + public ConvertReferenceRefreshToken() => throw new InvalidOperationException(new StringBuilder() .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") @@ -3484,13 +3738,8 @@ namespace OpenIddict.Server .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") .ToString()); - public CreateReferenceRefreshTokenEntry( - [NotNull] IOpenIddictApplicationManager applicationManager, - [NotNull] IOpenIddictTokenManager tokenManager) - { - _applicationManager = applicationManager; - _tokenManager = tokenManager; - } + public ConvertReferenceRefreshToken([NotNull] IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. @@ -3499,8 +3748,9 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() .AddFilter() - .UseScopedHandler() + .UseScopedHandler() .SetOrder(GenerateIdentityModelRefreshToken.Descriptor.Order + 1_000) .Build(); @@ -3523,6 +3773,24 @@ namespace OpenIddict.Server return; } + var principal = context.RefreshTokenPrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + + var identifier = principal.GetInternalTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException("The token identifier cannot be extracted from the principal."); + } + + var token = await _tokenManager.FindByIdAsync(identifier); + if (token == null) + { + throw new InvalidOperationException("The token entry cannot be found in the database."); + } + // Generate a new crypto-secure random identifier that will be substituted to the token. var data = new byte[256 / 8]; #if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS @@ -3531,39 +3799,21 @@ namespace OpenIddict.Server using var generator = RandomNumberGenerator.Create(); generator.GetBytes(data); #endif - var descriptor = new OpenIddictTokenDescriptor - { - AuthorizationId = context.RefreshTokenPrincipal.GetInternalAuthorizationId(), - CreationDate = context.RefreshTokenPrincipal.GetCreationDate(), - ExpirationDate = context.RefreshTokenPrincipal.GetExpirationDate(), - Payload = context.Response.RefreshToken, - Principal = context.RefreshTokenPrincipal, - ReferenceId = Base64UrlEncoder.Encode(data), - Status = Statuses.Valid, - Subject = context.RefreshTokenPrincipal.GetClaim(Claims.Subject), - Type = TokenTypeHints.RefreshToken - }; - - // If the client application is known, associate it with the token. - if (!string.IsNullOrEmpty(context.Request.ClientId)) - { - var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); - if (application == null) - { - throw new InvalidOperationException("The application entry cannot be found in the database."); - } + var descriptor = new OpenIddictTokenDescriptor(); + await _tokenManager.PopulateAsync(descriptor, token); - descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); - } + // Attach the generated token to the token entry, persist the change + // and replace the returned token by the reference identifier. + descriptor.Payload = context.Response.RefreshToken; + descriptor.Principal = principal; + descriptor.ReferenceId = Base64UrlEncoder.Encode(data); - var token = await _tokenManager.CreateAsync(descriptor); + await _tokenManager.UpdateAsync(token, descriptor); - context.RefreshTokenPrincipal.SetInternalTokenId(await _tokenManager.GetIdAsync(token)); context.Response.RefreshToken = descriptor.ReferenceId; - context.Logger.LogTrace("The reference token entry for refresh token '{Identifier}' was successfully " + - "created with the reference identifier '{ReferenceId}'.", - await _tokenManager.GetIdAsync(token), descriptor.ReferenceId); + context.Logger.LogTrace("The token entry for refresh token '{Identifier}' was successfully converted to a " + + "reference token with the identifier '{ReferenceId}'.", identifier, descriptor.ReferenceId); } } @@ -3580,7 +3830,7 @@ namespace OpenIddict.Server .AddFilter() .AddFilter() .UseSingletonHandler() - .SetOrder(CreateReferenceRefreshTokenEntry.Descriptor.Order + 1_000) + .SetOrder(ConvertReferenceRefreshToken.Descriptor.Order + 1_000) .Build(); /// @@ -3597,16 +3847,117 @@ namespace OpenIddict.Server throw new ArgumentNullException(nameof(context)); } + var principal = context.UserCodePrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + var identifier = context.DeviceCodePrincipal.GetInternalTokenId(); if (!string.IsNullOrEmpty(identifier)) { - context.UserCodePrincipal.SetClaim(Claims.Private.DeviceCodeId, identifier); + principal.SetClaim(Claims.Private.DeviceCodeId, identifier); } return default; } } + /// + /// Contains the logic responsible of creating a user code entry. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class CreateUserCodeEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictTokenManager _tokenManager; + + public CreateUserCodeEntry() => throw new InvalidOperationException(new StringBuilder() + .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") + .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") + .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") + .Append("Alternatively, you can disable the built-in database-based server features by enabling ") + .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") + .ToString()); + + public CreateUserCodeEntry( + [NotNull] IOpenIddictApplicationManager applicationManager, + [NotNull] IOpenIddictTokenManager tokenManager) + { + _applicationManager = applicationManager; + _tokenManager = tokenManager; + } + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(AttachDeviceCodeIdentifier.Descriptor.Order + 1_000) + .Build(); + + /// + /// Processes the event. + /// + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public async ValueTask HandleAsync([NotNull] ProcessSignInContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var principal = context.UserCodePrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + + var descriptor = new OpenIddictTokenDescriptor + { + AuthorizationId = principal.GetInternalAuthorizationId(), + CreationDate = principal.GetCreationDate(), + ExpirationDate = principal.GetExpirationDate(), + Principal = principal, + Status = Statuses.Valid, + Subject = null, // User codes are not bound to a user until authorization is granted. + Type = TokenTypeHints.UserCode + }; + + // If the client application is known, associate it with the token. + if (!string.IsNullOrEmpty(context.Request.ClientId)) + { + var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); + if (application == null) + { + throw new InvalidOperationException("The application entry cannot be found in the database."); + } + + descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); + } + + var token = await _tokenManager.CreateAsync(descriptor); + if (token == null) + { + throw new InvalidOperationException("An unknown error occurred while creating a token entry."); + } + + var identifier = await _tokenManager.GetIdAsync(token); + + // Attach the token identifier to the principal so that it can be stored in the token. + principal.SetInternalTokenId(identifier); + + context.Logger.LogTrace("The token entry for user code '{Identifier}' was successfully created.", identifier); + } + } + /// /// Contains the logic responsible of generating a user code using IdentityModel. /// @@ -3619,7 +3970,7 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(AttachDeviceCodeIdentifier.Descriptor.Order + 1_000) + .SetOrder(CreateUserCodeEntry.Descriptor.Order + 1_000) .Build(); /// @@ -3676,15 +4027,14 @@ namespace OpenIddict.Server } /// - /// Contains the logic responsible of creating a reference user code entry. + /// Contains the logic responsible of converting the user code to a reference token. /// Note: this handler is not used when the degraded mode is enabled. /// - public class CreateReferenceUserCodeEntry : IOpenIddictServerHandler + public class ConvertReferenceUserCode : IOpenIddictServerHandler { - private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictTokenManager _tokenManager; - public CreateReferenceUserCodeEntry() => throw new InvalidOperationException(new StringBuilder() + public ConvertReferenceUserCode() => throw new InvalidOperationException(new StringBuilder() .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") @@ -3692,13 +4042,8 @@ namespace OpenIddict.Server .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") .ToString()); - public CreateReferenceUserCodeEntry( - [NotNull] IOpenIddictApplicationManager applicationManager, - [NotNull] IOpenIddictTokenManager tokenManager) - { - _applicationManager = applicationManager; - _tokenManager = tokenManager; - } + public ConvertReferenceUserCode([NotNull] IOpenIddictTokenManager tokenManager) + => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. @@ -3707,8 +4052,9 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + // Note: user codes are always reference tokens. .AddFilter() - .UseScopedHandler() + .UseScopedHandler() .SetOrder(GenerateIdentityModelUserCode.Descriptor.Order + 1_000) .Build(); @@ -3731,6 +4077,24 @@ namespace OpenIddict.Server return; } + var principal = context.UserCodePrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + + var identifier = principal.GetInternalTokenId(); + if (string.IsNullOrEmpty(identifier)) + { + throw new InvalidOperationException("The token identifier cannot be extracted from the principal."); + } + + var token = await _tokenManager.FindByIdAsync(identifier); + if (token == null) + { + throw new InvalidOperationException("The token entry cannot be found in the database."); + } + // Note: unlike other reference tokens, user codes are meant to be used by humans, // who may have to enter it in a web form. To ensure it remains easy enough to type // even by users with non-Latin keyboards, user codes generated by OpenIddict are @@ -3751,39 +4115,21 @@ namespace OpenIddict.Server builder.AppendFormat(CultureInfo.InvariantCulture, "{0:D4}", BitConverter.ToUInt32(data, index) % 10000); } - var descriptor = new OpenIddictTokenDescriptor - { - AuthorizationId = context.UserCodePrincipal.GetInternalAuthorizationId(), - CreationDate = context.UserCodePrincipal.GetCreationDate(), - ExpirationDate = context.UserCodePrincipal.GetExpirationDate(), - Payload = context.Response.UserCode, - Principal = context.UserCodePrincipal, - ReferenceId = builder.ToString(), - Status = Statuses.Valid, - Subject = null, // User codes are not bound to a user until authorization is granted. - Type = TokenTypeHints.UserCode - }; - - // If the client application is known, associate it with the token. - if (!string.IsNullOrEmpty(context.Request.ClientId)) - { - var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); - if (application == null) - { - throw new InvalidOperationException("The application entry cannot be found in the database."); - } + var descriptor = new OpenIddictTokenDescriptor(); + await _tokenManager.PopulateAsync(descriptor, token); - descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); - } + // Attach the generated token to the token entry, persist the change + // and replace the returned token by the reference identifier. + descriptor.Payload = context.Response.UserCode; + descriptor.Principal = principal; + descriptor.ReferenceId = builder.ToString(); - var token = await _tokenManager.CreateAsync(descriptor); + await _tokenManager.UpdateAsync(token, descriptor); - context.UserCodePrincipal.SetInternalTokenId(await _tokenManager.GetIdAsync(token)); - context.Response.UserCode = builder.ToString(); + context.Response.UserCode = descriptor.ReferenceId; - context.Logger.LogTrace("The reference token entry for user code '{Identifier}' was successfully " + - "created with the reference identifier '{ReferenceId}'.", - await _tokenManager.GetIdAsync(token), descriptor.ReferenceId); + context.Logger.LogTrace("The token entry for user code '{Identifier}' was successfully converted to a " + + "reference token with the identifier '{ReferenceId}'.", identifier, descriptor.ReferenceId); } } @@ -3800,7 +4146,7 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(CreateReferenceUserCodeEntry.Descriptor.Order + 1_000) + .SetOrder(ConvertReferenceUserCode.Descriptor.Order + 1_000) .Build(); /// @@ -3919,6 +4265,101 @@ namespace OpenIddict.Server } } + /// + /// Contains the logic responsible of creating an identity token entry. + /// Note: this handler is not used when the degraded mode is enabled. + /// + public class CreateIdentityTokenEntry : IOpenIddictServerHandler + { + private readonly IOpenIddictApplicationManager _applicationManager; + private readonly IOpenIddictTokenManager _tokenManager; + + public CreateIdentityTokenEntry() => throw new InvalidOperationException(new StringBuilder() + .AppendLine("The core services must be registered when enabling the OpenIddict server feature.") + .Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") + .AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") + .Append("Alternatively, you can disable the built-in database-based server features by enabling ") + .Append("the degraded mode with 'services.AddOpenIddict().AddServer().EnableDegradedMode()'.") + .ToString()); + + public CreateIdentityTokenEntry( + [NotNull] IOpenIddictApplicationManager applicationManager, + [NotNull] IOpenIddictTokenManager tokenManager) + { + _applicationManager = applicationManager; + _tokenManager = tokenManager; + } + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictServerHandlerDescriptor Descriptor { get; } + = OpenIddictServerHandlerDescriptor.CreateBuilder() + .AddFilter() + .AddFilter() + .AddFilter() + .UseScopedHandler() + .SetOrder(AttachTokenDigests.Descriptor.Order + 1_000) + .Build(); + + /// + /// Processes the event. + /// + /// The context associated with the event to process. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public async ValueTask HandleAsync([NotNull] ProcessSignInContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var principal = context.IdentityTokenPrincipal; + if (principal == null) + { + throw new InvalidOperationException("A token entry cannot be created from a null principal."); + } + + var descriptor = new OpenIddictTokenDescriptor + { + AuthorizationId = principal.GetInternalAuthorizationId(), + CreationDate = principal.GetCreationDate(), + ExpirationDate = principal.GetExpirationDate(), + Principal = principal, + Status = Statuses.Valid, + Subject = principal.GetClaim(Claims.Subject), + Type = TokenTypeHints.IdToken + }; + + // If the client application is known, associate it with the token. + if (!string.IsNullOrEmpty(context.Request.ClientId)) + { + var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); + if (application == null) + { + throw new InvalidOperationException("The application entry cannot be found in the database."); + } + + descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); + } + + var token = await _tokenManager.CreateAsync(descriptor); + if (token == null) + { + throw new InvalidOperationException("An unknown error occurred while creating a token entry."); + } + + var identifier = await _tokenManager.GetIdAsync(token); + + // Attach the token identifier to the principal so that it can be stored in the token. + principal.SetInternalTokenId(identifier); + + context.Logger.LogTrace("The token entry for identity token '{Identifier}' was successfully created.", identifier); + } + } + /// /// Contains the logic responsible of generating an identity token using IdentityModel. /// @@ -3931,7 +4372,7 @@ namespace OpenIddict.Server = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() - .SetOrder(AttachTokenDigests.Descriptor.Order + 1_000) + .SetOrder(CreateIdentityTokenEntry.Descriptor.Order + 1_000) .Build(); /// diff --git a/src/OpenIddict.Server/OpenIddictServerOptions.cs b/src/OpenIddict.Server/OpenIddictServerOptions.cs index 13118b84..480e2ec3 100644 --- a/src/OpenIddict.Server/OpenIddictServerOptions.cs +++ b/src/OpenIddict.Server/OpenIddictServerOptions.cs @@ -27,14 +27,16 @@ namespace OpenIddict.Server /// /// 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. /// public IList EncryptionCredentials { get; } = new List(); /// /// 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. /// public IList SigningCredentials { get; } = new List(); @@ -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. /// public bool DisableAccessTokenEncryption { get; set; } @@ -234,8 +237,9 @@ namespace OpenIddict.Server /// /// 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). /// public bool DisableTokenStorage { get; set; } @@ -307,14 +311,15 @@ namespace OpenIddict.Server }; /// - /// Gets or sets a boolean indicating whether reference access tokens should be used. - /// When set to true, 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 true, 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. /// - public bool UseReferenceAccessTokens { get; set; } + public bool UseReferenceTokens { get; set; } /// /// 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). /// public bool UseRollingTokens { get; set; } } diff --git a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs index 355004ea..fbc1cd76 100644 --- a/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs +++ b/src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreConfiguration.cs @@ -61,7 +61,7 @@ namespace OpenIddict.Validation.AspNetCore /// /// Ensures that the authentication configuration is in a consistent and valid state. /// - /// The authentication scheme associated with the handler instance. + /// The name of the options instance to configure, if applicable. /// The options instance to initialize. public void PostConfigure([CanBeNull] string name, [NotNull] AuthenticationOptions options) { diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs index 815b8013..d01c3a17 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionConfiguration.cs +++ b/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. /// - /// The authentication scheme associated with the handler instance. + /// The name of the options instance to configure, if applicable. /// The options instance to initialize. public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictValidationDataProtectionOptions options) { diff --git a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs b/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs index f2a021f5..d4b1982c 100644 --- a/src/OpenIddict.Validation.DataProtection/OpenIddictValidationDataProtectionHandlers.cs +++ b/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) : diff --git a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs index 6734e55a..55477edf 100644 --- a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationConfiguration.cs +++ b/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 /// /// Contains the methods required to ensure that the OpenIddict validation/server integration configuration is valid. /// - public class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions + public class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions, + IPostConfigureOptions { private readonly IOptionsMonitor _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; + } + + /// + /// Populates the default OpenIddict validation/server integration options + /// and ensures that the configuration is in a consistent and valid state. + /// + /// The name of the options instance to configure, if applicable. + /// The options instance to initialize. + 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()); + } } } } diff --git a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs b/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs index c94d51e8..8baeb221 100644 --- a/src/OpenIddict.Validation.ServerIntegration/OpenIddictValidationServerIntegrationExtensions.cs +++ b/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, OpenIddictValidationServerIntegrationConfiguration>()); + // Note: TryAddEnumerable() is used here to ensure the initializers are registered only once. + builder.Services.TryAddEnumerable(new[] + { + ServiceDescriptor.Singleton, OpenIddictValidationServerIntegrationConfiguration>(), + ServiceDescriptor.Singleton, OpenIddictValidationServerIntegrationConfiguration>() + }); return new OpenIddictValidationServerIntegrationBuilder(builder.Services); } diff --git a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs index db06c0ad..b2b22c2a 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationBuilder.cs @@ -407,12 +407,23 @@ namespace Microsoft.Extensions.DependencyInjection /// /// 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. /// /// The . public OpenIddictValidationBuilder EnableAuthorizationValidation() => Configure(options => options.EnableAuthorizationValidation = true); + /// + /// 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. + /// + /// The . + public OpenIddictValidationBuilder EnableTokenValidation() + => Configure(options => options.EnableTokenValidation = true); + /// /// 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)); } - /// - /// 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. - /// - /// The . - public OpenIddictValidationBuilder UseReferenceAccessTokens() - => Configure(options => options.UseReferenceAccessTokens = true); - /// /// Determines whether the specified object is equal to the current object. /// diff --git a/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs b/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs index 7a399add..3c400d77 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationConfiguration.cs +++ b/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. /// - /// The authentication scheme associated with the handler instance. + /// The name of the options instance to configure, if applicable. /// The options instance to initialize. public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictValidationOptions options) { diff --git a/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs b/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs index f834eb63..6aa924d0 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationExtensions.cs +++ b/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(); - builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); // Note: TryAddEnumerable() is used here to ensure the initializer is registered only once. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs index 8ad59c18..104d6eca 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationHandlerFilters.cs @@ -32,9 +32,9 @@ namespace OpenIddict.Validation } /// - /// 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. /// - public class RequireReferenceAccessTokensEnabled : IOpenIddictValidationHandlerFilter + public class RequireTokenValidationEnabled : IOpenIddictValidationHandlerFilter { public ValueTask IsActiveAsync([NotNull] BaseContext context) { @@ -43,7 +43,7 @@ namespace OpenIddict.Validation throw new ArgumentNullException(nameof(context)); } - return new ValueTask(context.Options.UseReferenceAccessTokens); + return new ValueTask(context.Options.EnableTokenValidation); } } } diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs index 26dff39d..960dfee6 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs +++ b/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 /// public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() + .AddFilter() .UseScopedHandler() .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 /// public static OpenIddictValidationHandlerDescriptor Descriptor { get; } = OpenIddictValidationHandlerDescriptor.CreateBuilder() - .AddFilter() + .AddFilter() .UseScopedHandler() .SetOrder(MapInternalClaims.Descriptor.Order + 1_000) .Build(); @@ -523,6 +531,68 @@ namespace OpenIddict.Validation } } + /// + /// 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. + /// + public class ValidateTokenEntry : IOpenIddictValidationHandler + { + 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; + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictValidationHandlerDescriptor Descriptor { get; } + = OpenIddictValidationHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseScopedHandler() + .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)); + } + } + /// /// 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() .AddFilter() .UseScopedHandler() - .SetOrder(ValidateAudience.Descriptor.Order + 1_000) + .SetOrder(ValidateTokenEntry.Descriptor.Order + 1_000) .Build(); public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context) diff --git a/src/OpenIddict.Validation/OpenIddictValidationOptions.cs b/src/OpenIddict.Validation/OpenIddictValidationOptions.cs index 5b6f93d0..8d14f410 100644 --- a/src/OpenIddict.Validation/OpenIddictValidationOptions.cs +++ b/src/OpenIddict.Validation/OpenIddictValidationOptions.cs @@ -46,19 +46,19 @@ namespace OpenIddict.Validation /// /// 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. /// public bool EnableAuthorizationValidation { get; set; } /// - /// Gets or sets a boolean indicating whether reference access tokens should be used. - /// When set to true, 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. /// - public bool UseReferenceAccessTokens { get; set; } + public bool EnableTokenValidation { get; set; } /// /// Gets or sets the absolute URL of the OAuth 2.0/OpenID Connect server. diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs index a273e878..0a51ba19 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs +++ b/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()); + 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()); + 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()); + options.DisableAuthorizationStorage(); + options.DisableTokenStorage(); + options.DisableSlidingExpiration(); + options.Services.AddSingleton(manager); options.AddEventHandler(builder => @@ -1398,6 +1409,11 @@ namespace OpenIddict.Server.FunctionalTests var client = CreateClient(options => { + options.SetRevocationEndpointUris(Array.Empty()); + options.DisableAuthorizationStorage(); + options.DisableTokenStorage(); + options.DisableSlidingExpiration(); + options.Services.AddSingleton(manager); options.AddEventHandler(builder => diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs index c7b36bfa..b6a8bb17 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Exchange.cs +++ b/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()); + options.DisableTokenStorage(); + options.DisableSlidingExpiration(); options.Services.AddSingleton(manager); @@ -2832,6 +2835,9 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) .ReturnsAsync(true); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); })); options.Services.AddSingleton(manager); @@ -2914,6 +2920,9 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.GetAuthorizationIdAsync(token, It.IsAny())) .ReturnsAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0"); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); })); options.Services.AddSingleton(manager); @@ -3309,6 +3318,9 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) .ReturnsAsync(true); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); }); var client = CreateClient(options => diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs index 4fe8a18a..4ba20eb9 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Introspection.cs +++ b/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); }); diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs index 6bff9aaf..afa2c260 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Revocation.cs +++ b/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(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(builder => { @@ -570,7 +528,6 @@ namespace OpenIddict.Server.FunctionalTests var client = CreateClient(options => { options.EnableDegradedMode(); - options.UseReferenceAccessTokens(); options.AddEventHandler(builder => { @@ -615,7 +572,6 @@ namespace OpenIddict.Server.FunctionalTests var client = CreateClient(options => { options.EnableDegradedMode(); - options.UseReferenceAccessTokens(); options.AddEventHandler(builder => { @@ -663,7 +619,6 @@ namespace OpenIddict.Server.FunctionalTests var client = CreateClient(options => { options.EnableDegradedMode(); - options.UseReferenceAccessTokens(); options.AddEventHandler(builder => { @@ -878,7 +833,6 @@ namespace OpenIddict.Server.FunctionalTests var client = CreateClient(options => { options.EnableDegradedMode(); - options.UseReferenceAccessTokens(); options.AddEventHandler(builder => { @@ -923,7 +877,6 @@ namespace OpenIddict.Server.FunctionalTests var client = CreateClient(options => { options.EnableDegradedMode(); - options.UseReferenceAccessTokens(); options.AddEventHandler(builder => { @@ -971,7 +924,6 @@ namespace OpenIddict.Server.FunctionalTests var client = CreateClient(options => { options.EnableDegradedMode(); - options.UseReferenceAccessTokens(); options.AddEventHandler(builder => { @@ -1014,7 +966,6 @@ namespace OpenIddict.Server.FunctionalTests var client = CreateClient(options => { options.EnableDegradedMode(); - options.UseReferenceAccessTokens(); options.AddEventHandler(builder => { diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs index c5fac53c..b3260951 100644 --- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs +++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.cs @@ -2649,6 +2649,9 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) .ReturnsAsync(true); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); }); var client = CreateClient(options => @@ -2798,11 +2801,15 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.TryRedeemAsync(token, It.IsAny())) .ReturnsAsync(true); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); }); var client = CreateClient(options => { options.UseRollingTokens(); + options.DisableAuthorizationStorage(); options.AddEventHandler(builder => { @@ -2867,6 +2874,7 @@ namespace OpenIddict.Server.FunctionalTests var client = CreateClient(options => { options.UseRollingTokens(); + options.DisableAuthorizationStorage(); options.AddEventHandler(builder => { @@ -2921,6 +2929,9 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.HasStatusAsync(token, Statuses.Valid, It.IsAny())) .ReturnsAsync(true); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .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())) .Returns(tokens.ToAsyncEnumerable()); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .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())) .Returns(tokens.ToAsyncEnumerable()); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .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())) .ReturnsAsync(true); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .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())) .ReturnsAsync(true); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); }); var client = CreateClient(options => @@ -3280,6 +3303,9 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny())) .ReturnsAsync(value: null); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); }); var client = CreateClient(options => @@ -3341,6 +3367,9 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.GetExpirationDateAsync(token, It.IsAny())) .ReturnsAsync(DateTimeOffset.Now + TimeSpan.FromDays(1)); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictToken()); }); var client = CreateClient(options => @@ -3404,6 +3433,9 @@ namespace OpenIddict.Server.FunctionalTests mock.Setup(manager => manager.TryExtendAsync(token, It.IsAny(), It.IsAny())) .ReturnsAsync(false); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .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())) .ReturnsAsync(new OpenIddictAuthorization()); + + mock.Setup(manager => manager.CreateAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(new OpenIddictAuthorization()); }); var client = CreateClient(options =>