diff --git a/src/OpenIddict.Client/OpenIddictClientEvents.cs b/src/OpenIddict.Client/OpenIddictClientEvents.cs
index 74201830..a8f601b5 100644
--- a/src/OpenIddict.Client/OpenIddictClientEvents.cs
+++ b/src/OpenIddict.Client/OpenIddictClientEvents.cs
@@ -565,6 +565,70 @@ public static partial class OpenIddictClientEvents
///
public bool ValidateUserinfoToken { get; set; }
+ ///
+ /// Gets or sets a boolean indicating whether an invalid authorization code
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectAuthorizationCode { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid backchannel access token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectBackchannelAccessToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid backchannel identity token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectBackchannelIdentityToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid frontchannel access token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectFrontchannelAccessToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid frontchannel identity token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectFrontchannelIdentityToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid refresh token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectRefreshToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid state token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectStateToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid userinfo token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectUserinfoToken { get; set; }
+
///
/// Gets or sets the authorization code to validate, if applicable.
///
diff --git a/src/OpenIddict.Client/OpenIddictClientHandlers.cs b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
index 1b58bb2d..b35e68d5 100644
--- a/src/OpenIddict.Client/OpenIddictClientHandlers.cs
+++ b/src/OpenIddict.Client/OpenIddictClientHandlers.cs
@@ -401,23 +401,24 @@ public static partial class OpenIddictClientHandlers
(context.ExtractStateToken,
context.RequireStateToken,
- context.ValidateStateToken) = context.EndpointType switch
+ context.ValidateStateToken,
+ context.RejectStateToken) = context.EndpointType switch
{
// While the OAuth 2.0/2.1 and OpenID Connect specifications don't require sending a
// state as part of authorization requests, the identity provider MUST return the state
// if one was initially specified. Since OpenIddict always sends a state (used as a way
// to mitigate CSRF attacks and store per-authorization values like the identity of the
// chosen authorization server), the state is always considered required at this point.
- OpenIddictClientEndpointType.Redirection => (true, true, true),
+ OpenIddictClientEndpointType.Redirection => (true, true, true, true),
// While the OpenID Connect RP-initiated logout specification doesn't require sending
// a state as part of logout requests, the identity provider MUST return the state
// if one was initially specified. Since OpenIddict always sends a state (used as a
// way to mitigate CSRF attacks and store per-logout values like the identity of the
// chosen authorization server), the state is always considered required at this point.
- OpenIddictClientEndpointType.PostLogoutRedirection => (true, true, true),
+ OpenIddictClientEndpointType.PostLogoutRedirection => (true, true, true, true),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
return default;
@@ -555,10 +556,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectStateToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -1204,7 +1210,8 @@ public static partial class OpenIddictClientHandlers
(context.ExtractAuthorizationCode,
context.RequireAuthorizationCode,
- context.ValidateAuthorizationCode) = context.GrantType switch
+ context.ValidateAuthorizationCode,
+ context.RejectAuthorizationCode) = context.GrantType switch
{
// An authorization code is returned for the authorization code and implicit grants when
// the response type contains the "code" value, which includes the authorization code
@@ -1217,14 +1224,15 @@ public static partial class OpenIddictClientHandlers
GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList types &&
types.Contains(ResponseTypes.Code)
- => (true, true, false),
+ => (true, true, false, false),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
(context.ExtractFrontchannelAccessToken,
context.RequireFrontchannelAccessToken,
- context.ValidateFrontchannelAccessToken) = context.GrantType switch
+ context.ValidateFrontchannelAccessToken,
+ context.RejectFrontchannelAccessToken) = context.GrantType switch
{
// An access token is returned for the authorization code and implicit grants when
// the response type contains the "token" value, which includes some variations of
@@ -1237,14 +1245,15 @@ public static partial class OpenIddictClientHandlers
GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList types &&
types.Contains(ResponseTypes.Token)
- => (true, true, false),
+ => (true, true, false, false),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
(context.ExtractFrontchannelIdentityToken,
context.RequireFrontchannelIdentityToken,
- context.ValidateFrontchannelIdentityToken) = context.GrantType switch
+ context.ValidateFrontchannelIdentityToken,
+ context.RejectFrontchannelIdentityToken) = context.GrantType switch
{
// An identity token is returned for the authorization code and implicit grants when
// the response type contains the "id_token" value, which includes some variations
@@ -1256,9 +1265,9 @@ public static partial class OpenIddictClientHandlers
GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList types &&
types.Contains(ResponseTypes.IdToken)
- => (true, true, true),
+ => (true, true, true, true),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
return default;
@@ -1414,10 +1423,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectFrontchannelIdentityToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -1932,10 +1946,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectFrontchannelAccessToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -2002,10 +2021,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectAuthorizationCode)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -2489,7 +2513,8 @@ public static partial class OpenIddictClientHandlers
(context.ExtractBackchannelAccessToken,
context.RequireBackchannelAccessToken,
- context.ValidateBackchannelAccessToken) = context.GrantType switch
+ context.ValidateBackchannelAccessToken,
+ context.RejectBackchannelAccessToken) = context.GrantType switch
{
// An access token is always returned as part of token responses, independently of
// the negotiated response types or whether the server supports OpenID Connect or not.
@@ -2501,19 +2526,20 @@ public static partial class OpenIddictClientHandlers
GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList types &&
types.Contains(ResponseTypes.Code)
- => (true, true, false),
+ => (true, true, false, false),
// An access token is always returned as part of client credentials,
// resource owner password credentials and refresh token responses.
GrantTypes.ClientCredentials or GrantTypes.Password or GrantTypes.RefreshToken
- => (true, true, false),
+ => (true, true, false, false),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
(context.ExtractBackchannelIdentityToken,
context.RequireBackchannelIdentityToken,
- context.ValidateBackchannelIdentityToken) = context.GrantType switch
+ context.ValidateBackchannelIdentityToken,
+ context.RejectBackchannelIdentityToken) = context.GrantType switch
{
// An identity token is always returned as part of token responses for the code and
// hybrid flows when the authorization server supports OpenID Connect. As such,
@@ -2523,7 +2549,7 @@ public static partial class OpenIddictClientHandlers
context.ResponseType?.Split(Separators.Space) is IList types &&
types.Contains(ResponseTypes.Code) &&
context.StateTokenPrincipal is ClaimsPrincipal principal &&
- principal.HasScope(Scopes.OpenId) => (true, true, true),
+ principal.HasScope(Scopes.OpenId) => (true, true, true, true),
// The client credentials and resource owner password credentials grants don't have
// an equivalent in OpenID Connect so an identity token is typically never returned
@@ -2531,20 +2557,21 @@ public static partial class OpenIddictClientHandlers
// allow returning it as a non-standard artifact. As such, the identity token
// is not considered required but will always be validated using the same routine
// (except nonce validation) if it is present in the token response.
- GrantTypes.ClientCredentials or GrantTypes.Password => (true, false, true),
+ GrantTypes.ClientCredentials or GrantTypes.Password => (true, false, true, false),
// An identity token may or may not be returned as part of refresh token responses
// depending on the policy adopted by the remote authorization server. As such,
// the identity token is not considered required but will always be validated using
// the same routine (except nonce validation) if it is present in the token response.
- GrantTypes.RefreshToken => (true, false, true),
+ GrantTypes.RefreshToken => (true, false, true, false),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
(context.ExtractRefreshToken,
context.RequireRefreshToken,
- context.ValidateRefreshToken) = context.GrantType switch
+ context.ValidateRefreshToken,
+ context.RejectRefreshToken) = context.GrantType switch
{
// A refresh token may be returned as part of token responses, depending on the
// policy enforced by the remote authorization server (e.g the "offline_access"
@@ -2557,16 +2584,16 @@ public static partial class OpenIddictClientHandlers
GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList types &&
types.Contains(ResponseTypes.Code)
- => (true, false, false),
+ => (true, false, false, false),
// A refresh token may or may not be returned as part of client credentials,
// resource owner password credentials and refresh token responses depending
// on the policy adopted by the remote authorization server. As such, a
// refresh token is never considered required for such token responses.
GrantTypes.ClientCredentials or GrantTypes.Password or GrantTypes.RefreshToken
- => (true, false, false),
+ => (true, false, false, false),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
return default;
@@ -2719,10 +2746,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectBackchannelIdentityToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -3201,10 +3233,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectBackchannelAccessToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -3271,10 +3308,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectRefreshToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -3482,7 +3524,8 @@ public static partial class OpenIddictClientHandlers
// or responses will be extracted and validated when a userinfo request was sent.
(context.ExtractUserinfoToken,
context.RequireUserinfoToken,
- context.ValidateUserinfoToken) = (true, false, true);
+ context.ValidateUserinfoToken,
+ context.RejectUserinfoToken) = (true, false, true, true);
return default;
}
@@ -3584,10 +3627,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectUserinfoToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.Device.cs b/src/OpenIddict.Server/OpenIddictServerEvents.Device.cs
index 605f20b4..f32b270f 100644
--- a/src/OpenIddict.Server/OpenIddictServerEvents.Device.cs
+++ b/src/OpenIddict.Server/OpenIddictServerEvents.Device.cs
@@ -192,7 +192,7 @@ public static partial class OpenIddictServerEvents
}
///
- /// Gets or sets the security principal extracted from the user code.
+ /// Gets or sets the security principal extracted from the user code, if applicable.
///
public ClaimsPrincipal? Principal { get; set; }
}
@@ -220,6 +220,11 @@ public static partial class OpenIddictServerEvents
set => Transaction.Request = value;
}
+ ///
+ /// Gets or sets the security principal extracted from the user code, if applicable.
+ ///
+ public ClaimsPrincipal? UserCodePrincipal { get; set; }
+
///
/// Gets the additional parameters returned to the caller.
///
diff --git a/src/OpenIddict.Server/OpenIddictServerEvents.cs b/src/OpenIddict.Server/OpenIddictServerEvents.cs
index 9e964b6f..ba91329d 100644
--- a/src/OpenIddict.Server/OpenIddictServerEvents.cs
+++ b/src/OpenIddict.Server/OpenIddictServerEvents.cs
@@ -481,6 +481,62 @@ public static partial class OpenIddictServerEvents
///
public bool ValidateUserCode { get; set; }
+ ///
+ /// Gets or sets a boolean indicating whether an invalid access token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectAccessToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid authorization code
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectAuthorizationCode { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid device code
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectDeviceCode { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid generic token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectGenericToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid identity token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectIdentityToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid refresh token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectRefreshToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid user code
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectUserCode { get; set; }
+
///
/// Gets or sets the access token to validate, if applicable.
///
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
index e3790530..51f035da 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
@@ -1636,8 +1636,7 @@ public static partial class OpenIddictServerHandlers
}
///
- /// Contains the logic responsible for rejecting authorization
- /// requests that don't specify a valid id_token_hint.
+ /// Contains the logic responsible for validating the token(s) present in the request.
///
public sealed class ValidateToken : IOpenIddictServerHandler
{
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
index 50ea3383..d3d108fe 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
@@ -54,6 +54,11 @@ public static partial class OpenIddictServerHandlers
ApplyVerificationResponse.Descriptor,
ApplyVerificationResponse.Descriptor,
+ /*
+ * Verification request validation:
+ */
+ ValidateToken.Descriptor,
+
/*
* Verification request handling:
*/
@@ -951,6 +956,10 @@ public static partial class OpenIddictServerHandlers
var notification = new ValidateVerificationRequestContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
+ // Store the context object in the transaction so it can be later retrieved by handlers
+ // that want to access the context without triggering a new validation process.
+ context.Transaction.SetProperty(typeof(ValidateVerificationRequestContext).FullName!, notification);
+
if (notification.IsRequestHandled)
{
context.HandleRequest();
@@ -1122,42 +1131,33 @@ public static partial class OpenIddictServerHandlers
}
///
- /// Contains the logic responsible for attaching the claims principal resolved from the user code.
+ /// Contains the logic responsible for validating the token(s) present in the request.
///
- public sealed class AttachUserCodePrincipal : IOpenIddictServerHandler
+ public sealed class ValidateToken : IOpenIddictServerHandler
{
private readonly IOpenIddictServerDispatcher _dispatcher;
- public AttachUserCodePrincipal(IOpenIddictServerDispatcher dispatcher)
+ public ValidateToken(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
///
/// Gets the default descriptor definition assigned to this handler.
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
- = OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseScopedHandler()
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseScopedHandler()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
///
- public async ValueTask HandleAsync(HandleVerificationRequestContext context)
+ public async ValueTask HandleAsync(ValidateVerificationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
- // Note: the user_code may not be present (e.g when the user typed
- // the verification_uri manually without the user code appended).
- // In this case, ignore the missing token so that a view can be
- // rendered by the application to ask the user to enter the code.
- if (string.IsNullOrEmpty(context.Request.UserCode))
- {
- return;
- }
-
var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
@@ -1179,7 +1179,10 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected)
{
- // Note: authentication errors are deliberately not flowed up to the parent context.
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
return;
}
@@ -1187,5 +1190,38 @@ public static partial class OpenIddictServerHandlers
context.Principal = notification.UserCodePrincipal;
}
}
+
+ ///
+ /// Contains the logic responsible for attaching the principal extracted from the user code to the event context.
+ ///
+ public sealed class AttachUserCodePrincipal : IOpenIddictServerHandler
+ {
+ ///
+ /// Gets the default descriptor definition assigned to this handler.
+ ///
+ public static OpenIddictServerHandlerDescriptor Descriptor { get; }
+ = OpenIddictServerHandlerDescriptor.CreateBuilder()
+ .UseSingletonHandler()
+ .SetOrder(int.MinValue + 100_000)
+ .SetType(OpenIddictServerHandlerType.BuiltIn)
+ .Build();
+
+ ///
+ public ValueTask HandleAsync(HandleVerificationRequestContext context)
+ {
+ if (context is null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ var notification = context.Transaction.GetProperty(
+ typeof(ValidateVerificationRequestContext).FullName!) ??
+ throw new InvalidOperationException(SR.GetResourceString(SR.ID0007));
+
+ context.UserCodePrincipal ??= notification.Principal;
+
+ return default;
+ }
+ }
}
}
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
index 4f0f8a6d..77480861 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
@@ -1286,8 +1286,7 @@ public static partial class OpenIddictServerHandlers
}
///
- /// Contains the logic responsible for rejecting token requests that don't
- /// specify a valid authorization code, device code or refresh token.
+ /// Contains the logic responsible for validating the token(s) present in the request.
///
public sealed class ValidateToken : IOpenIddictServerHandler
{
@@ -1351,8 +1350,8 @@ public static partial class OpenIddictServerHandlers
// Attach the security principal extracted from the token to the validation context.
context.Principal = context.Request.IsAuthorizationCodeGrantType() ? notification.AuthorizationCodePrincipal :
- context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal :
- context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null;
+ context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal :
+ context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null;
}
}
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
index 553a9538..e29f87c3 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
@@ -650,7 +650,7 @@ public static partial class OpenIddictServerHandlers
}
///
- /// Contains the logic responsible for rejecting introspection requests that don't specify a valid token.
+ /// Contains the logic responsible for validating the token(s) present in the request.
///
public sealed class ValidateToken : IOpenIddictServerHandler
{
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
index 2ba37168..f09205a2 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
@@ -597,7 +597,7 @@ public static partial class OpenIddictServerHandlers
}
///
- /// Contains the logic responsible for rejecting revocation requests that don't specify a valid token.
+ /// Contains the logic responsible for validating the token(s) present in the request.
///
public sealed class ValidateToken : IOpenIddictServerHandler
{
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
index 4d9f423a..57ef3eb7 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
@@ -595,7 +595,7 @@ public static partial class OpenIddictServerHandlers
}
///
- /// Contains the logic responsible for rejecting logout requests that don't specify a valid id_token_hint.
+ /// Contains the logic responsible for validating the token(s) present in the request.
///
public sealed class ValidateToken : IOpenIddictServerHandler
{
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
index 275f5b75..b1948199 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
@@ -342,7 +342,7 @@ public static partial class OpenIddictServerHandlers
}
///
- /// Contains the logic responsible for rejecting userinfo requests that don't specify a valid token.
+ /// Contains the logic responsible for validating the token(s) present in the request.
///
public sealed class ValidateToken : IOpenIddictServerHandler
{
diff --git a/src/OpenIddict.Server/OpenIddictServerHandlers.cs b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
index 492b381c..19f2ba12 100644
--- a/src/OpenIddict.Server/OpenIddictServerHandlers.cs
+++ b/src/OpenIddict.Server/OpenIddictServerHandlers.cs
@@ -267,80 +267,90 @@ public static partial class OpenIddictServerHandlers
(context.ExtractAccessToken,
context.RequireAccessToken,
- context.ValidateAccessToken) = context.EndpointType switch
+ context.ValidateAccessToken,
+ context.RejectAccessToken) = context.EndpointType switch
{
// The userinfo endpoint requires sending a valid access token.
- OpenIddictServerEndpointType.Userinfo => (true, true, true),
+ OpenIddictServerEndpointType.Userinfo => (true, true, true, true),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
(context.ExtractAuthorizationCode,
context.RequireAuthorizationCode,
- context.ValidateAuthorizationCode) = context.EndpointType switch
+ context.ValidateAuthorizationCode,
+ context.RejectAuthorizationCode) = context.EndpointType switch
{
// The authorization code grant requires sending a valid authorization code.
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
- => (true, true, true),
+ => (true, true, true, true),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
(context.ExtractDeviceCode,
context.RequireDeviceCode,
- context.ValidateDeviceCode) = context.EndpointType switch
+ context.ValidateDeviceCode,
+ context.RejectDeviceCode) = context.EndpointType switch
{
// The device code grant requires sending a valid device code.
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType()
- => (true, true, true),
+ => (true, true, true, true),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
(context.ExtractGenericToken,
context.RequireGenericToken,
- context.ValidateGenericToken) = context.EndpointType switch
+ context.ValidateGenericToken,
+ context.RejectGenericToken) = context.EndpointType switch
{
// Tokens received by the introspection and revocation endpoints can be of any type.
// Additional token type filtering is made by the endpoint themselves, if needed.
OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation
- => (true, true, true),
+ => (true, true, true, true),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
(context.ExtractIdentityToken,
context.RequireIdentityToken,
- context.ValidateIdentityToken) = context.EndpointType switch
+ context.ValidateIdentityToken,
+ context.RejectIdentityToken) = context.EndpointType switch
{
// The identity token received by the authorization and logout
// endpoints are not required and serve as optional hints.
+ //
+ // As such, identity token hints are extracted and validated, but
+ // the authentication demand is not rejected if they are not valid.
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout
- => (true, false, true),
+ => (true, false, true, false),
- _ => (false, false, true)
+ _ => (false, false, false, false)
};
(context.ExtractRefreshToken,
context.RequireRefreshToken,
- context.ValidateRefreshToken) = context.EndpointType switch
+ context.ValidateRefreshToken,
+ context.RejectRefreshToken) = context.EndpointType switch
{
// The refresh token grant requires sending a valid refresh token.
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
- => (true, true, true),
+ => (true, true, true, true),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
(context.ExtractUserCode,
context.RequireUserCode,
- context.ValidateUserCode) = context.EndpointType switch
+ context.ValidateUserCode,
+ context.RejectUserCode) = context.EndpointType switch
{
// Note: the verification endpoint can be accessed without specifying a
// user code (that can be later set by the user using a form, for instance).
- OpenIddictServerEndpointType.Verification => (true, false, true),
+ OpenIddictServerEndpointType.Verification => (true, false, true, false),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
return default;
@@ -442,10 +452,10 @@ public static partial class OpenIddictServerHandlers
///
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder()
- .UseSingletonHandler()
+ .UseSingletonHandler()
// Note: this handler is registered with a high gap to allow handlers
// that do token extraction to be executed before this handler runs.
- .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 50_000)
+ .SetOrder(ResolveValidatedTokens.Descriptor.Order + 50_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
@@ -533,10 +543,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectAccessToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -600,10 +615,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectAuthorizationCode)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -667,10 +687,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectDeviceCode)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -741,10 +766,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectGenericToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -811,10 +841,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectIdentityToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -878,10 +913,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectRefreshToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
@@ -945,10 +985,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectUserCode)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
diff --git a/src/OpenIddict.Validation/OpenIddictValidationEvents.cs b/src/OpenIddict.Validation/OpenIddictValidationEvents.cs
index addab223..4d4e14c9 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationEvents.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationEvents.cs
@@ -321,6 +321,14 @@ public static partial class OpenIddictValidationEvents
/// recommended, except when dealing with non-standard clients.
///
public bool ValidateAccessToken { get; set; }
+
+ ///
+ /// Gets or sets a boolean indicating whether an invalid access token
+ /// will cause the authentication demand to be rejected or will be ignored.
+ /// Note: overriding the value of this property is generally not
+ /// recommended, except when dealing with non-standard clients.
+ ///
+ public bool RejectAccessToken { get; set; }
}
///
diff --git a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
index 285eead7..40ffd897 100644
--- a/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
+++ b/src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
@@ -90,13 +90,14 @@ public static partial class OpenIddictValidationHandlers
(context.ExtractAccessToken,
context.RequireAccessToken,
- context.ValidateAccessToken) = context.EndpointType switch
+ context.ValidateAccessToken,
+ context.RejectAccessToken) = context.EndpointType switch
{
// The validation handler is responsible for validating access tokens for endpoints
// it doesn't manage (typically, API endpoints using token authentication).
- OpenIddictValidationEndpointType.Unknown => (true, true, true),
+ OpenIddictValidationEndpointType.Unknown => (true, true, true, true),
- _ => (false, false, false)
+ _ => (false, false, false, false)
};
// Note: unlike the equivalent event in the server stack, authentication can be triggered for
@@ -203,10 +204,15 @@ public static partial class OpenIddictValidationHandlers
else if (notification.IsRejected)
{
- context.Reject(
- error: notification.Error ?? Errors.InvalidRequest,
- description: notification.ErrorDescription,
- uri: notification.ErrorUri);
+ if (context.RejectAccessToken)
+ {
+ context.Reject(
+ error: notification.Error ?? Errors.InvalidRequest,
+ description: notification.ErrorDescription,
+ uri: notification.ErrorUri);
+ return;
+ }
+
return;
}
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
index 05f33f57..94efc146 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
@@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
using static OpenIddict.Server.OpenIddictServerEvents;
+using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
namespace OpenIddict.Server.IntegrationTests;
@@ -1859,10 +1860,62 @@ public abstract partial class OpenIddictServerIntegrationTests
}
[Fact]
- public async Task ValidateAuthorizationRequest_InvalidIdentityTokenHintCausesAnError()
+ public async Task ValidateAuthorizationRequest_InvalidIdentityTokenHintDoesNotCauseAnError()
{
// Arrange
- await using var server = await CreateServerAsync(options => options.EnableDegradedMode());
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Null(context.IdentityTokenHintPrincipal);
+
+ context.Principal = new ClaimsPrincipal(new ClaimsIdentity("Bearer"))
+ .SetClaim(Claims.Subject, "Bob le Magnifique");
+
+ return default;
+ }));
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/authorize", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ IdTokenHint = "id_token",
+ RedirectUri = "http://www.fabrikam.com/path",
+ ResponseType = ResponseTypes.Token
+ });
+
+ // Assert
+ Assert.Null(response.Code);
+ Assert.NotNull(response.AccessToken);
+ }
+
+ [Fact]
+ public async Task ValidateAuthorizationRequest_InvalidIdentityTokenHintCausesAnErrorWhenRejectionIsEnabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ context.RejectIdentityToken = true;
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500);
+ });
+ });
+
await using var client = await server.CreateClientAsync();
// Act
diff --git a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs
index 83c2feef..7466436e 100644
--- a/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs
+++ b/test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs
@@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
using static OpenIddict.Server.OpenIddictServerEvents;
+using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
namespace OpenIddict.Server.IntegrationTests;
@@ -443,10 +444,60 @@ public abstract partial class OpenIddictServerIntegrationTests
}
[Fact]
- public async Task ValidateLogoutRequest_InvalidIdentityTokenHintCausesAnError()
+ public async Task ValidateLogoutRequest_InvalidIdentityTokenHintDoesNotCauseAnError()
{
// Arrange
- await using var server = await CreateServerAsync(options => options.EnableDegradedMode());
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ builder.UseInlineHandler(context =>
+ {
+ Assert.Null(context.IdentityTokenHintPrincipal);
+
+ context.SignOut();
+
+ return default;
+ }));
+ });
+
+ await using var client = await server.CreateClientAsync();
+
+ // Act
+ var response = await client.PostAsync("/connect/logout", new OpenIddictRequest
+ {
+ ClientId = "Fabrikam",
+ IdTokenHint = "id_token",
+ PostLogoutRedirectUri = "http://www.fabrikam.com/path",
+ State = "af0ifjsldkj"
+ });
+
+ // Assert
+ Assert.Equal("af0ifjsldkj", response.State);
+ }
+
+ [Fact]
+ public async Task ValidateLogoutRequest_InvalidIdentityTokenHintCausesAnErrorWhenRejectionIsEnabled()
+ {
+ // Arrange
+ await using var server = await CreateServerAsync(options =>
+ {
+ options.EnableDegradedMode();
+
+ options.AddEventHandler(builder =>
+ {
+ builder.UseInlineHandler(context =>
+ {
+ context.RejectIdentityToken = true;
+
+ return default;
+ });
+
+ builder.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500);
+ });
+ });
+
await using var client = await server.CreateClientAsync();
// Act