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 4 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> /// </summary>
public bool ValidateUserinfoToken { get; set; } 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> /// <summary>
/// Gets or sets the authorization code to validate, if applicable. /// Gets or sets the authorization code to validate, if applicable.
/// </summary> /// </summary>

166
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -401,23 +401,24 @@ public static partial class OpenIddictClientHandlers
(context.ExtractStateToken, (context.ExtractStateToken,
context.RequireStateToken, 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 // 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 // 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 // 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 // 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. // 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 // 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 // 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 // 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 // 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. // 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; return default;
@ -555,10 +556,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectStateToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -1204,7 +1210,8 @@ public static partial class OpenIddictClientHandlers
(context.ExtractAuthorizationCode, (context.ExtractAuthorizationCode,
context.RequireAuthorizationCode, 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 // 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 // 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 GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList<string> types && context.ResponseType?.Split(Separators.Space) is IList<string> types &&
types.Contains(ResponseTypes.Code) types.Contains(ResponseTypes.Code)
=> (true, true, false), => (true, true, false, false),
_ => (false, false, false) _ => (false, false, false, false)
}; };
(context.ExtractFrontchannelAccessToken, (context.ExtractFrontchannelAccessToken,
context.RequireFrontchannelAccessToken, 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 // 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 // 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 GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList<string> types && context.ResponseType?.Split(Separators.Space) is IList<string> types &&
types.Contains(ResponseTypes.Token) types.Contains(ResponseTypes.Token)
=> (true, true, false), => (true, true, false, false),
_ => (false, false, false) _ => (false, false, false, false)
}; };
(context.ExtractFrontchannelIdentityToken, (context.ExtractFrontchannelIdentityToken,
context.RequireFrontchannelIdentityToken, 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 // 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 // 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 GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList<string> types && context.ResponseType?.Split(Separators.Space) is IList<string> types &&
types.Contains(ResponseTypes.IdToken) types.Contains(ResponseTypes.IdToken)
=> (true, true, true), => (true, true, true, true),
_ => (false, false, false) _ => (false, false, false, false)
}; };
return default; return default;
@ -1414,10 +1423,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectFrontchannelIdentityToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -1932,10 +1946,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectFrontchannelAccessToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -2002,10 +2021,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectAuthorizationCode)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -2489,7 +2513,8 @@ public static partial class OpenIddictClientHandlers
(context.ExtractBackchannelAccessToken, (context.ExtractBackchannelAccessToken,
context.RequireBackchannelAccessToken, 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 // 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. // 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 GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList<string> types && context.ResponseType?.Split(Separators.Space) is IList<string> types &&
types.Contains(ResponseTypes.Code) types.Contains(ResponseTypes.Code)
=> (true, true, false), => (true, true, false, false),
// An access token is always returned as part of client credentials, // An access token is always returned as part of client credentials,
// resource owner password credentials and refresh token responses. // resource owner password credentials and refresh token responses.
GrantTypes.ClientCredentials or GrantTypes.Password or GrantTypes.RefreshToken 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.ExtractBackchannelIdentityToken,
context.RequireBackchannelIdentityToken, 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 // 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, // 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 && context.ResponseType?.Split(Separators.Space) is IList<string> types &&
types.Contains(ResponseTypes.Code) && types.Contains(ResponseTypes.Code) &&
context.StateTokenPrincipal is ClaimsPrincipal principal && 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 // 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 // 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 // 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 // is not considered required but will always be validated using the same routine
// (except nonce validation) if it is present in the token response. // (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 // 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, // 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 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. // 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.ExtractRefreshToken,
context.RequireRefreshToken, 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 // 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" // 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 GrantTypes.AuthorizationCode or GrantTypes.Implicit when
context.ResponseType?.Split(Separators.Space) is IList<string> types && context.ResponseType?.Split(Separators.Space) is IList<string> types &&
types.Contains(ResponseTypes.Code) 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, // A refresh token may or may not be returned as part of client credentials,
// resource owner password credentials and refresh token responses depending // resource owner password credentials and refresh token responses depending
// on the policy adopted by the remote authorization server. As such, a // on the policy adopted by the remote authorization server. As such, a
// refresh token is never considered required for such token responses. // refresh token is never considered required for such token responses.
GrantTypes.ClientCredentials or GrantTypes.Password or GrantTypes.RefreshToken GrantTypes.ClientCredentials or GrantTypes.Password or GrantTypes.RefreshToken
=> (true, false, false), => (true, false, false, false),
_ => (false, false, false) _ => (false, false, false, false)
}; };
return default; return default;
@ -2719,10 +2746,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectBackchannelIdentityToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -3201,10 +3233,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectBackchannelAccessToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -3271,10 +3308,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectRefreshToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -3482,7 +3524,8 @@ public static partial class OpenIddictClientHandlers
// or responses will be extracted and validated when a userinfo request was sent. // or responses will be extracted and validated when a userinfo request was sent.
(context.ExtractUserinfoToken, (context.ExtractUserinfoToken,
context.RequireUserinfoToken, context.RequireUserinfoToken,
context.ValidateUserinfoToken) = (true, false, true); context.ValidateUserinfoToken,
context.RejectUserinfoToken) = (true, false, true, true);
return default; return default;
} }
@ -3584,10 +3627,15 @@ public static partial class OpenIddictClientHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectUserinfoToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }

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

@ -192,7 +192,7 @@ public static partial class OpenIddictServerEvents
} }
/// <summary> /// <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> /// </summary>
public ClaimsPrincipal? Principal { get; set; } public ClaimsPrincipal? Principal { get; set; }
} }
@ -220,6 +220,11 @@ public static partial class OpenIddictServerEvents
set => Transaction.Request = value; 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> /// <summary>
/// Gets the additional parameters returned to the caller. /// Gets the additional parameters returned to the caller.
/// </summary> /// </summary>

56
src/OpenIddict.Server/OpenIddictServerEvents.cs

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

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

@ -1636,8 +1636,7 @@ public static partial class OpenIddictServerHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for rejecting authorization /// Contains the logic responsible for validating the token(s) present in the request.
/// requests that don't specify a valid id_token_hint.
/// </summary> /// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateAuthorizationRequestContext> 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<ProcessRequestContext>.Descriptor,
ApplyVerificationResponse<ProcessSignInContext>.Descriptor, ApplyVerificationResponse<ProcessSignInContext>.Descriptor,
/*
* Verification request validation:
*/
ValidateToken.Descriptor,
/* /*
* Verification request handling: * Verification request handling:
*/ */
@ -951,6 +956,10 @@ public static partial class OpenIddictServerHandlers
var notification = new ValidateVerificationRequestContext(context.Transaction); var notification = new ValidateVerificationRequestContext(context.Transaction);
await _dispatcher.DispatchAsync(notification); 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) if (notification.IsRequestHandled)
{ {
context.HandleRequest(); context.HandleRequest();
@ -1122,42 +1131,33 @@ public static partial class OpenIddictServerHandlers
} }
/// <summary> /// <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> /// </summary>
public sealed class AttachUserCodePrincipal : IOpenIddictServerHandler<HandleVerificationRequestContext> public sealed class ValidateToken : IOpenIddictServerHandler<ValidateVerificationRequestContext>
{ {
private readonly IOpenIddictServerDispatcher _dispatcher; private readonly IOpenIddictServerDispatcher _dispatcher;
public AttachUserCodePrincipal(IOpenIddictServerDispatcher dispatcher) public ValidateToken(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
/// </summary> /// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleVerificationRequestContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateVerificationRequestContext>()
.UseScopedHandler<AttachUserCodePrincipal>() .UseScopedHandler<ValidateToken>()
.SetOrder(int.MinValue + 100_000) .SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask HandleAsync(HandleVerificationRequestContext context) public async ValueTask HandleAsync(ValidateVerificationRequestContext context)
{ {
if (context is null) if (context is null)
{ {
throw new ArgumentNullException(nameof(context)); 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); var notification = new ProcessAuthenticationContext(context.Transaction);
await _dispatcher.DispatchAsync(notification); await _dispatcher.DispatchAsync(notification);
@ -1179,7 +1179,10 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected) 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; return;
} }
@ -1187,5 +1190,38 @@ public static partial class OpenIddictServerHandlers
context.Principal = notification.UserCodePrincipal; 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> /// <summary>
/// Contains the logic responsible for rejecting token requests that don't /// Contains the logic responsible for validating the token(s) present in the request.
/// specify a valid authorization code, device code or refresh token.
/// </summary> /// </summary>
public sealed class ValidateToken : IOpenIddictServerHandler<ValidateTokenRequestContext> 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. // Attach the security principal extracted from the token to the validation context.
context.Principal = context.Request.IsAuthorizationCodeGrantType() ? notification.AuthorizationCodePrincipal : context.Principal = context.Request.IsAuthorizationCodeGrantType() ? notification.AuthorizationCodePrincipal :
context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal : context.Request.IsDeviceCodeGrantType() ? notification.DeviceCodePrincipal :
context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null; context.Request.IsRefreshTokenGrantType() ? notification.RefreshTokenPrincipal : null;
} }
} }

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

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

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

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

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

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

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

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

147
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -267,80 +267,90 @@ public static partial class OpenIddictServerHandlers
(context.ExtractAccessToken, (context.ExtractAccessToken,
context.RequireAccessToken, context.RequireAccessToken,
context.ValidateAccessToken) = context.EndpointType switch context.ValidateAccessToken,
context.RejectAccessToken) = context.EndpointType switch
{ {
// The userinfo endpoint requires sending a valid access token. // 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.ExtractAuthorizationCode,
context.RequireAuthorizationCode, context.RequireAuthorizationCode,
context.ValidateAuthorizationCode) = context.EndpointType switch context.ValidateAuthorizationCode,
context.RejectAuthorizationCode) = context.EndpointType switch
{ {
// The authorization code grant requires sending a valid authorization code. // The authorization code grant requires sending a valid authorization code.
OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType()
=> (true, true, true), => (true, true, true, true),
_ => (false, false, false) _ => (false, false, false, false)
}; };
(context.ExtractDeviceCode, (context.ExtractDeviceCode,
context.RequireDeviceCode, context.RequireDeviceCode,
context.ValidateDeviceCode) = context.EndpointType switch context.ValidateDeviceCode,
context.RejectDeviceCode) = context.EndpointType switch
{ {
// The device code grant requires sending a valid device code. // The device code grant requires sending a valid device code.
OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType()
=> (true, true, true), => (true, true, true, true),
_ => (false, false, false) _ => (false, false, false, false)
}; };
(context.ExtractGenericToken, (context.ExtractGenericToken,
context.RequireGenericToken, 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. // 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. // Additional token type filtering is made by the endpoint themselves, if needed.
OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation
=> (true, true, true), => (true, true, true, true),
_ => (false, false, false) _ => (false, false, false, false)
}; };
(context.ExtractIdentityToken, (context.ExtractIdentityToken,
context.RequireIdentityToken, context.RequireIdentityToken,
context.ValidateIdentityToken) = context.EndpointType switch context.ValidateIdentityToken,
context.RejectIdentityToken) = context.EndpointType switch
{ {
// The identity token received by the authorization and logout // The identity token received by the authorization and logout
// endpoints are not required and serve as optional hints. // 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 OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout
=> (true, false, true), => (true, false, true, false),
_ => (false, false, true) _ => (false, false, false, false)
}; };
(context.ExtractRefreshToken, (context.ExtractRefreshToken,
context.RequireRefreshToken, context.RequireRefreshToken,
context.ValidateRefreshToken) = context.EndpointType switch context.ValidateRefreshToken,
context.RejectRefreshToken) = context.EndpointType switch
{ {
// The refresh token grant requires sending a valid refresh token. // The refresh token grant requires sending a valid refresh token.
OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType()
=> (true, true, true), => (true, true, true, true),
_ => (false, false, false) _ => (false, false, false, false)
}; };
(context.ExtractUserCode, (context.ExtractUserCode,
context.RequireUserCode, context.RequireUserCode,
context.ValidateUserCode) = context.EndpointType switch context.ValidateUserCode,
context.RejectUserCode) = context.EndpointType switch
{ {
// Note: the verification endpoint can be accessed without specifying a // 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). // 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; return default;
@ -442,10 +452,10 @@ public static partial class OpenIddictServerHandlers
/// </summary> /// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<EvaluateValidatedTokens>() .UseSingletonHandler<ValidateRequiredTokens>()
// Note: this handler is registered with a high gap to allow handlers // Note: this handler is registered with a high gap to allow handlers
// that do token extraction to be executed before this handler runs. // 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) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -533,10 +543,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectAccessToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -600,10 +615,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectAuthorizationCode)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -667,10 +687,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectDeviceCode)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -741,10 +766,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectGenericToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -811,10 +841,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectIdentityToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -878,10 +913,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectRefreshToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }
@ -945,10 +985,15 @@ public static partial class OpenIddictServerHandlers
else if (notification.IsRejected) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectUserCode)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
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. /// recommended, except when dealing with non-standard clients.
/// </summary> /// </summary>
public bool ValidateAccessToken { get; set; } 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> /// <summary>

20
src/OpenIddict.Validation/OpenIddictValidationHandlers.cs

@ -90,13 +90,14 @@ public static partial class OpenIddictValidationHandlers
(context.ExtractAccessToken, (context.ExtractAccessToken,
context.RequireAccessToken, 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 // The validation handler is responsible for validating access tokens for endpoints
// it doesn't manage (typically, API endpoints using token authentication). // 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 // 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) else if (notification.IsRejected)
{ {
context.Reject( if (context.RejectAccessToken)
error: notification.Error ?? Errors.InvalidRequest, {
description: notification.ErrorDescription, context.Reject(
uri: notification.ErrorUri); error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
return; return;
} }

57
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Authentication.cs

@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;
using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
namespace OpenIddict.Server.IntegrationTests; namespace OpenIddict.Server.IntegrationTests;
@ -1859,10 +1860,62 @@ public abstract partial class OpenIddictServerIntegrationTests
} }
[Fact] [Fact]
public async Task ValidateAuthorizationRequest_InvalidIdentityTokenHintCausesAnError() public async Task ValidateAuthorizationRequest_InvalidIdentityTokenHintDoesNotCauseAnError()
{ {
// Arrange // 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(); await using var client = await server.CreateClientAsync();
// Act // Act

55
test/OpenIddict.Server.IntegrationTests/OpenIddictServerIntegrationTests.Session.cs

@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
using Moq; using Moq;
using Xunit; using Xunit;
using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers;
using static OpenIddict.Server.OpenIddictServerHandlers.Protection; using static OpenIddict.Server.OpenIddictServerHandlers.Protection;
namespace OpenIddict.Server.IntegrationTests; namespace OpenIddict.Server.IntegrationTests;
@ -443,10 +444,60 @@ public abstract partial class OpenIddictServerIntegrationTests
} }
[Fact] [Fact]
public async Task ValidateLogoutRequest_InvalidIdentityTokenHintCausesAnError() public async Task ValidateLogoutRequest_InvalidIdentityTokenHintDoesNotCauseAnError()
{ {
// Arrange // 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(); await using var client = await server.CreateClientAsync();
// Act // Act

Loading…
Cancel
Save