diff --git a/src/OpenIddict.Client/OpenIddictClientEvents.cs b/src/OpenIddict.Client/OpenIddictClientEvents.cs
index 6b51b320..9781984b 100644
--- a/src/OpenIddict.Client/OpenIddictClientEvents.cs
+++ b/src/OpenIddict.Client/OpenIddictClientEvents.cs
@@ -826,6 +826,14 @@ public static partial class OpenIddictClientEvents
/// Note: overriding the value of this property is generally not recommended.
///
public bool DisableFrontchannelIdentityTokenNonceValidation { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether userinfo validation should be disabled.
+ ///
+ ///
+ /// Note: overriding the value of this property is generally not recommended.
+ ///
+ public bool DisableUserinfoValidation { get; set; }
}
///
diff --git a/src/OpenIddict.Client/OpenIddictClientExtensions.cs b/src/OpenIddict.Client/OpenIddictClientExtensions.cs
index 7fa124fe..dcd87f55 100644
--- a/src/OpenIddict.Client/OpenIddictClientExtensions.cs
+++ b/src/OpenIddict.Client/OpenIddictClientExtensions.cs
@@ -63,6 +63,7 @@ public static class OpenIddictClientExtensions
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
+ builder.Services.TryAddSingleton();
// Register the built-in client event handlers used by the OpenIddict client components.
// Note: the order used here is not important, as the actual order is set in the options.
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs b/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
index 5279e909..f4cc0d8c 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
@@ -475,4 +475,21 @@ public static class OpenIddictClientHandlerFilters
return new(context.UserinfoTokenPrincipal is not null);
}
}
+
+ ///
+ /// Represents a filter that excludes the associated handlers if userinfo validation was disabled.
+ ///
+ public sealed class RequireUserinfoValidationEnabled : IOpenIddictClientHandlerFilter
+ {
+ ///
+ public ValueTask IsActiveAsync(ProcessAuthenticationContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ return new(!context.DisableUserinfoValidation);
+ }
+ }
}
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
index 39f5ccaf..0be38508 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
@@ -3404,6 +3404,12 @@ public static partial class OpenIddictClientHandlers
_ => false
};
+ // The OpenIddict client is expected to be used with standard OpenID Connect userinfo endpoints
+ // but must also support non-standard implementations, that are common with OAuth 2.0-only servers.
+ //
+ // As such, protocol requirements are only enforced if the server supports OpenID Connect.
+ context.DisableUserinfoValidation = !context.Configuration.ScopesSupported.Contains(Scopes.OpenId);
+
return default;
}
}
@@ -3659,6 +3665,7 @@ public static partial class OpenIddictClientHandlers
///
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
+ .AddFilter()
.AddFilter()
.UseSingletonHandler()
.SetOrder(ValidateUserinfoToken.Descriptor.Order + 1_000)
@@ -3675,28 +3682,21 @@ public static partial class OpenIddictClientHandlers
Debug.Assert(context.UserinfoTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
- // The OpenIddict client is expected to be used with standard OpenID Connect userinfo endpoints
- // but must also support non-standard implementations, that are common with OAuth 2.0-only servers.
- //
- // As such, protocol requirements are only enforced if the server supports OpenID Connect.
- if (context.Configuration.ScopesSupported.Contains(Scopes.OpenId))
+ foreach (var group in context.UserinfoTokenPrincipal.Claims
+ .GroupBy(claim => claim.Type)
+ .ToDictionary(group => group.Key, group => group.ToList()))
{
- foreach (var group in context.UserinfoTokenPrincipal.Claims
- .GroupBy(claim => claim.Type)
- .ToDictionary(group => group.Key, group => group.ToList()))
+ if (ValidateClaimGroup(group))
{
- if (ValidateClaimGroup(group))
- {
- continue;
- }
+ continue;
+ }
- context.Reject(
- error: Errors.InvalidRequest,
- description: SR.FormatID2131(group.Key),
- uri: SR.FormatID8000(SR.ID2131));
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.FormatID2131(group.Key),
+ uri: SR.FormatID8000(SR.ID2131));
- return default;
- }
+ return default;
}
return default;
@@ -3725,6 +3725,7 @@ public static partial class OpenIddictClientHandlers
///
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
+ .AddFilter()
.AddFilter()
.UseSingletonHandler()
.SetOrder(ValidateUserinfoTokenWellknownClaims.Descriptor.Order + 1_000)
@@ -3741,53 +3742,46 @@ public static partial class OpenIddictClientHandlers
Debug.Assert(context.UserinfoTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
- // The OpenIddict client is expected to be used with standard OpenID Connect userinfo endpoints
- // but must also support non-standard implementations, that are common with OAuth 2.0-only servers.
- //
- // As such, protocol requirements are only enforced if the server supports OpenID Connect.
- if (context.Configuration.ScopesSupported.Contains(Scopes.OpenId))
+ // Standard OpenID Connect userinfo responses/tokens MUST contain a "sub" claim. For more
+ // information, see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
+ if (!context.UserinfoTokenPrincipal.HasClaim(Claims.Subject))
{
- // Standard OpenID Connect userinfo responses/tokens MUST contain a "sub" claim. For more
- // information, see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
- if (!context.UserinfoTokenPrincipal.HasClaim(Claims.Subject))
- {
- context.Reject(
- error: Errors.InvalidRequest,
- description: SR.FormatID2132(Claims.Subject),
- uri: SR.FormatID8000(SR.ID2132));
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.FormatID2132(Claims.Subject),
+ uri: SR.FormatID8000(SR.ID2132));
- return default;
- }
+ return default;
+ }
- // The "sub" claim returned as part of the userinfo response/token MUST exactly match the value
- // returned in the frontchannel identity token, if one was returned. For more information,
- // see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
- if (context.FrontchannelIdentityTokenPrincipal is not null && !string.Equals(
- context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.Subject),
- context.UserinfoTokenPrincipal.GetClaim(Claims.Subject), StringComparison.Ordinal))
- {
- context.Reject(
- error: Errors.InvalidRequest,
- description: SR.FormatID2133(Claims.Subject),
- uri: SR.FormatID8000(SR.ID2133));
+ // The "sub" claim returned as part of the userinfo response/token MUST exactly match the value
+ // returned in the frontchannel identity token, if one was returned. For more information,
+ // see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
+ if (context.FrontchannelIdentityTokenPrincipal is not null && !string.Equals(
+ context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.Subject),
+ context.UserinfoTokenPrincipal.GetClaim(Claims.Subject), StringComparison.Ordinal))
+ {
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.FormatID2133(Claims.Subject),
+ uri: SR.FormatID8000(SR.ID2133));
- return default;
- }
+ return default;
+ }
- // The "sub" claim returned as part of the userinfo response/token MUST exactly match the value
- // returned in the frontchannel identity token, if one was returned. For more information,
- // see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
- if (context.BackchannelIdentityTokenPrincipal is not null && !string.Equals(
- context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.Subject),
- context.UserinfoTokenPrincipal.GetClaim(Claims.Subject), StringComparison.Ordinal))
- {
- context.Reject(
- error: Errors.InvalidRequest,
- description: SR.FormatID2133(Claims.Subject),
- uri: SR.FormatID8000(SR.ID2133));
+ // The "sub" claim returned as part of the userinfo response/token MUST exactly match the value
+ // returned in the frontchannel identity token, if one was returned. For more information,
+ // see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
+ if (context.BackchannelIdentityTokenPrincipal is not null && !string.Equals(
+ context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.Subject),
+ context.UserinfoTokenPrincipal.GetClaim(Claims.Subject), StringComparison.Ordinal))
+ {
+ context.Reject(
+ error: Errors.InvalidRequest,
+ description: SR.FormatID2133(Claims.Subject),
+ uri: SR.FormatID8000(SR.ID2133));
- return default;
- }
+ return default;
}
return default;