diff --git a/src/OpenIddict.Abstractions/OpenIddictResources.resx b/src/OpenIddict.Abstractions/OpenIddictResources.resx
index bf0fd2e4..61147ea0 100644
--- a/src/OpenIddict.Abstractions/OpenIddictResources.resx
+++ b/src/OpenIddict.Abstractions/OpenIddictResources.resx
@@ -1775,6 +1775,21 @@ Alternatively, you can disable the token storage feature by calling 'services.Ad
An unsupported content encoding was returned by the remote server.
+
+ The configuration request was rejected by the remote server.
+
+
+ The cryptography request was rejected by the remote server.
+
+
+ The introspection request was rejected by the remote server.
+
+
+ The token request was rejected by the remote server.
+
+
+ The userinfo request was rejected by the remote server.
+
The '{0}' parameter shouldn't be null or empty at this point.
@@ -2392,6 +2407,21 @@ This may indicate that the hashed entry is corrupted or malformed.
Client validation failed because '{PostLogoutRedirectUri}' was not a valid post_logout_redirect_uri for {Client}.
+
+ The configuration request was rejected by the remote authorization server: {Response}.
+
+
+ The cryptography request was rejected by the remote authorization server: {Response}.
+
+
+ The introspection request was rejected by the remote authorization server: {Response}.
+
+
+ The token request was rejected by the remote authorization server: {Response}.
+
+
+ The userinfo request was rejected by the remote authorization server: {Response}.
+
https://documentation.openiddict.com/errors/{0}
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.Discovery.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.Discovery.cs
index 27db3d28..8a5cb767 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.Discovery.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.Discovery.cs
@@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.Text.Json;
+using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
namespace OpenIddict.Client;
@@ -18,8 +19,8 @@ public static partial class OpenIddictClientHandlers
/*
* Configuration response handling:
*/
- HandleErrorResponse.Descriptor,
ValidateWellKnownConfigurationParameters.Descriptor,
+ HandleConfigurationErrorResponse.Descriptor,
ValidateIssuer.Descriptor,
ExtractAuthorizationEndpoint.Descriptor,
ExtractCryptographyEndpoint.Descriptor,
@@ -37,8 +38,8 @@ public static partial class OpenIddictClientHandlers
/*
* Cryptography response handling:
*/
- HandleErrorResponse.Descriptor,
ValidateWellKnownCryptographyParameters.Descriptor,
+ HandleCryptographyErrorResponse.Descriptor,
ExtractSigningKeys.Descriptor);
///
@@ -52,7 +53,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000)
+ .SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -88,6 +89,10 @@ public static partial class OpenIddictClientHandlers
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateParameterType(string name, OpenIddictParameter value) => name switch
{
+ // Error parameters MUST be formatted as unique strings:
+ Parameters.Error or Parameters.ErrorDescription or Parameters.ErrorUri
+ => ((JsonElement) value).ValueKind is JsonValueKind.String,
+
// The following parameters MUST be formatted as unique strings:
Metadata.AuthorizationEndpoint or
Metadata.EndSessionEndpoint or
@@ -130,6 +135,49 @@ public static partial class OpenIddictClientHandlers
}
}
+ ///
+ /// Contains the logic responsible for surfacing potential errors from the configuration response.
+ ///
+ public class HandleConfigurationErrorResponse : IOpenIddictClientHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictClientHandlerDescriptor Descriptor { get; }
+ = OpenIddictClientHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateWellKnownConfigurationParameters.Descriptor.Order + 1_000)
+ .SetType(OpenIddictClientHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(HandleConfigurationResponseContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Note: the specification doesn't define a standard way to return an error other than
+ // returning a 4xx status code. That said, some implementations are known to return
+ // JSON payloads similar to standard errored token responses. For more information, see
+ // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse.
+ if (!string.IsNullOrEmpty(context.Response.Error))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6203), context.Response);
+
+ context.Reject(
+ error: Errors.ServerError,
+ description: SR.GetResourceString(SR.ID2144),
+ uri: SR.FormatID8000(SR.ID2144));
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for extracting the issuer from the discovery document.
///
@@ -141,7 +189,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateWellKnownConfigurationParameters.Descriptor.Order + 1_000)
+ .SetOrder(HandleConfigurationErrorResponse.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -721,7 +769,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000)
+ .SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -757,6 +805,10 @@ public static partial class OpenIddictClientHandlers
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateParameterType(string name, OpenIddictParameter value) => name switch
{
+ // Error parameters MUST be formatted as unique strings:
+ Parameters.Error or Parameters.ErrorDescription or Parameters.ErrorUri
+ => ((JsonElement) value).ValueKind is JsonValueKind.String,
+
// The following parameters MUST be formatted as arrays of objects:
JsonWebKeySetParameterNames.Keys => ((JsonElement) value) is JsonElement element &&
element.ValueKind is JsonValueKind.Array && ValidateObjectArray(element),
@@ -780,6 +832,49 @@ public static partial class OpenIddictClientHandlers
}
}
+ ///
+ /// Contains the logic responsible for surfacing potential errors from the cryptography response.
+ ///
+ public class HandleCryptographyErrorResponse : IOpenIddictClientHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictClientHandlerDescriptor Descriptor { get; }
+ = OpenIddictClientHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateWellKnownCryptographyParameters.Descriptor.Order + 1_000)
+ .SetType(OpenIddictClientHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(HandleCryptographyResponseContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Note: the specification doesn't define a standard way to return an error other than
+ // returning a 4xx status code. That said, some implementations are known to return
+ // JSON payloads similar to standard errored token responses. For more information, see
+ // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse.
+ if (!string.IsNullOrEmpty(context.Response.Error))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6204), context.Response);
+
+ context.Reject(
+ error: Errors.ServerError,
+ description: SR.GetResourceString(SR.ID2145),
+ uri: SR.FormatID8000(SR.ID2145));
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for extracting the signing keys from the JWKS document.
///
@@ -791,7 +886,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateWellKnownCryptographyParameters.Descriptor.Order + 1_000)
+ .SetOrder(HandleCryptographyErrorResponse.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.Exchange.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.Exchange.cs
index ac9869e8..8e9bb383 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.Exchange.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.Exchange.cs
@@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.Text.Json;
+using Microsoft.Extensions.Logging;
namespace OpenIddict.Client;
@@ -17,8 +18,8 @@ public static partial class OpenIddictClientHandlers
/*
* Token response handling:
*/
- HandleErrorResponse.Descriptor,
- ValidateWellKnownParameters.Descriptor);
+ ValidateWellKnownParameters.Descriptor,
+ HandleErrorResponse.Descriptor);
///
/// Contains the logic responsible for validating the well-known parameters contained in the token response.
@@ -67,6 +68,10 @@ public static partial class OpenIddictClientHandlers
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateParameterType(string name, OpenIddictParameter value) => name switch
{
+ // Error parameters MUST be formatted as unique strings:
+ Parameters.Error or Parameters.ErrorDescription or Parameters.ErrorUri
+ => ((JsonElement) value).ValueKind is JsonValueKind.String,
+
// The following parameters MUST be formatted as unique strings:
Parameters.AccessToken or Parameters.IdToken or Parameters.RefreshToken
=> ((JsonElement) value).ValueKind is JsonValueKind.String,
@@ -79,5 +84,54 @@ public static partial class OpenIddictClientHandlers
};
}
}
+
+ ///
+ /// Contains the logic responsible for surfacing potential errors from the token response.
+ ///
+ public class HandleErrorResponse : IOpenIddictClientHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictClientHandlerDescriptor Descriptor { get; }
+ = OpenIddictClientHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateWellKnownParameters.Descriptor.Order + 1_000)
+ .SetType(OpenIddictClientHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(HandleTokenResponseContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // For more information, see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2.
+ if (!string.IsNullOrEmpty(context.Response.Error))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6206), context.Response);
+
+ context.Reject(
+ error: context.Response.Error switch
+ {
+ Errors.InvalidClient => Errors.InvalidRequest,
+ Errors.InvalidGrant => Errors.InvalidGrant,
+ Errors.InvalidScope => Errors.InvalidScope,
+ Errors.InvalidRequest => Errors.InvalidRequest,
+ Errors.UnauthorizedClient => Errors.UnauthorizedClient,
+ Errors.UnsupportedGrantType => Errors.UnsupportedGrantType,
+ _ => Errors.ServerError
+ },
+ description: SR.GetResourceString(SR.ID2147),
+ uri: SR.FormatID8000(SR.ID2147));
+
+ return default;
+ }
+
+ return default;
+ }
+ }
}
}
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs
index a44eda6d..766c6f56 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs
@@ -7,6 +7,7 @@
using System.Collections.Immutable;
using System.Security.Claims;
using System.Text.Json;
+using Microsoft.Extensions.Logging;
namespace OpenIddict.Client;
@@ -18,21 +19,21 @@ public static partial class OpenIddictClientHandlers
/*
* Userinfo response handling:
*/
- HandleErrorResponse.Descriptor,
- ValidateWellKnownClaims.Descriptor,
+ ValidateWellKnownParameters.Descriptor,
+ HandleErrorResponse.Descriptor,
PopulateClaims.Descriptor);
///
/// Contains the logic responsible for validating the well-known parameters contained in the userinfo response.
///
- public class ValidateWellKnownClaims : IOpenIddictClientHandler
+ public class ValidateWellKnownParameters : IOpenIddictClientHandler
{
///
/// Gets the default descriptor definition assigned to this handler.
///
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
+ .UseSingletonHandler()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -75,6 +76,10 @@ public static partial class OpenIddictClientHandlers
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateParameterType(string name, OpenIddictParameter value) => name switch
{
+ // Error parameters MUST be formatted as unique strings:
+ Parameters.Error or Parameters.ErrorDescription or Parameters.ErrorUri
+ => ((JsonElement) value).ValueKind is JsonValueKind.String,
+
// The following parameters MUST be formatted as unique strings:
Claims.Subject => ((JsonElement) value).ValueKind is JsonValueKind.String,
@@ -84,6 +89,52 @@ public static partial class OpenIddictClientHandlers
}
}
+ ///
+ /// Contains the logic responsible for surfacing potential errors from the userinfo response.
+ ///
+ public class HandleErrorResponse : IOpenIddictClientHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictClientHandlerDescriptor Descriptor { get; }
+ = OpenIddictClientHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateWellKnownParameters.Descriptor.Order + 1_000)
+ .SetType(OpenIddictClientHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(HandleUserinfoResponseContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // For more information, see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError.
+ if (!string.IsNullOrEmpty(context.Response.Error))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6207), context.Response);
+
+ context.Reject(
+ error: context.Response.Error switch
+ {
+ Errors.InsufficientScope => Errors.InsufficientScope,
+ Errors.InvalidRequest => Errors.InvalidToken,
+ Errors.InvalidToken => Errors.InvalidToken,
+ _ => Errors.ServerError
+ },
+ description: SR.GetResourceString(SR.ID2148),
+ uri: SR.FormatID8000(SR.ID2148));
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for extracting the claims from the introspection response.
///
@@ -95,7 +146,7 @@ public static partial class OpenIddictClientHandlers
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateWellKnownClaims.Descriptor.Order + 1_000)
+ .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
index ecb8ccde..a1b6d3a0 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
@@ -10,7 +10,9 @@ using System.Diagnostics;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
+using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
+using static OpenIddict.Abstractions.OpenIddictExceptions;
#if !SUPPORTS_TIME_CONSTANT_COMPARISONS
using Org.BouncyCastle.Utilities;
@@ -60,7 +62,6 @@ public static partial class OpenIddictClientHandlers
GenerateClientAssertionToken.Descriptor,
AttachTokenRequestClientCredentials.Descriptor,
SendTokenRequest.Descriptor,
- ValidateTokenErrorParameters.Descriptor,
EvaluateValidatedBackchannelTokens.Descriptor,
ResolveValidatedBackchannelTokens.Descriptor,
@@ -80,7 +81,6 @@ public static partial class OpenIddictClientHandlers
EvaluateUserinfoRequest.Descriptor,
AttachUserinfoRequestParameters.Descriptor,
SendUserinfoRequest.Descriptor,
- ValidateUserinfoErrorParameters.Descriptor,
EvaluateValidatedUserinfoToken.Descriptor,
ValidateRequiredUserinfoToken.Descriptor,
ValidateUserinfoToken.Descriptor,
@@ -2017,47 +2017,21 @@ public static partial class OpenIddictClientHandlers
throw new InvalidOperationException(SR.FormatID0301(Metadata.TokenEndpoint));
}
- context.TokenResponse = await _service.SendTokenRequestAsync(
- context.Registration, context.TokenEndpoint, context.TokenRequest);
- }
- }
-
- ///
- /// Contains the logic responsible for rejecting errored token responses.
- ///
- public class ValidateTokenErrorParameters : IOpenIddictClientHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictClientHandlerDescriptor Descriptor { get; }
- = OpenIddictClientHandlerDescriptor.CreateBuilder()
- .AddFilter()
- .UseSingletonHandler()
- .SetOrder(SendTokenRequest.Descriptor.Order + 1_000)
- .Build();
-
- ///
- public ValueTask HandleAsync(ProcessAuthenticationContext context)
- {
- if (context is null)
+ try
{
- throw new ArgumentNullException(nameof(context));
+ context.TokenResponse = await _service.SendTokenRequestAsync(
+ context.Registration, context.TokenEndpoint, context.TokenRequest);
}
- Debug.Assert(context.TokenResponse is not null, SR.GetResourceString(SR.ID4007));
-
- if (!string.IsNullOrEmpty(context.TokenResponse.Error))
+ catch (ProtocolException exception)
{
context.Reject(
- error: context.TokenResponse.Error,
- description: context.TokenResponse.ErrorDescription,
- uri: context.TokenResponse.ErrorUri);
+ error: exception.Error,
+ description: exception.ErrorDescription,
+ uri: exception.ErrorUri);
- return default;
+ return;
}
-
- return default;
}
}
@@ -2073,7 +2047,7 @@ public static partial class OpenIddictClientHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.AddFilter()
.UseSingletonHandler()
- .SetOrder(ValidateTokenErrorParameters.Descriptor.Order + 1_000)
+ .SetOrder(SendTokenRequest.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -3002,47 +2976,21 @@ public static partial class OpenIddictClientHandlers
// - application/json responses containing a JSON object listing the user claims as-is.
// - application/jwt responses containing a signed/encrypted JSON Web Token containing the user claims.
- (context.UserinfoResponse, (context.UserinfoTokenPrincipal, context.UserinfoToken)) =
- await _service.SendUserinfoRequestAsync(context.Registration, context.UserinfoEndpoint, context.UserinfoRequest);
- }
- }
-
- ///
- /// Contains the logic responsible for rejecting errored userinfo responses.
- ///
- public class ValidateUserinfoErrorParameters : IOpenIddictClientHandler
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictClientHandlerDescriptor Descriptor { get; }
- = OpenIddictClientHandlerDescriptor.CreateBuilder()
- .AddFilter()
- .UseSingletonHandler()
- .SetOrder(SendUserinfoRequest.Descriptor.Order + 1_000)
- .Build();
-
- ///
- public ValueTask HandleAsync(ProcessAuthenticationContext context)
- {
- if (context is null)
+ try
{
- throw new ArgumentNullException(nameof(context));
+ (context.UserinfoResponse, (context.UserinfoTokenPrincipal, context.UserinfoToken)) =
+ await _service.SendUserinfoRequestAsync(context.Registration, context.UserinfoEndpoint, context.UserinfoRequest);
}
- Debug.Assert(context.UserinfoResponse is not null, SR.GetResourceString(SR.ID4007));
-
- if (!string.IsNullOrEmpty(context.UserinfoResponse.Error))
+ catch (ProtocolException exception)
{
context.Reject(
- error: context.UserinfoResponse.Error,
- description: context.UserinfoResponse.ErrorDescription,
- uri: context.UserinfoResponse.ErrorUri);
+ error: exception.Error,
+ description: exception.ErrorDescription,
+ uri: exception.ErrorUri);
- return default;
+ return;
}
-
- return default;
}
}
@@ -3058,7 +3006,7 @@ public static partial class OpenIddictClientHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder()
.AddFilter()
.UseSingletonHandler()
- .SetOrder(ValidateUserinfoErrorParameters.Descriptor.Order + 1_000)
+ .SetOrder(SendUserinfoRequest.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
@@ -4979,43 +4927,6 @@ public static partial class OpenIddictClientHandlers
}
}
- ///
- /// Contains the logic responsible for extracting potential errors from the response.
- ///
- public class HandleErrorResponse : IOpenIddictClientHandler where TContext : BaseValidatingContext
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictClientHandlerDescriptor Descriptor { get; }
- = OpenIddictClientHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler>()
- .SetOrder(int.MinValue + 100_000)
- .SetType(OpenIddictClientHandlerType.BuiltIn)
- .Build();
-
- ///
- public ValueTask HandleAsync(TContext context)
- {
- if (context is null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (!string.IsNullOrEmpty(context.Transaction.Response?.Error))
- {
- context.Reject(
- error: context.Transaction.Response.Error,
- description: context.Transaction.Response.ErrorDescription,
- uri: context.Transaction.Response.ErrorUri);
-
- return default;
- }
-
- return default;
- }
- }
-
///
/// Contains the logic responsible for attaching the appropriate parameters to the error response.
///
diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Discovery.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Discovery.cs
index df8d2e3f..f53886a3 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Discovery.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Discovery.cs
@@ -6,6 +6,7 @@
using System.Collections.Immutable;
using System.Text.Json;
+using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
namespace OpenIddict.Validation;
@@ -18,8 +19,8 @@ public static partial class OpenIddictValidationHandlers
/*
* Configuration response handling:
*/
- HandleErrorResponse.Descriptor,
ValidateWellKnownConfigurationParameters.Descriptor,
+ HandleConfigurationErrorResponse.Descriptor,
ValidateIssuer.Descriptor,
ExtractCryptographyEndpoint.Descriptor,
ExtractIntrospectionEndpoint.Descriptor,
@@ -28,8 +29,8 @@ public static partial class OpenIddictValidationHandlers
/*
* Cryptography response handling:
*/
- HandleErrorResponse.Descriptor,
ValidateWellKnownCryptographyParameters.Descriptor,
+ HandleCryptographyErrorResponse.Descriptor,
ExtractSigningKeys.Descriptor);
///
@@ -43,7 +44,7 @@ public static partial class OpenIddictValidationHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000)
+ .SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@@ -79,6 +80,10 @@ public static partial class OpenIddictValidationHandlers
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateParameterType(string name, OpenIddictParameter value) => name switch
{
+ // Error parameters MUST be formatted as unique strings:
+ Parameters.Error or Parameters.ErrorDescription or Parameters.ErrorUri
+ => ((JsonElement) value).ValueKind is JsonValueKind.String,
+
// The following parameters MUST be formatted as unique strings:
Metadata.IntrospectionEndpoint or
Metadata.Issuer
@@ -108,6 +113,49 @@ public static partial class OpenIddictValidationHandlers
}
}
+ ///
+ /// Contains the logic responsible for surfacing potential errors from the configuration response.
+ ///
+ public class HandleConfigurationErrorResponse : IOpenIddictValidationHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
+ = OpenIddictValidationHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateWellKnownConfigurationParameters.Descriptor.Order + 1_000)
+ .SetType(OpenIddictValidationHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(HandleConfigurationResponseContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Note: the specification doesn't define a standard way to return an error other than
+ // returning a 4xx status code. That said, some implementations are known to return
+ // JSON payloads similar to standard errored token responses. For more information, see
+ // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse.
+ if (!string.IsNullOrEmpty(context.Response.Error))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6203), context.Response);
+
+ context.Reject(
+ error: Errors.ServerError,
+ description: SR.GetResourceString(SR.ID2144),
+ uri: SR.FormatID8000(SR.ID2144));
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for extracting the issuer from the discovery document.
///
@@ -119,7 +167,7 @@ public static partial class OpenIddictValidationHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateWellKnownConfigurationParameters.Descriptor.Order + 1_000)
+ .SetOrder(HandleConfigurationErrorResponse.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@@ -320,7 +368,7 @@ public static partial class OpenIddictValidationHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000)
+ .SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@@ -356,6 +404,10 @@ public static partial class OpenIddictValidationHandlers
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateParameterType(string name, OpenIddictParameter value) => name switch
{
+ // Error parameters MUST be formatted as unique strings:
+ Parameters.Error or Parameters.ErrorDescription or Parameters.ErrorUri
+ => ((JsonElement) value).ValueKind is JsonValueKind.String,
+
// The following parameters MUST be formatted as arrays of objects:
JsonWebKeySetParameterNames.Keys => ((JsonElement) value) is JsonElement element &&
element.ValueKind is JsonValueKind.Array && ValidateObjectArray(element),
@@ -379,6 +431,49 @@ public static partial class OpenIddictValidationHandlers
}
}
+ ///
+ /// Contains the logic responsible for surfacing potential errors from the cryptography response.
+ ///
+ public class HandleCryptographyErrorResponse : IOpenIddictValidationHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
+ = OpenIddictValidationHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateWellKnownCryptographyParameters.Descriptor.Order + 1_000)
+ .SetType(OpenIddictValidationHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(HandleCryptographyResponseContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Note: the specification doesn't define a standard way to return an error other than
+ // returning a 4xx status code. That said, some implementations are known to return
+ // JSON payloads similar to standard errored token responses. For more information, see
+ // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse.
+ if (!string.IsNullOrEmpty(context.Response.Error))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6204), context.Response);
+
+ context.Reject(
+ error: Errors.ServerError,
+ description: SR.GetResourceString(SR.ID2145),
+ uri: SR.FormatID8000(SR.ID2145));
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for extracting the signing keys from the JWKS document.
///
@@ -390,7 +485,7 @@ public static partial class OpenIddictValidationHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateWellKnownCryptographyParameters.Descriptor.Order + 1_000)
+ .SetOrder(HandleCryptographyErrorResponse.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs
index ce51a2f5..71a66f85 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs
@@ -7,6 +7,7 @@
using System.Collections.Immutable;
using System.Security.Claims;
using System.Text.Json;
+using Microsoft.Extensions.Logging;
namespace OpenIddict.Validation;
@@ -24,8 +25,8 @@ public static partial class OpenIddictValidationHandlers
/*
* Introspection response handling:
*/
- HandleErrorResponse.Descriptor,
ValidateWellKnownParameters.Descriptor,
+ HandleErrorResponse.Descriptor,
HandleInactiveResponse.Descriptor,
ValidateIssuer.Descriptor,
ValidateTokenUsage.Descriptor,
@@ -102,7 +103,7 @@ public static partial class OpenIddictValidationHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000)
+ .SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
@@ -138,25 +139,29 @@ public static partial class OpenIddictValidationHandlers
// JsonElement instance using the same value type as the original parameter value.
static bool ValidateParameterType(string name, OpenIddictParameter value) => name switch
{
- // The following parameters MUST be formatted as booleans:
+ // Error parameters MUST be formatted as unique strings:
+ Parameters.Error or Parameters.ErrorDescription or Parameters.ErrorUri
+ => ((JsonElement) value).ValueKind is JsonValueKind.String,
+
+ // The following claims MUST be formatted as booleans:
Claims.Active => ((JsonElement) value).ValueKind is JsonValueKind.True or JsonValueKind.False,
- // The following parameters MUST be formatted as unique strings:
+ // The following claims MUST be formatted as unique strings:
Claims.JwtId or Claims.Issuer or Claims.Scope or Claims.TokenUsage
=> ((JsonElement) value).ValueKind is JsonValueKind.String,
- // The following parameters MUST be formatted as strings or arrays of strings:
+ // The following claims MUST be formatted as strings or arrays of strings:
//
// Note: empty arrays and arrays that contain a single value are also considered valid.
Claims.Audience => ((JsonElement) value) is JsonElement element &&
element.ValueKind is JsonValueKind.String ||
(element.ValueKind is JsonValueKind.Array && ValidateStringArray(element)),
- // The following parameters MUST be formatted as numeric dates:
+ // The following claims MUST be formatted as numeric dates:
Claims.ExpiresAt or Claims.IssuedAt or Claims.NotBefore
=> ((JsonElement) value).ValueKind is JsonValueKind.Number,
- // Parameters that are not in the well-known list can be of any type.
+ // Claims that are not in the well-known list can be of any type.
_ => true
};
@@ -175,6 +180,52 @@ public static partial class OpenIddictValidationHandlers
}
}
+ ///
+ /// Contains the logic responsible for surfacing potential errors from the introspection response.
+ ///
+ public class HandleErrorResponse : IOpenIddictValidationHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
+ = OpenIddictValidationHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(ValidateWellKnownParameters.Descriptor.Order + 1_000)
+ .SetType(OpenIddictValidationHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(HandleIntrospectionResponseContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ // Note: the specification requires returning most errors (e.g invalid token errors)
+ // as "active: false" responses instead of as proper OAuth 2.0 error responses.
+ // For more information, see https://datatracker.ietf.org/doc/html/rfc7662#section-2.3.
+ if (!string.IsNullOrEmpty(context.Response.Error))
+ {
+ context.Logger.LogInformation(SR.GetResourceString(SR.ID6205), context.Response);
+
+ context.Reject(
+ error: context.Response.Error switch
+ {
+ Errors.UnauthorizedClient => Errors.UnauthorizedClient,
+ _ => Errors.ServerError
+ },
+ description: SR.GetResourceString(SR.ID2146),
+ uri: SR.FormatID8000(SR.ID2146));
+
+ return default;
+ }
+
+ return default;
+ }
+ }
+
///
/// Contains the logic responsible for extracting the active: false marker from the response.
///
@@ -186,7 +237,7 @@ public static partial class OpenIddictValidationHandlers
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder()
.UseSingletonHandler()
- .SetOrder(ValidateWellKnownParameters.Descriptor.Order + 1_000)
+ .SetOrder(HandleErrorResponse.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
index d037ff05..f8e80aef 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
@@ -10,6 +10,7 @@ using System.Globalization;
using System.Security.Claims;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
+using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.Validation;
@@ -336,14 +337,14 @@ public static partial class OpenIddictValidationHandlers
}) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0141));
}
- catch (Exception exception)
+ catch (ProtocolException exception)
{
context.Logger.LogDebug(exception, SR.GetResourceString(SR.ID6155));
context.Reject(
- error: Errors.InvalidToken,
- description: SR.GetResourceString(SR.ID2004),
- uri: SR.FormatID8000(SR.ID2004));
+ error: exception.Error,
+ description: exception.ErrorDescription,
+ uri: exception.ErrorUri);
return;
}
diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
index 6e8154aa..4bdcb4fd 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
@@ -365,41 +365,4 @@ public static partial class OpenIddictValidationHandlers
return default;
}
}
-
- ///
- /// Contains the logic responsible for extracting potential errors from the response.
- ///
- public class HandleErrorResponse : IOpenIddictValidationHandler where TContext : BaseValidatingContext
- {
- ///
- /// Gets the default descriptor definition assigned to this handler.
- ///
- public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
- = OpenIddictValidationHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler>()
- .SetOrder(int.MinValue + 100_000)
- .SetType(OpenIddictValidationHandlerType.BuiltIn)
- .Build();
-
- ///
- public ValueTask HandleAsync(TContext context)
- {
- if (context is null)
- {
- throw new ArgumentNullException(nameof(context));
- }
-
- if (!string.IsNullOrEmpty(context.Transaction.Response?.Error))
- {
- context.Reject(
- error: context.Transaction.Response.Error,
- description: context.Transaction.Response.ErrorDescription,
- uri: context.Transaction.Response.ErrorUri);
-
- return default;
- }
-
- return default;
- }
- }
}