Browse Source

Introduce new properties to disable token rejection and don't reject authentication demands for invalid identity token hints

pull/1668/head
Kévin Chalet 3 years ago
parent
commit
eb1fcc82bd
  1. 64
      src/OpenIddict.Client/OpenIddictClientEvents.cs
  2. 166
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  3. 7
      src/OpenIddict.Server/OpenIddictServerEvents.Device.cs
  4. 56
      src/OpenIddict.Server/OpenIddictServerEvents.cs
  5. 3
      src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
  6. 68
      src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs
  7. 7
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  8. 2
      src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
  9. 2
      src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs
  10. 2
      src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
  11. 2
      src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
  12. 147
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  13. 8
      src/OpenIddict.Validation/OpenIddictValidationEvents.cs
  14. 20
      src/OpenIddict.Validation/OpenIddictValidationHandlers.cs
  15. 57
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs
  16. 55
      test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs

64
src/OpenIddict.Client/OpenIddictClientEvents.cs

@ -565,6 +565,70 @@ public static partial class OpenIddictClientEvents
/// </summary>
public bool ValidateUserinfoToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectAuthorizationCode { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectBackchannelAccessToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectBackchannelIdentityToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectFrontchannelAccessToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectFrontchannelIdentityToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectRefreshToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectStateToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectUserinfoToken { get; set; }
/// <summary>
/// Gets or sets the authorization code to validate, if applicable.
/// </summary>

166
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<string> 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<string> 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<string> 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<string> 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<string> 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<string> 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;
}

7
src/OpenIddict.Server/OpenIddictServerEvents.Device.cs

@ -192,7 +192,7 @@ public static partial class OpenIddictServerEvents
}
/// <summary>
/// Gets or sets the security principal extracted from the user code.
/// Gets or sets the security principal extracted from the user code, if applicable.
/// </summary>
public ClaimsPrincipal? Principal { get; set; }
}
@ -220,6 +220,11 @@ public static partial class OpenIddictServerEvents
set => Transaction.Request = value;
}
/// <summary>
/// Gets or sets the security principal extracted from the user code, if applicable.
/// </summary>
public ClaimsPrincipal? UserCodePrincipal { get; set; }
/// <summary>
/// Gets the additional parameters returned to the caller.
/// </summary>

56
src/OpenIddict.Server/OpenIddictServerEvents.cs

@ -481,6 +481,62 @@ public static partial class OpenIddictServerEvents
/// </summary>
public bool ValidateUserCode { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectAccessToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectAuthorizationCode { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectDeviceCode { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectGenericToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectIdentityToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectRefreshToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectUserCode { get; set; }
/// <summary>
/// Gets or sets the access token to validate, if applicable.
/// </summary>

3
src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs

@ -1636,8 +1636,7 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// 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.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{

68
src/OpenIddict.Server/OpenIddictServerHandlers.Device.cs

@ -54,6 +54,11 @@ public static partial class OpenIddictServerHandlers
ApplyVerificationResponse<ProcessRequestContext>.Descriptor,
ApplyVerificationResponse<ProcessSignInContext>.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
}
/// <summary>
/// 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.
/// </summary>
public sealed class AttachUserCodePrincipal : IOpenIddictServerHandler<HandleVerificationRequestContext>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateVerificationRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public AttachUserCodePrincipal(IOpenIddictServerDispatcher dispatcher)
public ValidateToken(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleVerificationRequestContext>()
.UseScopedHandler<AttachUserCodePrincipal>()
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateVerificationRequestContext>()
.UseScopedHandler<ValidateToken>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
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;
}
}
/// <summary>
/// Contains the logic responsible for attaching the principal extracted from the user code to the event context.
/// </summary>
public sealed class AttachUserCodePrincipal : IOpenIddictServerHandler<HandleVerificationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleVerificationRequestContext>()
.UseSingletonHandler<AttachUserCodePrincipal>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(HandleVerificationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = context.Transaction.GetProperty<ValidateVerificationRequestContext>(
typeof(ValidateVerificationRequestContext).FullName!) ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0007));
context.UserCodePrincipal ??= notification.Principal;
return default;
}
}
}
}

7
src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs

@ -1286,8 +1286,7 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// 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.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
@ -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;
}
}

2
src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs

@ -650,7 +650,7 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// 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.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateIntrospectionRequestContext>
{

2
src/OpenIddict.Server/OpenIddictServerHandlers.Revocation.cs

@ -597,7 +597,7 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// 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.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateRevocationRequestContext>
{

2
src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs

@ -595,7 +595,7 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// 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.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateLogoutRequestContext>
{

2
src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs

@ -342,7 +342,7 @@ public static partial class OpenIddictServerHandlers
}
/// <summary>
/// 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.
/// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateUserinfoRequestContext>
{

147
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
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<EvaluateValidatedTokens>()
.UseSingletonHandler<ValidateRequiredTokens>()
// 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;
}

8
src/OpenIddict.Validation/OpenIddictValidationEvents.cs

@ -321,6 +321,14 @@ public static partial class OpenIddictValidationEvents
/// recommended, except when dealing with non-standard clients.
/// </summary>
public bool ValidateAccessToken { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool RejectAccessToken { get; set; }
}
/// <summary>

20
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;
}

57
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<HandleAuthorizationRequestContext>(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<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
context.RejectIdentityToken = true;
return default;
});
builder.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500);
});
});
await using var client = await server.CreateClientAsync();
// Act

55
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<HandleLogoutRequestContext>(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<ProcessAuthenticationContext>(builder =>
{
builder.UseInlineHandler(context =>
{
context.RejectIdentityToken = true;
return default;
});
builder.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 500);
});
});
await using var client = await server.CreateClientAsync();
// Act

Loading…
Cancel
Save