diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index 1390848f..f4d735eb 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1659,6 +1659,12 @@ To apply post-logout redirection responses, create a class implementing 'IOpenId
The specified format string cannot contain a '{0}' character when it is included as an allowed character in the charset.
+
+ The client registration corresponding to the specified nonce could not be resolved, which may indicate an invalid authentication demand or an invalid configuration. When using interactive user authentication flows in desktop or mobile applications, make sure the system integration is registered by calling 'services.AddOpenIddict().AddClient().UseSystemIntegration()'. When using interactive user authentication flows in an ASP.NET Core or OWIN application, use the authentication APIs exposed by 'IAuthenticationService' or 'IAuthenticationManager' instead of the 'OpenIddictClientService' class.
+
+
+ The base URI could not be resolved from the context, which may indicate an invalid authentication demand or an invalid configuration. When using interactive user authentication flows in desktop or mobile applications, make sure the system integration is registered by calling 'services.AddOpenIddict().AddClient().UseSystemIntegration()'. When using interactive user authentication flows in an ASP.NET Core or OWIN application, use the authentication APIs exposed by 'IAuthenticationService' or 'IAuthenticationManager' instead of the 'OpenIddictClientService' class.
+
The security token is missing.
diff --git a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs
index ae5120af..cce83bd4 100644
--- a/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs
+++ b/src/OpenIddict.Client.SystemIntegration/OpenIddictClientSystemIntegrationHandlers.cs
@@ -46,10 +46,10 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
*/
WaitMarshalledAuthentication.Descriptor,
+ RestoreClientRegistrationFromMarshalledContext.Descriptor,
RestoreStateTokenFromMarshalledAuthentication.Descriptor,
RestoreStateTokenPrincipalFromMarshalledAuthentication.Descriptor,
RestoreHostAuthenticationPropertiesFromMarshalledAuthentication.Descriptor,
- RestoreClientRegistrationFromMarshalledContext.Descriptor,
RedirectProtocolActivation.Descriptor,
ResolveRequestForgeryProtection.Descriptor,
@@ -537,7 +537,7 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.AddFilter()
.UseSingletonHandler()
- .SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 500)
+ .SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 250)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -567,8 +567,8 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
// 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
- // marshal uses a TaskCompletionSource (one per authentication) that will be automatically completed
- // or aborted by a specialized event handler as part of the ProcessRequest/ProcessError events processing.
+ // marshal uses a TaskCompletionSource (one per authentication) that will be automatically completed or
+ // aborted by a specialized event handler as part of the ProcessRequest/ProcessError events processing.
try
{
@@ -616,6 +616,52 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
}
}
+ ///
+ /// Contains the logic responsible for restoring the client registration and
+ /// configuration from the marshalled authentication context, if applicable.
+ ///
+ public sealed class RestoreClientRegistrationFromMarshalledContext : IOpenIddictClientHandler
+ {
+ private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
+
+ public RestoreClientRegistrationFromMarshalledContext(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(ResolveClientRegistrationFromAuthenticationContext.Descriptor.Order - 250)
+ .SetType(OpenIddictClientHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(ProcessAuthenticationContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
+
+ (context.Configuration, context.Registration) = context.EndpointType switch
+ {
+ // When the authentication context is marshalled, restore the
+ // issuer registration and configuration from the other instance.
+ OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
+ => (notification.Configuration, notification.Registration),
+
+ _ => (context.Configuration, context.Registration)
+ };
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for restoring the state token
/// from the marshalled authentication context, if applicable.
@@ -756,52 +802,6 @@ public static partial class OpenIddictClientSystemIntegrationHandlers
}
}
- ///
- /// Contains the logic responsible for restoring the client registration and
- /// configuration from the marshalled authentication context, if applicable.
- ///
- public sealed class RestoreClientRegistrationFromMarshalledContext : IOpenIddictClientHandler
- {
- private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
-
- public RestoreClientRegistrationFromMarshalledContext(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(ResolveClientRegistrationFromStateToken.Descriptor.Order + 500)
- .SetType(OpenIddictClientHandlerType.BuiltIn)
- .Build();
-
- ///
- public ValueTask HandleAsync(ProcessAuthenticationContext context)
- {
- if (context is null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
-
- (context.Configuration, context.Registration) = context.EndpointType switch
- {
- // When the authentication context is marshalled, restore the
- // issuer registration and configuration from the other instance.
- OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
- => (notification.Configuration, notification.Registration),
-
- _ => (context.Configuration, context.Registration)
- };
-
- return default;
- }
- }
-
///
/// Contains the logic responsible for redirecting the protocol activation to
/// the instance that initially started the authentication demand, if applicable.
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
index c9778f23..018729d2 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
@@ -406,11 +406,21 @@ public static partial class OpenIddictClientHandlers
//
// Client registrations/configurations that need to be resolved as part of authentication demands
// triggered from the redirection or post-logout redirection requests are handled elsewhere.
- if (!string.IsNullOrEmpty(context.Nonce) || context.EndpointType is not OpenIddictClientEndpointType.Unknown)
+ if (context.EndpointType is OpenIddictClientEndpointType.PostLogoutRedirection or
+ OpenIddictClientEndpointType.Redirection)
{
return;
}
+ // When using a user interactive flow with the system integration host, the client registration is expected
+ // to be attached by a dedicated event handler registered by the system integration package. If a nonce was
+ // attached but no client registration was resolved at this point, throw an exception to let the user know
+ // that the authentication demand is invalid or the system integration host is not correctly configured.
+ if (context.Registration is null && !string.IsNullOrEmpty(context.Nonce))
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0442));
+ }
+
context.Registration ??= context switch
{
// If specified, resolve the registration using the attached registration identifier.
@@ -785,8 +795,8 @@ public static partial class OpenIddictClientHandlers
.AddFilter()
.AddFilter()
.UseScopedHandler()
- // Note: this handler is deliberately executed early in the pipeline to ensure
- // that the state token entry is always marked as redeemed even if the authentication
+ // Note: this handler is deliberately executed early in the pipeline to ensure that
+ // the state token entry is always marked as redeemed even if the authentication
// demand is rejected later in the pipeline (e.g because an error was returned).
.SetOrder(ResolveNonceFromStateToken.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
@@ -4905,15 +4915,32 @@ public static partial class OpenIddictClientHandlers
throw new ArgumentNullException(nameof(context));
}
+ // Don't overwrite the redirect_uri if one was already explicitly attached.
+ if (context.RedirectUri is not null)
+ {
+ return default;
+ }
+
// Unlike OpenID Connect, OAuth 2.0 and 2.1 don't require specifying a redirect_uri
// but it is always considered mandatory in OpenIddict (independently of whether the
// selected flow is an OpenID Connect flow) as it's later used to ensure the redirection
// URI the authorization response was sent to matches the expected endpoint, which helps
// mitigate mix-up attacks when no standard issuer validation can be directly used.
- context.RedirectUri ??= OpenIddictHelpers.CreateAbsoluteUri(
- context.BaseUri,
- context.Registration.RedirectUri)?.AbsoluteUri ??
+ if (context.Registration.RedirectUri is null)
+ {
throw new InvalidOperationException(SR.GetResourceString(SR.ID0300));
+ }
+
+ // If the redirect_uri attached to the client registration is not an
+ // absolute URI and the base URI is not available, throw an exception.
+ if (context.BaseUri is null && !context.Registration.RedirectUri.IsAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0443));
+ }
+
+ context.RedirectUri = OpenIddictHelpers.CreateAbsoluteUri(
+ left : context.BaseUri,
+ right: context.Registration.RedirectUri).AbsoluteUri;
return default;
}
@@ -7329,10 +7356,28 @@ public static partial class OpenIddictClientHandlers
throw new ArgumentNullException(nameof(context));
}
+ // Don't overwrite the post_logout_redirect_uri if one was already explicitly attached.
+ if (context.PostLogoutRedirectUri is not null)
+ {
+ return default;
+ }
+
// Note: the post_logout_redirect_uri parameter is optional.
- context.PostLogoutRedirectUri ??= OpenIddictHelpers.CreateAbsoluteUri(
- context.BaseUri,
- context.Registration.PostLogoutRedirectUri)?.AbsoluteUri;
+ if (context.Registration.PostLogoutRedirectUri is null)
+ {
+ return default;
+ }
+
+ // If the post_logout_redirect_uri attached to the client registration is not
+ // an absolute URI and the base URI is not available, throw an exception.
+ if (context.BaseUri is null && !context.Registration.PostLogoutRedirectUri.IsAbsoluteUri)
+ {
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0443));
+ }
+
+ context.PostLogoutRedirectUri = OpenIddictHelpers.CreateAbsoluteUri(
+ left : context.BaseUri,
+ right: context.Registration.PostLogoutRedirectUri).AbsoluteUri;
return default;
}