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 })