From 29c6668d451ed1e5c0adce1da7a89f0a5525a1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Tue, 24 Feb 2026 17:36:12 +0100 Subject: [PATCH] Change how authentication demands are marshalled and support token binding certificates/additional token request parameters for interactive flows --- .../InteractiveService.cs | 28 +- ...enIddictClientSystemIntegrationHandlers.cs | 677 ++++++++++-------- .../OpenIddictClientWebIntegrationHandlers.cs | 5 +- .../OpenIddictClientEvents.cs | 6 + .../OpenIddictClientExtensions.cs | 1 + .../OpenIddictClientHandlerFilters.cs | 14 + .../OpenIddictClientHandlers.cs | 16 +- .../OpenIddictClientModels.cs | 22 + .../OpenIddictClientService.cs | 5 +- 9 files changed, 459 insertions(+), 315 deletions(-) diff --git a/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs b/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs index e3d83376..d4d4ec69 100644 --- a/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs +++ b/sandbox/OpenIddict.Sandbox.Console.Client/InteractiveService.cs @@ -44,6 +44,19 @@ public class InteractiveService : BackgroundService if (await AuthenticateUserInteractivelyAsync(registration, configuration, stoppingToken)) { + // Note: the OpenIddict server stack supports mTLS-based token binding for public clients: + // while these clients cannot authenticate using a TLS client certificate, the certificate + // can be used to bind the refresh (and access) tokens returned by the authorization server + // to the client application, which prevents such tokens from being used without providing a + // proof-of-possession matching the TLS client certificate used when the token was acquired. + // + // While this sample deliberately doesn't store the generated certificate in a persistent + // location, the certificate used for token binding should typically be stored in the user + // certificate store to be reloaded across application restarts in a real-world application. + var certificate = configuration.TlsClientCertificateBoundAccessTokens is true + ? GenerateEphemeralTlsClientCertificate() + : null; + var flow = await GetSelectedFlowAsync(registration, configuration, stoppingToken); AnsiConsole.MarkupLine("[cyan]Launching the system browser.[/]"); @@ -64,7 +77,8 @@ public class InteractiveService : BackgroundService var response = await _service.AuthenticateInteractivelyAsync(new() { CancellationToken = stoppingToken, - Nonce = result.Nonce + Nonce = result.Nonce, + TokenBindingCertificate = certificate }); AnsiConsole.MarkupLine("[green]Interactive authentication successful:[/]"); @@ -112,7 +126,8 @@ public class InteractiveService : BackgroundService { CancellationToken = stoppingToken, ProviderName = provider, - RefreshToken = response.RefreshToken + RefreshToken = response.RefreshToken, + TokenBindingCertificate = certificate })).Principal)); } @@ -151,15 +166,6 @@ public class InteractiveService : BackgroundService var type = await GetSelectedGrantTypeAsync(registration, configuration, stoppingToken); if (type is GrantTypes.DeviceCode) { - // Note: the OpenIddict server stack supports mTLS-based token binding for public clients: - // while these clients cannot authenticate using a TLS client certificate, the certificate - // can be used to bind the refresh (and access) tokens returned by the authorization server - // to the client application, which prevents such tokens from being used without providing a - // proof-of-possession matching the TLS client certificate used when the token was acquired. - // - // While this sample deliberately doesn't store the generated certificate in a persistent - // location, the certificate used for token binding should typically be stored in the user - // certificate store to be reloaded across application restarts in a real-world application. var certificate = configuration.TlsClientCertificateBoundAccessTokens is true ? GenerateEphemeralTlsClientCertificate() : null; diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs index 16b5df0d..3caab7e6 100644 --- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs +++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs @@ -40,25 +40,20 @@ public static partial class OpenIddictClientSystemIntegrationHandlers WaitMarshalledAuthentication.Descriptor, RestoreClientRegistrationFromMarshalledContext.Descriptor, - RestoreStateTokenFromMarshalledAuthentication.Descriptor, - RestoreStateTokenPrincipalFromMarshalledAuthentication.Descriptor, - RestoreHostAuthenticationPropertiesFromMarshalledAuthentication.Descriptor, + + EvaluateValidatedUpfrontTokensForMarshalledContext.Descriptor, + ResolveValidatedStateTokenFromMarshalledContext.Descriptor, + EvaluateValidatedFrontchannelTokensForMarshalledContext.Descriptor, + ResolveValidatedFrontchannelTokensFromMarshalledContext.Descriptor, + EvaluateValidatedBackchannelTokensForMarshalledContext.Descriptor, + + DisableStateTokenRedeeming.Descriptor, + DisableTokenRequestSending.Descriptor, + DisableUserInfoRequestSending.Descriptor, RedirectProtocolActivation.Descriptor, ResolveRequestForgeryProtection.Descriptor, - RestoreFrontchannelTokensFromMarshalledAuthentication.Descriptor, - RestoreFrontchannelIdentityTokenPrincipalFromMarshalledAuthentication.Descriptor, - RestoreFrontchannelAccessTokenPrincipalFromMarshalledAuthentication.Descriptor, - RestoreAuthorizationCodePrincipalFromMarshalledAuthentication.Descriptor, - RestoreTokenResponseFromMarshalledAuthentication.Descriptor, - RestoreBackchannelTokensFromMarshalledAuthentication.Descriptor, - RestoreBackchannelIdentityTokenPrincipalFromMarshalledAuthentication.Descriptor, - RestoreBackchannelAccessTokenPrincipalFromMarshalledAuthentication.Descriptor, - RestoreRefreshTokenPrincipalFromMarshalledAuthentication.Descriptor, - RestoreUserInfoDetailsFromMarshalledAuthentication.Descriptor, - RestoreMergedPrincipalFromMarshalledAuthentication.Descriptor, - CompleteAuthenticationOperation.Descriptor, UntrackMarshalledAuthenticationOperation.Descriptor, @@ -613,7 +608,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers throw new InvalidOperationException(SR.GetResourceString(SR.ID0379)); } - // At this point, user authentication demands cannot complete until the authorization response has been + // At this point, the user authentication demand cannot complete until the authorization response has been // returned to the redirection endpoint (materialized as a registered protocol activation URI) and handled // by OpenIddict via the ProcessRequest event. Since it is asynchronous by nature, this process requires // using a signal mechanism to unblock the authentication operation once it is complete. For that, the @@ -697,8 +692,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers (context.Configuration, context.Registration) = context.EndpointType switch { - // When the authentication context is marshalled, restore the - // issuer registration and configuration from the other instance. + // When the authentication demand is marshalled from a different context, + // restore the registration and configuration from the other instance. OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) => (notification.Configuration, notification.Registration), @@ -710,14 +705,14 @@ public static partial class OpenIddictClientSystemIntegrationHandlers } /// - /// Contains the logic responsible for restoring the state token - /// from the marshalled authentication context, if applicable. + /// Contains the logic responsible for determining the types of + /// tokens to validate upfront when the context is marshalled. /// - public sealed class RestoreStateTokenFromMarshalledAuthentication : IOpenIddictClientHandler + public sealed class EvaluateValidatedUpfrontTokensForMarshalledContext : IOpenIddictClientHandler { private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreStateTokenFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) + public EvaluateValidatedUpfrontTokensForMarshalledContext(OpenIddictClientSystemIntegrationMarshal marshal) => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); /// @@ -726,8 +721,49 @@ public static partial class OpenIddictClientSystemIntegrationHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(ResolveValidatedStateToken.Descriptor.Order + 500) + .UseSingletonHandler() + .SetOrder(EvaluateValidatedUpfrontTokens.Descriptor.Order + 250) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) + { + ArgumentNullException.ThrowIfNull(context); + + Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); + + // When the authentication demand is marshalled from a different context, always + // extract and validate the state token to ensure the authentication details + // contained in the state token principal can be used to validate the operation. + if (context.EndpointType is OpenIddictClientEndpointType.Unknown && _marshal.IsTracked(context.Nonce)) + { + context.ExtractStateToken = context.RequireStateToken = true; + context.ValidateStateToken = context.RejectStateToken = true; + } + + return ValueTask.CompletedTask; + } + } + + /// + /// Contains the logic responsible for resolving the state token to validate upfront from the marshalled context. + /// + public sealed class ResolveValidatedStateTokenFromMarshalledContext : IOpenIddictClientHandler + { + private readonly OpenIddictClientSystemIntegrationMarshal _marshal; + + public ResolveValidatedStateTokenFromMarshalledContext(OpenIddictClientSystemIntegrationMarshal marshal) + => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ResolveValidatedStateToken.Descriptor.Order + 250) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); @@ -740,12 +776,12 @@ public static partial class OpenIddictClientSystemIntegrationHandlers context.StateToken = context.EndpointType switch { - // When the authentication context is marshalled, restore the state token from the other instance. + // When the authentication demand is marshalled from a different context, + // always restore the state token from the instance that extracted it. OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) => notification.StateToken, - // Otherwise, don't alter the current context. - _ => context.StateToken + _ => null }; return ValueTask.CompletedTask; @@ -753,14 +789,14 @@ public static partial class OpenIddictClientSystemIntegrationHandlers } /// - /// Contains the logic responsible for restoring the state token - /// principal from the marshalled authentication context, if applicable. + /// Contains the logic responsible for determining the set of + /// frontchannel tokens to validate when the context is marshalled. /// - public sealed class RestoreStateTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler + public sealed class EvaluateValidatedFrontchannelTokensForMarshalledContext : IOpenIddictClientHandler { private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreStateTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) + public EvaluateValidatedFrontchannelTokensForMarshalledContext(OpenIddictClientSystemIntegrationMarshal marshal) => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); /// @@ -769,8 +805,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(ValidateStateToken.Descriptor.Order + 500) + .UseSingletonHandler() + .SetOrder(EvaluateValidatedFrontchannelTokens.Descriptor.Order + 250) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); @@ -781,30 +817,30 @@ public static partial class OpenIddictClientSystemIntegrationHandlers Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - context.StateTokenPrincipal = context.EndpointType switch + // When the authentication demand is expected to be marshalled to a different context, + // always skip the validation of all the frontchannel tokens by default as the security + // principals they contain are not needed to marshal the authentication demand. + if (context.EndpointType is + OpenIddictClientEndpointType.Redirection or + OpenIddictClientEndpointType.PostLogoutRedirection && _marshal.IsTracked(context.Nonce)) { - // When the authentication context is marshalled, restore - // the state token principal from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => notification.StateTokenPrincipal, - - // Otherwise, don't alter the current context. - _ => context.StateTokenPrincipal - }; + context.ValidateAuthorizationCode = context.RejectAuthorizationCode = false; + context.ValidateFrontchannelAccessToken = context.RejectFrontchannelAccessToken = false; + context.ValidateFrontchannelIdentityToken = context.RejectFrontchannelIdentityToken = false; + } return ValueTask.CompletedTask; } } /// - /// Contains the logic responsible for restoring the host authentication - /// properties from the marshalled authentication context, if applicable. + /// Contains the logic responsible for resolving the frontchannel tokens from the marshalled context. /// - public sealed class RestoreHostAuthenticationPropertiesFromMarshalledAuthentication : IOpenIddictClientHandler + public sealed class ResolveValidatedFrontchannelTokensFromMarshalledContext : IOpenIddictClientHandler { private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreHostAuthenticationPropertiesFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) + public ResolveValidatedFrontchannelTokensFromMarshalledContext(OpenIddictClientSystemIntegrationMarshal marshal) => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); /// @@ -813,8 +849,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers public static OpenIddictClientHandlerDescriptor Descriptor { get; } = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() - .UseSingletonHandler() - .SetOrder(ResolveHostAuthenticationPropertiesFromStateToken.Descriptor.Order + 500) + .UseSingletonHandler() + .SetOrder(ResolveValidatedFrontchannelTokens.Descriptor.Order + 250) .SetType(OpenIddictClientHandlerType.BuiltIn) .Build(); @@ -825,21 +861,271 @@ public static partial class OpenIddictClientSystemIntegrationHandlers Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - // When the authentication context is marshalled, restore the - // host authentication properties from the other instance. + // When the authentication context is marshalled, restore the frontchannel tokens from the other instance. if (context.EndpointType is OpenIddictClientEndpointType.Unknown && _marshal.TryGetResult(context.Nonce, out var notification)) { - foreach (var property in notification.Properties) - { - context.Properties[property.Key] = property.Value; - } + context.AuthorizationCode = notification.AuthorizationCode; + context.FrontchannelAccessToken = notification.FrontchannelAccessToken; + context.FrontchannelAccessTokenExpirationDate = notification.FrontchannelAccessTokenExpirationDate; + context.FrontchannelIdentityToken = notification.FrontchannelIdentityToken; } return ValueTask.CompletedTask; } } + /// + /// Contains the logic responsible for determining the set of + /// backchannel tokens to validate when the context is marshalled. + /// + public sealed class EvaluateValidatedBackchannelTokensForMarshalledContext : IOpenIddictClientHandler + { + private readonly OpenIddictClientSystemIntegrationMarshal _marshal; + + public EvaluateValidatedBackchannelTokensForMarshalledContext(OpenIddictClientSystemIntegrationMarshal marshal) + => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(EvaluateValidatedBackchannelTokens.Descriptor.Order + 250) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) + { + ArgumentNullException.ThrowIfNull(context); + + Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); + + // When the authentication demand is expected to be marshalled to a different context, + // always skip the validation of all the backchannel tokens by default as the security + // principals they contain are not needed to marshal the authentication demand. + if (context.EndpointType is + OpenIddictClientEndpointType.Redirection or + OpenIddictClientEndpointType.PostLogoutRedirection && _marshal.IsTracked(context.Nonce)) + { + context.ValidateBackchannelAccessToken = context.RejectBackchannelAccessToken = false; + context.ValidateBackchannelIdentityToken = context.RejectBackchannelIdentityToken = false; + context.ValidateIssuedToken = context.RejectIssuedToken = false; + context.ValidateRefreshToken = context.RejectRefreshToken = false; + } + + return ValueTask.CompletedTask; + } + } + + /// + /// Contains the logic responsible for disabling the redeeming of the state token, if applicable. + /// + public sealed class DisableStateTokenRedeeming : IOpenIddictClientHandler + { + private readonly OpenIddictClientSystemIntegrationMarshal _marshal; + + public DisableStateTokenRedeeming(OpenIddictClientSystemIntegrationMarshal marshal) + => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(RedeemStateTokenEntry.Descriptor.Order - 250) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) + { + ArgumentNullException.ThrowIfNull(context); + + Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); + + context.DisableStateTokenRedeeming = context.EndpointType switch + { + // When the authentication demand is expected to be marshalled to a different context, + // disable the redeeming of the state token to ensure it is not in an invalid state + // when the marshalled authentication demand is processed by the other instance. + OpenIddictClientEndpointType.Redirection or + OpenIddictClientEndpointType.PostLogoutRedirection when _marshal.IsTracked(context.Nonce) + => true, + + _ => context.DisableStateTokenRedeeming + }; + + return ValueTask.CompletedTask; + } + } + + /// + /// Contains the logic responsible for preventing a token request from being sent, if applicable. + /// + public sealed class DisableTokenRequestSending : IOpenIddictClientHandler + { + private readonly OpenIddictClientSystemIntegrationMarshal _marshal; + + public DisableTokenRequestSending(OpenIddictClientSystemIntegrationMarshal marshal) + => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(EvaluateTokenRequest.Descriptor.Order + 250) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) + { + ArgumentNullException.ThrowIfNull(context); + + Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); + + context.SendTokenRequest = context.EndpointType switch + { + // When the authentication demand is expected to be marshalled to a different + // context, do not send a token request and let the other instance do it. + OpenIddictClientEndpointType.Redirection or + OpenIddictClientEndpointType.PostLogoutRedirection when _marshal.IsTracked(context.Nonce) + => false, + + _ => context.SendTokenRequest + }; + + return ValueTask.CompletedTask; + } + } + + /// + /// Contains the logic responsible for preventing a userinfo request from being sent, if applicable. + /// + public sealed class DisableUserInfoRequestSending : IOpenIddictClientHandler + { + private readonly OpenIddictClientSystemIntegrationMarshal _marshal; + + public DisableUserInfoRequestSending(OpenIddictClientSystemIntegrationMarshal marshal) + => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(EvaluateUserInfoRequest.Descriptor.Order + 250) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) + { + ArgumentNullException.ThrowIfNull(context); + + Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); + + context.SendUserInfoRequest = context.EndpointType switch + { + // When the authentication demand is expected to be marshalled to a different + // context, do not send a userinfo request and let the other instance do it. + OpenIddictClientEndpointType.Redirection or + OpenIddictClientEndpointType.PostLogoutRedirection when _marshal.IsTracked(context.Nonce) + => false, + + _ => context.SendUserInfoRequest + }; + + return ValueTask.CompletedTask; + } + } + + /// + /// Contains the logic responsible for restoring the state token + /// from the marshalled authentication context, if applicable. + /// + [Obsolete("This class is obsolete and will be removed in a future version.")] + public sealed class RestoreStateTokenFromMarshalledAuthentication : IOpenIddictClientHandler + { + public RestoreStateTokenFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ResolveValidatedStateToken.Descriptor.Order + 500) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; + } + + /// + /// Contains the logic responsible for restoring the state token + /// principal from the marshalled authentication context, if applicable. + /// + [Obsolete("This class is obsolete and will be removed in a future version.")] + public sealed class RestoreStateTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler + { + public RestoreStateTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ValidateStateToken.Descriptor.Order + 500) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; + } + + /// + /// Contains the logic responsible for restoring the host authentication + /// properties from the marshalled authentication context, if applicable. + /// + [Obsolete("This class is obsolete and will be removed in a future version.")] + public sealed class RestoreHostAuthenticationPropertiesFromMarshalledAuthentication : IOpenIddictClientHandler + { + public RestoreHostAuthenticationPropertiesFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); + + /// + /// Gets the default descriptor definition assigned to this handler. + /// + public static OpenIddictClientHandlerDescriptor Descriptor { get; } + = OpenIddictClientHandlerDescriptor.CreateBuilder() + .AddFilter() + .UseSingletonHandler() + .SetOrder(ResolveHostAuthenticationPropertiesFromStateToken.Descriptor.Order + 500) + .SetType(OpenIddictClientHandlerType.BuiltIn) + .Build(); + + /// + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; + } + /// /// Contains the logic responsible for redirecting the protocol activation to /// the instance that initially started the authentication demand, if applicable. @@ -984,12 +1270,11 @@ public static partial class OpenIddictClientSystemIntegrationHandlers /// Contains the logic responsible for restoring the frontchannel tokens /// from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreFrontchannelTokensFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreFrontchannelTokensFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1003,38 +1288,18 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - (context.AuthorizationCode, - context.FrontchannelAccessToken, - context.FrontchannelIdentityToken) = context.EndpointType switch - { - // When the authentication context is marshalled, restore the tokens from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => (notification.AuthorizationCode, notification.FrontchannelAccessToken, notification.FrontchannelIdentityToken), - - // Otherwise, don't alter the current context. - _ => (context.AuthorizationCode, context.FrontchannelAccessToken, context.FrontchannelIdentityToken) - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the frontchannel identity /// token principal from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreFrontchannelIdentityTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreFrontchannelIdentityTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1048,37 +1313,18 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - context.FrontchannelIdentityTokenPrincipal = context.EndpointType switch - { - // When the authentication context is marshalled, restore the - // frontchannel identity token principal from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => notification.FrontchannelIdentityTokenPrincipal, - - // Otherwise, don't alter the current context. - _ => context.FrontchannelIdentityTokenPrincipal - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the frontchannel access /// token principal from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreFrontchannelAccessTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreFrontchannelAccessTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1092,37 +1338,18 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - context.FrontchannelAccessTokenPrincipal = context.EndpointType switch - { - // When the authentication context is marshalled, restore the - // frontchannel access token principal from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => notification.FrontchannelAccessTokenPrincipal, - - // Otherwise, don't alter the current context. - _ => context.FrontchannelAccessTokenPrincipal - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the authorization code /// principal from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreAuthorizationCodePrincipalFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreAuthorizationCodePrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1136,37 +1363,18 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - context.AuthorizationCodePrincipal = context.EndpointType switch - { - // When the authentication context is marshalled, restore the - // authorization code principal from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => notification.AuthorizationCodePrincipal, - - // Otherwise, don't alter the current context. - _ => context.AuthorizationCodePrincipal - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the token response /// from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreTokenResponseFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreTokenResponseFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1180,36 +1388,18 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - context.TokenResponse = context.EndpointType switch - { - // When the authentication context is marshalled, restore the token response from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => notification.TokenResponse, - - // Otherwise, don't alter the current context. - _ => context.TokenResponse - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the backchannel tokens /// from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreBackchannelTokensFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreBackchannelTokensFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1223,38 +1413,18 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - (context.BackchannelAccessToken, - context.BackchannelIdentityToken, - context.RefreshToken) = context.EndpointType switch - { - // When the authentication context is marshalled, restore the tokens from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => (notification.BackchannelAccessToken, notification.BackchannelIdentityToken, notification.RefreshToken), - - // Otherwise, don't alter the current context. - _ => (context.BackchannelAccessToken, context.BackchannelIdentityToken, context.RefreshToken) - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the backchannel identity /// token principal from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreBackchannelIdentityTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreBackchannelIdentityTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1268,37 +1438,18 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - context.BackchannelIdentityTokenPrincipal = context.EndpointType switch - { - // When the authentication context is marshalled, restore the - // frontchannel identity token principal from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => notification.BackchannelIdentityTokenPrincipal, - - // Otherwise, don't alter the current context. - _ => context.BackchannelIdentityTokenPrincipal - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the frontchannel access /// token principal from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreBackchannelAccessTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreBackchannelAccessTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1312,37 +1463,18 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - context.BackchannelAccessTokenPrincipal = context.EndpointType switch - { - // When the authentication context is marshalled, restore the - // frontchannel access token principal from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => notification.BackchannelAccessTokenPrincipal, - - // Otherwise, don't alter the current context. - _ => context.BackchannelAccessTokenPrincipal - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the refresh token /// principal from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreRefreshTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreRefreshTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1356,37 +1488,18 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - context.RefreshTokenPrincipal = context.EndpointType switch - { - // When the authentication context is marshalled, restore - // the refresh token principal from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => notification.RefreshTokenPrincipal, - - // Otherwise, don't alter the current context. - _ => context.RefreshTokenPrincipal - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the userinfo details /// from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreUserInfoDetailsFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreUserInfoDetailsFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1400,35 +1513,17 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - (context.UserInfoResponse, context.UserInfoTokenPrincipal, context.UserInfoToken) = context.EndpointType switch - { - // When the authentication context is marshalled, restore the userinfo details from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => (notification.UserInfoResponse, notification.UserInfoTokenPrincipal, notification.UserInfoToken), - - // Otherwise, don't alter the current context. - _ => (context.UserInfoResponse, context.UserInfoTokenPrincipal, context.UserInfoToken) - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// /// Contains the logic responsible for restoring the merged principal from the marshalled authentication context, if applicable. /// + [Obsolete("This class is obsolete and will be removed in a future version.")] public sealed class RestoreMergedPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler { - private readonly OpenIddictClientSystemIntegrationMarshal _marshal; - public RestoreMergedPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal) - => _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal)); + => throw new NotSupportedException(SR.GetResourceString(SR.ID0403)); /// /// Gets the default descriptor definition assigned to this handler. @@ -1442,24 +1537,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers .Build(); /// - public ValueTask HandleAsync(ProcessAuthenticationContext context) - { - ArgumentNullException.ThrowIfNull(context); - - Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - - context.MergedPrincipal = context.EndpointType switch - { - // When the authentication context is marshalled, restore the merged principal from the other instance. - OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification) - => notification.MergedPrincipal, - - // Otherwise, don't alter the current context. - _ => context.MergedPrincipal - }; - - return ValueTask.CompletedTask; - } + public ValueTask HandleAsync(ProcessAuthenticationContext context) => ValueTask.CompletedTask; } /// @@ -1492,8 +1570,13 @@ public static partial class OpenIddictClientSystemIntegrationHandlers Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019)); - // Inform the marshal that the authentication demand is complete. - if (!_marshal.TryComplete(context.Nonce, context)) + if (context.EndpointType is not (OpenIddictClientEndpointType.Redirection or + OpenIddictClientEndpointType.PostLogoutRedirection)) + { + return ValueTask.CompletedTask; + } + + if (_marshal.IsTracked(context.Nonce) && !_marshal.TryComplete(context.Nonce, context)) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0380)); } diff --git a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs index 3bbc6c6d..346aaaff 100644 --- a/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs +++ b/src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs @@ -833,8 +833,9 @@ public static partial class OpenIddictClientWebIntegrationHandlers ProviderTypes.PayPal => (false, false, false), // NetSuite does not return an id_token when using the refresh_token grant type. - // Additionally, the at_hash inside their id_token is not a valid hash of the - // access token, but is instead a copy of the RS256 signature within the access token. + // + // Additionally, the at_hash inside the id_token is not a valid hash of the access + // token, but is instead a copy of the RS256 signature within the access token. ProviderTypes.NetSuite => (true, false, false), _ => (context.ExtractBackchannelIdentityToken, diff --git a/src/OpenIddict.Client/OpenIddictClientEvents.cs b/src/OpenIddict.Client/OpenIddictClientEvents.cs index 1e3a0180..e01e8d95 100644 --- a/src/OpenIddict.Client/OpenIddictClientEvents.cs +++ b/src/OpenIddict.Client/OpenIddictClientEvents.cs @@ -441,6 +441,12 @@ public static partial class OpenIddictClientEvents [Obsolete("This property is no longer used and will be removed in a future version.")] public HashSet UserInfoEndpointTokenBindingMethods { get; } = new(StringComparer.Ordinal); + /// + /// Gets or sets a boolean indicating whether the token entry associated + /// with the state token should be marked as redeemed in the database. + /// + public bool DisableStateTokenRedeeming { get; set; } + /// /// Gets or sets a boolean indicating whether a token request should be sent. /// diff --git a/src/OpenIddict.Client/OpenIddictClientExtensions.cs b/src/OpenIddict.Client/OpenIddictClientExtensions.cs index 0465e0c9..a16a3a79 100644 --- a/src/OpenIddict.Client/OpenIddictClientExtensions.cs +++ b/src/OpenIddict.Client/OpenIddictClientExtensions.cs @@ -60,6 +60,7 @@ public static class OpenIddictClientExtensions 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.Client/OpenIddictClientHandlerFilters.cs b/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs index d3991c91..5d123317 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs @@ -406,6 +406,20 @@ public static class OpenIddictClientHandlerFilters } } + /// + /// Represents a filter that excludes the associated handlers if the state token should not be redeemed. + /// + public sealed class RequireStateTokenRedeemed : IOpenIddictClientHandlerFilter + { + /// + public ValueTask IsActiveAsync(ProcessAuthenticationContext context) + { + ArgumentNullException.ThrowIfNull(context); + + return new(!context.DisableStateTokenRedeeming); + } + } + /// /// Represents a filter that excludes the associated handlers if no state token is validated. /// diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs index be10f7ab..d3feab31 100644 --- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs +++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs @@ -824,6 +824,7 @@ public static partial class OpenIddictClientHandlers = OpenIddictClientHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() + .AddFilter() .AddFilter() .UseScopedHandler() // Note: this handler is deliberately executed early in the pipeline to ensure that @@ -896,7 +897,7 @@ public static partial class OpenIddictClientHandlers // Reject the authentication demand if the expected endpoint type doesn't // match the current endpoint type as it may indicate a mix-up attack (e.g a // state token created for a logout operation was used for a login operation). - if (type != context.EndpointType) + if (context.EndpointType is not OpenIddictClientEndpointType.Unknown && context.EndpointType != type) { context.Reject( error: Errors.InvalidRequest, @@ -995,6 +996,12 @@ public static partial class OpenIddictClientHandlers Debug.Assert(context.StateTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); + // Only validate the endpoint type if the endpoint is well-known. + if (context.EndpointType is OpenIddictClientEndpointType.Unknown) + { + return ValueTask.CompletedTask; + } + // Resolve the endpoint type allowed to be used with the state token. if (!Enum.TryParse(context.StateTokenPrincipal.GetClaim(Claims.Private.EndpointType), ignoreCase: true, out OpenIddictClientEndpointType type)) @@ -1174,8 +1181,8 @@ public static partial class OpenIddictClientHandlers { ArgumentNullException.ThrowIfNull(context); - // To help mitigate mix-up attacks, the identity of the issuer can be returned by - // authorization servers that support it as a part of the "iss" parameter, which + // To help mitigate mix-up attacks, the identity of the issuer can be returned + // by authorization servers that support it as part of the "iss" parameter, which // allows comparing it to the issuer in the state token. Depending on the selected // response_type, the same information could be retrieved from the identity token // that is expected to contain an "iss" claim containing the issuer identity. @@ -1217,6 +1224,7 @@ public static partial class OpenIddictClientHandlers // Reject authorization responses containing an "iss" parameter if the configuration // doesn't indicate this parameter is supported, as recommended by the specification. + // // See https://datatracker.ietf.org/doc/html/draft-ietf-oauth-iss-auth-resp-05#section-2.4 // for more information. else if (!string.IsNullOrEmpty(issuer)) @@ -1467,7 +1475,7 @@ public static partial class OpenIddictClientHandlers } /// - /// Contains the logic responsible for resolving the token from the incoming request. + /// Contains the logic responsible for resolving the frontchannel tokens from the incoming request. /// public sealed class ResolveValidatedFrontchannelTokens : IOpenIddictClientHandler { diff --git a/src/OpenIddict.Client/OpenIddictClientModels.cs b/src/OpenIddict.Client/OpenIddictClientModels.cs index 524c93b8..8f90df54 100644 --- a/src/OpenIddict.Client/OpenIddictClientModels.cs +++ b/src/OpenIddict.Client/OpenIddictClientModels.cs @@ -20,6 +20,11 @@ public static class OpenIddictClientModels /// public sealed record class InteractiveAuthenticationRequest { + /// + /// Gets or sets the parameters that will be added to the token request, if applicable. + /// + public Dictionary? AdditionalTokenRequestParameters { get; init; } + /// /// Gets or sets the cancellation token that will be /// used to determine if the operation was aborted. @@ -35,6 +40,23 @@ public static class OpenIddictClientModels /// Gets or sets the application-specific properties that will be added to the context. /// public Dictionary? Properties { get; init; } + + /// + /// Gets or sets the X.509 client certificate used to bind the access and/or + /// refresh tokens issued by the authorization server, if applicable. + /// + /// + /// + /// Note: when mTLs is also used for OAuth 2.0 client authentication, the + /// certificate set here replaces the client certificate chosen by OpenIddict. + /// + /// + /// Note: if a certificate-based client authentication or token binding method is + /// negotiated, the type of the certificate must match the negotiated methods. + /// + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public X509Certificate2? TokenBindingCertificate { get; init; } } /// diff --git a/src/OpenIddict.Client/OpenIddictClientService.cs b/src/OpenIddict.Client/OpenIddictClientService.cs index a0949190..11b558d0 100644 --- a/src/OpenIddict.Client/OpenIddictClientService.cs +++ b/src/OpenIddict.Client/OpenIddictClientService.cs @@ -271,7 +271,10 @@ public class OpenIddictClientService var context = new ProcessAuthenticationContext(transaction) { CancellationToken = request.CancellationToken, - Nonce = request.Nonce + Nonce = request.Nonce, + TokenEndpointClientCertificate = request.TokenBindingCertificate, + TokenRequest = request.AdditionalTokenRequestParameters + is Dictionary parameters ? new(parameters) : new() }; if (request.Properties is { Count: > 0 })