Browse Source

Revamp the challenge responses handling logic

pull/1443/head
Kévin Chalet 4 years ago
parent
commit
120a29fe47
  1. 2
      sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs
  2. 1
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.Authentication.cs
  3. 54
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs
  4. 141
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  5. 1
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs
  6. 2
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs
  7. 2
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Discovery.cs
  8. 1
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs
  9. 1
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs
  10. 1
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs
  11. 1
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs
  12. 1
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Userinfo.cs
  13. 179
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
  14. 51
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  15. 155
      src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs
  16. 192
      src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs
  17. 60
      src/OpenIddict.Validation/OpenIddictValidationHandlers.cs

2
sandbox/OpenIddict.Sandbox.AspNet.Client/Startup.cs

@ -22,7 +22,7 @@ namespace OpenIddict.Sandbox.AspNet.Client
// Register the Autofac scope injector middleware. // Register the Autofac scope injector middleware.
app.UseAutofacLifetimeScopeInjector(container); app.UseAutofacLifetimeScopeInjector(container);
// Register the cookie middleware responsible of storing the user sessions. // Register the cookie middleware responsible for storing the user sessions.
app.UseCookieAuthentication(new CookieAuthenticationOptions app.UseCookieAuthentication(new CookieAuthenticationOptions
{ {
ExpireTimeSpan = TimeSpan.FromMinutes(50), ExpireTimeSpan = TimeSpan.FromMinutes(50),

1
src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.Authentication.cs

@ -33,6 +33,7 @@ public static partial class OpenIddictClientOwinHandlers
* Redirection response handling: * Redirection response handling:
*/ */
AttachHttpResponseCode<ApplyRedirectionResponseContext>.Descriptor, AttachHttpResponseCode<ApplyRedirectionResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyRedirectionResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyRedirectionResponseContext>.Descriptor, AttachCacheControlHeader<ApplyRedirectionResponseContext>.Descriptor,
ProcessPassthroughErrorResponse<ApplyRedirectionResponseContext, RequireRedirectionEndpointPassthroughEnabled>.Descriptor, ProcessPassthroughErrorResponse<ApplyRedirectionResponseContext, RequireRedirectionEndpointPassthroughEnabled>.Descriptor,
ProcessLocalErrorResponse<ApplyRedirectionResponseContext>.Descriptor); ProcessLocalErrorResponse<ApplyRedirectionResponseContext>.Descriptor);

54
src/OpenIddict.Client.Owin/OpenIddictClientOwinHandlers.cs

@ -616,6 +616,58 @@ public static partial class OpenIddictClientOwinHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for attaching an OWIN response chalenge to the context, if necessary.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class AttachOwinResponseChallenge<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<AttachOwinResponseChallenge<TContext>>()
.SetOrder(AttachHttpResponseCode<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetOwinRequest()?.Context.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
// OWIN authentication middleware configured to use active authentication (which is the default mode)
// are known to aggressively intercept 401 responses even if the request is already considered fully
// handled. In practice, this behavior is often seen with the cookies authentication middleware,
// that will rewrite the 401 responses returned by OpenIddict and try to redirect the user agent
// to the login page configured in the options. To prevent this undesirable behavior, a fake
// response challenge pointing to a non-existent middleware is manually added to the OWIN context
// to prevent the active authentication middleware from rewriting OpenIddict's 401 HTTP responses.
//
// Note: while 403 responses are generally not intercepted by the built-in OWIN authentication
// middleware, they are treated the same way as 401 responses to account for custom middleware
// that may potentially use the same interception logic for both 401 and 403 HTTP responses.
if (response.StatusCode is 401 or 403 &&
response.Context.Authentication.AuthenticationResponseChallenge is null)
{
response.Context.Authentication.AuthenticationResponseChallenge =
new AuthenticationResponseChallenge(new[] { Guid.NewGuid().ToString() }, null);
}
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for attaching the appropriate HTTP response cache headers. /// Contains the logic responsible for attaching the appropriate HTTP response cache headers.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
@ -629,7 +681,7 @@ public static partial class OpenIddictClientOwinHandlers
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>() = OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<AttachCacheControlHeader<TContext>>() .UseSingletonHandler<AttachCacheControlHeader<TContext>>()
.SetOrder(AttachHttpResponseCode<TContext>.Descriptor.Order + 1_000) .SetOrder(AttachOwinResponseChallenge<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn) .SetType(OpenIddictClientHandlerType.BuiltIn)
.Build(); .Build();

141
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs

@ -37,6 +37,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers
/* /*
* Challenge processing: * Challenge processing:
*/ */
AttachHostChallengeError.Descriptor,
ResolveHostChallengeParameters.Descriptor, ResolveHostChallengeParameters.Descriptor,
/* /*
@ -277,11 +278,10 @@ public static partial class OpenIddictServerAspNetCoreHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional sign-in parameters stored in the ASP.NET /// Contains the logic responsible for attaching the error details using the ASP.NET Core authentication properties.
/// Core authentication properties specified by the application that triggered the sign-in operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core. /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary> /// </summary>
public class ResolveHostChallengeParameters : IOpenIddictServerHandler<ProcessChallengeContext> public class AttachHostChallengeError : IOpenIddictServerHandler<ProcessChallengeContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -289,7 +289,7 @@ public static partial class OpenIddictServerAspNetCoreHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ResolveHostChallengeParameters>() .UseSingletonHandler<AttachHostChallengeError>()
.SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500) .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -303,33 +303,48 @@ public static partial class OpenIddictServerAspNetCoreHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not null)
{ {
return default; context.Response.Error = properties.GetString(Properties.Error);
context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription);
context.Response.ErrorUri = properties.GetString(Properties.ErrorUri);
context.Response.Scope = properties.GetString(Properties.Scope);
} }
if (properties.Items.TryGetValue(Properties.Error, out string? error) && return default;
!string.IsNullOrEmpty(error)) }
{ }
context.Parameters[Parameters.Error] = error;
}
if (properties.Items.TryGetValue(Properties.ErrorDescription, out string? description) && /// <summary>
!string.IsNullOrEmpty(description)) /// Contains the logic responsible for resolving the additional sign-in parameters stored in the ASP.NET
{ /// Core authentication properties specified by the application that triggered the sign-in operation.
context.Parameters[Parameters.ErrorDescription] = description; /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
} /// </summary>
public class ResolveHostChallengeParameters : IOpenIddictServerHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ResolveHostChallengeParameters>()
.SetOrder(AttachChallengeParameters.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
if (properties.Items.TryGetValue(Properties.ErrorUri, out string? uri) && /// <inheritdoc/>
!string.IsNullOrEmpty(uri)) public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{ {
context.Parameters[Parameters.ErrorUri] = uri; throw new ArgumentNullException(nameof(context));
} }
if (properties.Items.TryGetValue(Properties.Scope, out string? scope) && var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
!string.IsNullOrEmpty(scope)) if (properties is null)
{ {
context.Parameters[Parameters.Scope] = scope; return default;
} }
foreach (var parameter in properties.Parameters) foreach (var parameter in properties.Parameters)
@ -875,29 +890,37 @@ public static partial class OpenIddictServerAspNetCoreHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007));
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack. // this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetHttpRequest()?.HttpContext.Response ?? var response = context.Transaction.GetHttpRequest()?.HttpContext.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0114));
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); response.StatusCode = (context.EndpointType, context.Transaction.Response.Error) switch
// When client authentication is made using basic authentication, the authorization server MUST return
// a 401 response with a valid WWW-Authenticate header containing the Basic scheme and a non-empty realm.
// A similar error MAY be returned even when basic authentication is not used and MUST also be returned
// when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme.
// To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors
// and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials
// were specified in the request form instead of the HTTP headers, as allowed by the specification.
response.StatusCode = context.Transaction.Response.Error switch
{ {
null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects). // Note: the default code may be replaced by another handler (e.g when doing redirects).
(_, null or { Length: 0 }) => 200,
// Unlike other server endpoints, errors returned by the userinfo endpoint follow the same logic as
// errors returned by API endpoints implementing bearer token authentication and MUST be returned
// as part of the standard WWW-Authenticate header. For more information, see
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError.
(OpenIddictServerEndpointType.Userinfo, Errors.InvalidToken or Errors.MissingToken) => 401,
(OpenIddictServerEndpointType.Userinfo, Errors.InsufficientAccess or Errors.InsufficientScope) => 403,
Errors.InvalidClient or Errors.InvalidToken or Errors.MissingToken => 401, // When client authentication is made using basic authentication, the authorization server
// MUST return a 401 response with a valid WWW-Authenticate header containing the HTTP Basic
// authentication scheme. A similar error MAY be returned even when using client_secret_post.
// To simplify the logic, a 401 response with the Basic scheme is returned for invalid_client
// errors, even if credentials were specified in the form, as allowed by the specification.
(not OpenIddictServerEndpointType.Userinfo, Errors.InvalidClient) => 401,
Errors.InsufficientAccess or Errors.InsufficientScope => 403, (_, Errors.ServerError) => 500,
_ => 400 // Note: unless specified otherwise, errors are expected to result in 400 responses.
// See https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 for more information.
_ => 400
}; };
return default; return default;
@ -973,29 +996,35 @@ public static partial class OpenIddictServerAspNetCoreHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007));
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved, // This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack. // this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetHttpRequest()?.HttpContext.Response ?? var response = context.Transaction.GetHttpRequest()?.HttpContext.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0114));
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007)); if (string.IsNullOrEmpty(context.Transaction.Response.Error))
{
return default;
}
// When client authentication is made using basic authentication, the authorization server MUST return var scheme = (context.EndpointType, context.Transaction.Response.Error) switch
// a 401 response with a valid WWW-Authenticate header containing the HTTP Basic authentication scheme.
// A similar error MAY be returned even when basic authentication is not used and MUST also be returned
// when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme.
// To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors
// and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials
// were specified in the request form instead of the HTTP headers, as allowed by the specification.
var scheme = context.Transaction.Response.Error switch
{ {
Errors.InvalidClient => Schemes.Basic, // Unlike other server endpoints, errors returned by the userinfo endpoint follow the same
// logic as errors returned by API endpoints implementing bearer token authentication and
// MUST be returned as part of the standard WWW-Authenticate header. For more information,
// see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError.
(OpenIddictServerEndpointType.Userinfo, _) => Schemes.Bearer,
Errors.InvalidToken or // When client authentication is made using basic authentication, the authorization server
Errors.MissingToken or // MUST return a 401 response with a valid WWW-Authenticate header containing the HTTP Basic
Errors.InsufficientAccess or // authentication scheme. A similar error MAY be returned even when using client_secret_post.
Errors.InsufficientScope => Schemes.Bearer, // To simplify the logic, a 401 response with the Basic scheme is returned for invalid_client
// errors, even if credentials were specified in the form, as allowed by the specification.
(_, Errors.InvalidClient) => Schemes.Basic,
// For all other errors, don't return a WWW-Authenticate header and return server errors
// as formatted JSON responses, as required by the OAuth 2.0 base specification.
_ => null _ => null
}; };
@ -1007,19 +1036,19 @@ public static partial class OpenIddictServerAspNetCoreHandlers
var parameters = new Dictionary<string, string>(StringComparer.Ordinal); var parameters = new Dictionary<string, string>(StringComparer.Ordinal);
// If a realm was configured in the options, attach it to the parameters. // If a realm was configured in the options, attach it to the parameters.
if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) if (_options.CurrentValue.Realm is string { Length: > 0 } realm)
{ {
parameters[Parameters.Realm] = _options.CurrentValue.Realm; parameters[Parameters.Realm] = realm;
} }
foreach (var parameter in context.Transaction.Response.GetParameters()) foreach (var parameter in context.Transaction.Response.GetParameters())
{ {
// Note: the error details are only included if the error was not caused by a missing token, as recommended // Note: the error details are only included if the error was not caused by a missing token, as recommended
// by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1.
if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && if (context.Transaction.Response.Error is Errors.MissingToken &&
(string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || parameter.Key is Parameters.Error or
string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || Parameters.ErrorDescription or
string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) Parameters.ErrorUri)
{ {
continue; continue;
} }

1
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Authentication.cs

@ -44,6 +44,7 @@ public static partial class OpenIddictServerOwinHandlers
*/ */
RemoveCachedRequest.Descriptor, RemoveCachedRequest.Descriptor,
AttachHttpResponseCode<ApplyAuthorizationResponseContext>.Descriptor, AttachHttpResponseCode<ApplyAuthorizationResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyAuthorizationResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyAuthorizationResponseContext>.Descriptor, AttachCacheControlHeader<ApplyAuthorizationResponseContext>.Descriptor,
ProcessFormPostResponse.Descriptor, ProcessFormPostResponse.Descriptor,
ProcessQueryResponse.Descriptor, ProcessQueryResponse.Descriptor,

2
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Device.cs

@ -25,6 +25,7 @@ public static partial class OpenIddictServerOwinHandlers
* Device response processing: * Device response processing:
*/ */
AttachHttpResponseCode<ApplyDeviceResponseContext>.Descriptor, AttachHttpResponseCode<ApplyDeviceResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyDeviceResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyDeviceResponseContext>.Descriptor, AttachCacheControlHeader<ApplyDeviceResponseContext>.Descriptor,
AttachWwwAuthenticateHeader<ApplyDeviceResponseContext>.Descriptor, AttachWwwAuthenticateHeader<ApplyDeviceResponseContext>.Descriptor,
ProcessJsonResponse<ApplyDeviceResponseContext>.Descriptor, ProcessJsonResponse<ApplyDeviceResponseContext>.Descriptor,
@ -43,6 +44,7 @@ public static partial class OpenIddictServerOwinHandlers
* Verification response processing: * Verification response processing:
*/ */
AttachHttpResponseCode<ApplyVerificationResponseContext>.Descriptor, AttachHttpResponseCode<ApplyVerificationResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyVerificationResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyVerificationResponseContext>.Descriptor, AttachCacheControlHeader<ApplyVerificationResponseContext>.Descriptor,
ProcessHostRedirectionResponse.Descriptor, ProcessHostRedirectionResponse.Descriptor,
ProcessPassthroughErrorResponse<ApplyVerificationResponseContext, RequireVerificationEndpointPassthroughEnabled>.Descriptor, ProcessPassthroughErrorResponse<ApplyVerificationResponseContext, RequireVerificationEndpointPassthroughEnabled>.Descriptor,

2
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Discovery.cs

@ -22,6 +22,7 @@ public static partial class OpenIddictServerOwinHandlers
* Configuration response processing: * Configuration response processing:
*/ */
AttachHttpResponseCode<ApplyConfigurationResponseContext>.Descriptor, AttachHttpResponseCode<ApplyConfigurationResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyConfigurationResponseContext>.Descriptor,
AttachWwwAuthenticateHeader<ApplyConfigurationResponseContext>.Descriptor, AttachWwwAuthenticateHeader<ApplyConfigurationResponseContext>.Descriptor,
ProcessJsonResponse<ApplyConfigurationResponseContext>.Descriptor, ProcessJsonResponse<ApplyConfigurationResponseContext>.Descriptor,
@ -34,6 +35,7 @@ public static partial class OpenIddictServerOwinHandlers
* Cryptography response processing: * Cryptography response processing:
*/ */
AttachHttpResponseCode<ApplyCryptographyResponseContext>.Descriptor, AttachHttpResponseCode<ApplyCryptographyResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyCryptographyResponseContext>.Descriptor,
AttachWwwAuthenticateHeader<ApplyCryptographyResponseContext>.Descriptor, AttachWwwAuthenticateHeader<ApplyCryptographyResponseContext>.Descriptor,
ProcessJsonResponse<ApplyCryptographyResponseContext>.Descriptor); ProcessJsonResponse<ApplyCryptographyResponseContext>.Descriptor);
} }

1
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Exchange.cs

@ -28,6 +28,7 @@ public static partial class OpenIddictServerOwinHandlers
* Token response processing: * Token response processing:
*/ */
AttachHttpResponseCode<ApplyTokenResponseContext>.Descriptor, AttachHttpResponseCode<ApplyTokenResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyTokenResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyTokenResponseContext>.Descriptor, AttachCacheControlHeader<ApplyTokenResponseContext>.Descriptor,
AttachWwwAuthenticateHeader<ApplyTokenResponseContext>.Descriptor, AttachWwwAuthenticateHeader<ApplyTokenResponseContext>.Descriptor,
ProcessJsonResponse<ApplyTokenResponseContext>.Descriptor); ProcessJsonResponse<ApplyTokenResponseContext>.Descriptor);

1
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs

@ -23,6 +23,7 @@ public static partial class OpenIddictServerOwinHandlers
* Introspection response processing: * Introspection response processing:
*/ */
AttachHttpResponseCode<ApplyIntrospectionResponseContext>.Descriptor, AttachHttpResponseCode<ApplyIntrospectionResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyIntrospectionResponseContext>.Descriptor,
AttachWwwAuthenticateHeader<ApplyIntrospectionResponseContext>.Descriptor, AttachWwwAuthenticateHeader<ApplyIntrospectionResponseContext>.Descriptor,
ProcessJsonResponse<ApplyIntrospectionResponseContext>.Descriptor); ProcessJsonResponse<ApplyIntrospectionResponseContext>.Descriptor);
} }

1
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Revocation.cs

@ -23,6 +23,7 @@ public static partial class OpenIddictServerOwinHandlers
* Revocation response processing: * Revocation response processing:
*/ */
AttachHttpResponseCode<ApplyRevocationResponseContext>.Descriptor, AttachHttpResponseCode<ApplyRevocationResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyRevocationResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyRevocationResponseContext>.Descriptor, AttachCacheControlHeader<ApplyRevocationResponseContext>.Descriptor,
AttachWwwAuthenticateHeader<ApplyRevocationResponseContext>.Descriptor, AttachWwwAuthenticateHeader<ApplyRevocationResponseContext>.Descriptor,
ProcessJsonResponse<ApplyRevocationResponseContext>.Descriptor); ProcessJsonResponse<ApplyRevocationResponseContext>.Descriptor);

1
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Session.cs

@ -42,6 +42,7 @@ public static partial class OpenIddictServerOwinHandlers
*/ */
RemoveCachedRequest.Descriptor, RemoveCachedRequest.Descriptor,
AttachHttpResponseCode<ApplyLogoutResponseContext>.Descriptor, AttachHttpResponseCode<ApplyLogoutResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyLogoutResponseContext>.Descriptor,
AttachCacheControlHeader<ApplyLogoutResponseContext>.Descriptor, AttachCacheControlHeader<ApplyLogoutResponseContext>.Descriptor,
ProcessHostRedirectionResponse.Descriptor, ProcessHostRedirectionResponse.Descriptor,
ProcessPassthroughErrorResponse<ApplyLogoutResponseContext, RequireLogoutEndpointPassthroughEnabled>.Descriptor, ProcessPassthroughErrorResponse<ApplyLogoutResponseContext, RequireLogoutEndpointPassthroughEnabled>.Descriptor,

1
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Userinfo.cs

@ -28,6 +28,7 @@ public static partial class OpenIddictServerOwinHandlers
* Userinfo response processing: * Userinfo response processing:
*/ */
AttachHttpResponseCode<ApplyUserinfoResponseContext>.Descriptor, AttachHttpResponseCode<ApplyUserinfoResponseContext>.Descriptor,
AttachOwinResponseChallenge<ApplyUserinfoResponseContext>.Descriptor,
AttachWwwAuthenticateHeader<ApplyUserinfoResponseContext>.Descriptor, AttachWwwAuthenticateHeader<ApplyUserinfoResponseContext>.Descriptor,
ProcessChallengeErrorResponse<ApplyUserinfoResponseContext>.Descriptor, ProcessChallengeErrorResponse<ApplyUserinfoResponseContext>.Descriptor,
ProcessJsonResponse<ApplyUserinfoResponseContext>.Descriptor); ProcessJsonResponse<ApplyUserinfoResponseContext>.Descriptor);

179
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs

@ -32,7 +32,7 @@ public static partial class OpenIddictServerOwinHandlers
/* /*
* Challenge processing: * Challenge processing:
*/ */
ResolveHostChallengeParameters.Descriptor) AttachHostChallengeError.Descriptor)
.AddRange(Authentication.DefaultHandlers) .AddRange(Authentication.DefaultHandlers)
.AddRange(Device.DefaultHandlers) .AddRange(Device.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers) .AddRange(Discovery.DefaultHandlers)
@ -264,11 +264,10 @@ public static partial class OpenIddictServerOwinHandlers
} }
/// <summary> /// <summary>
/// Contains the logic responsible for resolving the additional sign-in parameters stored in the OWIN /// Contains the logic responsible for attaching the error details using the OWIN authentication properties.
/// authentication properties specified by the application that triggered the sign-in operation.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary> /// </summary>
public class ResolveHostChallengeParameters : IOpenIddictServerHandler<ProcessChallengeContext> public class AttachHostChallengeError : IOpenIddictServerHandler<ProcessChallengeContext>
{ {
/// <summary> /// <summary>
/// Gets the default descriptor definition assigned to this handler. /// Gets the default descriptor definition assigned to this handler.
@ -276,8 +275,8 @@ public static partial class OpenIddictServerOwinHandlers
public static OpenIddictServerHandlerDescriptor Descriptor { get; } public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ResolveHostChallengeParameters>() .UseSingletonHandler<AttachHostChallengeError>()
.SetOrder(AttachChallengeParameters.Descriptor.Order - 500) .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -290,36 +289,18 @@ public static partial class OpenIddictServerOwinHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not null)
{ {
return default; context.Response.Error = GetProperty(properties, Properties.Error);
} context.Response.ErrorDescription = GetProperty(properties, Properties.ErrorDescription);
context.Response.ErrorUri = GetProperty(properties, Properties.ErrorUri);
if (properties.Dictionary.TryGetValue(Properties.Error, out string? error) && context.Response.Scope = GetProperty(properties, Properties.Scope);
!string.IsNullOrEmpty(error))
{
context.Parameters[Parameters.Error] = error;
}
if (properties.Dictionary.TryGetValue(Properties.ErrorDescription, out string? description) &&
!string.IsNullOrEmpty(description))
{
context.Parameters[Parameters.ErrorDescription] = description;
}
if (properties.Dictionary.TryGetValue(Properties.ErrorUri, out string? uri) &&
!string.IsNullOrEmpty(uri))
{
context.Parameters[Parameters.ErrorUri] = uri;
}
if (properties.Dictionary.TryGetValue(Properties.Scope, out string? scope) &&
!string.IsNullOrEmpty(scope))
{
context.Parameters[Parameters.Scope] = scope;
} }
return default; return default;
static string? GetProperty(AuthenticationProperties properties, string name)
=> properties.Dictionary.TryGetValue(name, out string? value) ? value : null;
} }
} }
@ -740,23 +721,83 @@ public static partial class OpenIddictServerOwinHandlers
var response = context.Transaction.GetOwinRequest()?.Context.Response ?? var response = context.Transaction.GetOwinRequest()?.Context.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
// When client authentication is made using basic authentication, the authorization server MUST return response.StatusCode = (context.EndpointType, context.Transaction.Response.Error) switch
// a 401 response with a valid WWW-Authenticate header containing the Basic scheme and a non-empty realm.
// A similar error MAY be returned even when basic authentication is not used and MUST also be returned
// when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme.
// To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors
// and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials
// were specified in the request form instead of the HTTP headers, as allowed by the specification.
response.StatusCode = context.Transaction.Response.Error switch
{ {
null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects). // Note: the default code may be replaced by another handler (e.g when doing redirects).
(_, null or { Length: 0 }) => 200,
// Unlike other server endpoints, errors returned by the userinfo endpoint follow the same logic as
// errors returned by API endpoints implementing bearer token authentication and MUST be returned
// as part of the standard WWW-Authenticate header. For more information, see
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError.
(OpenIddictServerEndpointType.Userinfo, Errors.InvalidToken or Errors.MissingToken) => 401,
(OpenIddictServerEndpointType.Userinfo, Errors.InsufficientAccess or Errors.InsufficientScope) => 403,
// When client authentication is made using basic authentication, the authorization server
// MUST return a 401 response with a valid WWW-Authenticate header containing the HTTP Basic
// authentication scheme. A similar error MAY be returned even when using client_secret_post.
// To simplify the logic, a 401 response with the Basic scheme is returned for invalid_client
// errors, even if credentials were specified in the form, as allowed by the specification.
(not OpenIddictServerEndpointType.Userinfo, Errors.InvalidClient) => 401,
(_, Errors.ServerError) => 500,
// Note: unless specified otherwise, errors are expected to result in 400 responses.
// See https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 for more information.
_ => 400
};
Errors.InvalidClient or Errors.InvalidToken or Errors.MissingToken => 401, return default;
}
}
Errors.InsufficientAccess or Errors.InsufficientScope => 403, /// <summary>
/// Contains the logic responsible for attaching an OWIN response chalenge to the context, if necessary.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class AttachOwinResponseChallenge<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<AttachOwinResponseChallenge<TContext>>()
.SetOrder(AttachHttpResponseCode<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
_ => 400 /// <inheritdoc/>
}; public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetOwinRequest()?.Context.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
// OWIN authentication middleware configured to use active authentication (which is the default mode)
// are known to aggressively intercept 401 responses even if the request is already considered fully
// handled. In practice, this behavior is often seen with the cookies authentication middleware,
// that will rewrite the 401 responses returned by OpenIddict and try to redirect the user agent
// to the login page configured in the options. To prevent this undesirable behavior, a fake
// response challenge pointing to a non-existent middleware is manually added to the OWIN context
// to prevent the active authentication middleware from rewriting OpenIddict's 401 HTTP responses.
//
// Note: while 403 responses are generally not intercepted by the built-in OWIN authentication
// middleware, they are treated the same way as 401 responses to account for custom middleware
// that may potentially use the same interception logic for both 401 and 403 HTTP responses.
if (response.StatusCode is 401 or 403 &&
response.Context.Authentication.AuthenticationResponseChallenge is null)
{
response.Context.Authentication.AuthenticationResponseChallenge =
new AuthenticationResponseChallenge(new[] { Guid.NewGuid().ToString() }, null);
}
return default; return default;
} }
@ -775,7 +816,7 @@ public static partial class OpenIddictServerOwinHandlers
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>() = OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<AttachCacheControlHeader<TContext>>() .UseSingletonHandler<AttachCacheControlHeader<TContext>>()
.SetOrder(AttachHttpResponseCode<TContext>.Descriptor.Order + 1_000) .SetOrder(AttachOwinResponseChallenge<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn) .SetType(OpenIddictServerHandlerType.BuiltIn)
.Build(); .Build();
@ -838,22 +879,28 @@ public static partial class OpenIddictServerOwinHandlers
var response = context.Transaction.GetOwinRequest()?.Context.Response ?? var response = context.Transaction.GetOwinRequest()?.Context.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0120)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
// When client authentication is made using basic authentication, the authorization server MUST return if (string.IsNullOrEmpty(context.Transaction.Response.Error))
// a 401 response with a valid WWW-Authenticate header containing the HTTP Basic authentication scheme.
// A similar error MAY be returned even when basic authentication is not used and MUST also be returned
// when an invalid token is received by the userinfo endpoint using the Bearer authentication scheme.
// To simplify the logic, a 401 response with the Bearer scheme is returned for invalid_token errors
// and a 401 response with the Basic scheme is returned for invalid_client, even if the credentials
// were specified in the request form instead of the HTTP headers, as allowed by the specification.
var scheme = context.Transaction.Response.Error switch
{ {
Errors.InvalidClient => Schemes.Basic, return default;
}
Errors.InvalidToken or
Errors.MissingToken or
Errors.InsufficientAccess or
Errors.InsufficientScope => Schemes.Bearer,
var scheme = (context.EndpointType, context.Transaction.Response.Error) switch
{
// Unlike other server endpoints, errors returned by the userinfo endpoint follow the same
// logic as errors returned by API endpoints implementing bearer token authentication and
// MUST be returned as part of the standard WWW-Authenticate header. For more information,
// see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError.
(OpenIddictServerEndpointType.Userinfo, _) => Schemes.Bearer,
// When client authentication is made using basic authentication, the authorization server
// MUST return a 401 response with a valid WWW-Authenticate header containing the HTTP Basic
// authentication scheme. A similar error MAY be returned even when using client_secret_post.
// To simplify the logic, a 401 response with the Basic scheme is returned for invalid_client
// errors, even if credentials were specified in the form, as allowed by the specification.
(_, Errors.InvalidClient) => Schemes.Basic,
// For all other errors, don't return a WWW-Authenticate header and return server errors
// as formatted JSON responses, as required by the OAuth 2.0 base specification.
_ => null _ => null
}; };
@ -865,19 +912,19 @@ public static partial class OpenIddictServerOwinHandlers
var parameters = new Dictionary<string, string>(StringComparer.Ordinal); var parameters = new Dictionary<string, string>(StringComparer.Ordinal);
// If a realm was configured in the options, attach it to the parameters. // If a realm was configured in the options, attach it to the parameters.
if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) if (_options.CurrentValue.Realm is string { Length: > 0 } realm)
{ {
parameters[Parameters.Realm] = _options.CurrentValue.Realm; parameters[Parameters.Realm] = realm;
} }
foreach (var parameter in context.Transaction.Response.GetParameters()) foreach (var parameter in context.Transaction.Response.GetParameters())
{ {
// Note: the error details are only included if the error was not caused by a missing token, as recommended // Note: the error details are only included if the error was not caused by a missing token, as recommended
// by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1.
if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && if (context.Transaction.Response.Error is Errors.MissingToken &&
(string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || parameter.Key is Parameters.Error or
string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || Parameters.ErrorDescription or
string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) Parameters.ErrorUri)
{ {
continue; continue;
} }

51
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -907,47 +907,38 @@ public static partial class OpenIddictServerHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (!context.Parameters.ContainsKey(Parameters.Error)) context.Response.Error ??= context.EndpointType switch
{ {
context.Parameters[Parameters.Error] = context.EndpointType switch OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification
{ => Errors.AccessDenied,
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification
=> Errors.AccessDenied,
OpenIddictServerEndpointType.Token => Errors.InvalidGrant, OpenIddictServerEndpointType.Token => Errors.InvalidGrant,
OpenIddictServerEndpointType.Userinfo => Errors.InsufficientAccess, OpenIddictServerEndpointType.Userinfo => Errors.InsufficientAccess,
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006))
}; };
}
if (!context.Parameters.ContainsKey(Parameters.ErrorDescription)) context.Response.ErrorDescription ??= context.EndpointType switch
{ {
context.Parameters[Parameters.ErrorDescription] = context.EndpointType switch OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification
{ => SR.GetResourceString(SR.ID2015),
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification
=> SR.GetResourceString(SR.ID2015),
OpenIddictServerEndpointType.Token => SR.GetResourceString(SR.ID2024), OpenIddictServerEndpointType.Token => SR.GetResourceString(SR.ID2024),
OpenIddictServerEndpointType.Userinfo => SR.GetResourceString(SR.ID2025), OpenIddictServerEndpointType.Userinfo => SR.GetResourceString(SR.ID2025),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006))
}; };
}
if (!context.Parameters.ContainsKey(Parameters.ErrorUri)) context.Response.ErrorUri ??= context.EndpointType switch
{ {
context.Parameters[Parameters.ErrorUri] = context.EndpointType switch OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification
{ => SR.FormatID8000(SR.ID2015),
OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification
=> SR.FormatID8000(SR.ID2015),
OpenIddictServerEndpointType.Token => SR.FormatID8000(SR.ID2024), OpenIddictServerEndpointType.Token => SR.FormatID8000(SR.ID2024),
OpenIddictServerEndpointType.Userinfo => SR.FormatID8000(SR.ID2025), OpenIddictServerEndpointType.Userinfo => SR.FormatID8000(SR.ID2025),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006))
}; };
}
return default; return default;
} }

155
src/OpenIddict.Validation.AspNetCore/OpenIddictValidationAspNetCoreHandlers.cs

@ -43,6 +43,7 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
* Challenge processing: * Challenge processing:
*/ */
AttachHostChallengeError.Descriptor, AttachHostChallengeError.Descriptor,
ResolveHostChallengeParameters.Descriptor,
/* /*
* Response processing: * Response processing:
@ -51,13 +52,16 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
AttachCacheControlHeader<ProcessChallengeContext>.Descriptor, AttachCacheControlHeader<ProcessChallengeContext>.Descriptor,
AttachWwwAuthenticateHeader<ProcessChallengeContext>.Descriptor, AttachWwwAuthenticateHeader<ProcessChallengeContext>.Descriptor,
ProcessChallengeErrorResponse<ProcessChallengeContext>.Descriptor, ProcessChallengeErrorResponse<ProcessChallengeContext>.Descriptor,
ProcessJsonResponse<ProcessChallengeContext>.Descriptor,
AttachHttpResponseCode<ProcessErrorContext>.Descriptor, AttachHttpResponseCode<ProcessErrorContext>.Descriptor,
AttachCacheControlHeader<ProcessErrorContext>.Descriptor, AttachCacheControlHeader<ProcessErrorContext>.Descriptor,
AttachWwwAuthenticateHeader<ProcessErrorContext>.Descriptor, AttachWwwAuthenticateHeader<ProcessErrorContext>.Descriptor,
ProcessChallengeErrorResponse<ProcessChallengeContext>.Descriptor, ProcessChallengeErrorResponse<ProcessErrorContext>.Descriptor,
ProcessJsonResponse<ProcessErrorContext>.Descriptor);
/*
* Error processing:
*/
AttachErrorParameters.Descriptor);
/// <summary> /// <summary>
/// Contains the logic responsible for infering the default issuer from the HTTP request host and validating it. /// Contains the logic responsible for infering the default issuer from the HTTP request host and validating it.
@ -292,7 +296,7 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>() .AddFilter<RequireHttpRequest>()
.UseSingletonHandler<AttachHostChallengeError>() .UseSingletonHandler<AttachHostChallengeError>()
.SetOrder(int.MinValue + 50_000) .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build(); .Build();
@ -305,33 +309,48 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not null)
{ {
return default; context.Response.Error = properties.GetString(Properties.Error);
context.Response.ErrorDescription = properties.GetString(Properties.ErrorDescription);
context.Response.ErrorUri = properties.GetString(Properties.ErrorUri);
context.Response.Scope = properties.GetString(Properties.Scope);
} }
if (properties.Items.TryGetValue(Properties.Error, out string? error) && return default;
!string.IsNullOrEmpty(error)) }
{ }
context.Parameters[Parameters.Error] = error;
}
if (properties.Items.TryGetValue(Properties.ErrorDescription, out string? description) && /// <summary>
!string.IsNullOrEmpty(description)) /// Contains the logic responsible for resolving the additional sign-in parameters stored in the ASP.NET
{ /// Core authentication properties specified by the application that triggered the sign-in operation.
context.Parameters[Parameters.ErrorDescription] = description; /// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
} /// </summary>
public class ResolveHostChallengeParameters : IOpenIddictValidationHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ResolveHostChallengeParameters>()
.SetOrder(AttachChallengeParameters.Descriptor.Order - 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
if (properties.Items.TryGetValue(Properties.ErrorUri, out string? uri) && /// <inheritdoc/>
!string.IsNullOrEmpty(uri)) public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{ {
context.Parameters[Parameters.ErrorUri] = uri; throw new ArgumentNullException(nameof(context));
} }
if (properties.Items.TryGetValue(Properties.Scope, out string? scope) && var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
!string.IsNullOrEmpty(scope)) if (properties is null)
{ {
context.Parameters[Parameters.Scope] = scope; return default;
} }
foreach (var parameter in properties.Parameters) foreach (var parameter in properties.Parameters)
@ -391,12 +410,15 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
response.StatusCode = context.Transaction.Response.Error switch response.StatusCode = context.Transaction.Response.Error switch
{ {
null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects).
null or { Length: 0 } => 200,
Errors.InvalidToken or Errors.MissingToken => 401, Errors.InvalidToken or Errors.MissingToken => 401,
Errors.InsufficientAccess or Errors.InsufficientScope => 403, Errors.InsufficientAccess or Errors.InsufficientScope => 403,
Errors.ServerError => 500,
_ => 400 _ => 400
}; };
@ -480,37 +502,34 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
var response = context.Transaction.GetHttpRequest()?.HttpContext.Response ?? var response = context.Transaction.GetHttpRequest()?.HttpContext.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0114)); throw new InvalidOperationException(SR.GetResourceString(SR.ID0114));
var scheme = context.Transaction.Response.Error switch if (string.IsNullOrEmpty(context.Transaction.Response.Error))
{
Errors.InvalidToken or
Errors.MissingToken or
Errors.InsufficientAccess or
Errors.InsufficientScope => Schemes.Bearer,
_ => null
};
if (string.IsNullOrEmpty(scheme))
{ {
return default; return default;
} }
// Note: unlike the server stack, the validation stack doesn't expose any endpoint
// and thus never returns responses containing a formatted body (e.g a JSON response).
//
// As such, all errors - even errors indicating an invalid request - are returned
// as part of the standard WWW-Authenticate header, as defined by the specification.
// See https://datatracker.ietf.org/doc/html/rfc6750#section-3 for more information.
var parameters = new Dictionary<string, string>(StringComparer.Ordinal); var parameters = new Dictionary<string, string>(StringComparer.Ordinal);
// If a realm was configured in the options, attach it to the parameters. // If a realm was configured in the options, attach it to the parameters.
if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) if (_options.CurrentValue.Realm is string { Length: > 0 } realm)
{ {
parameters[Parameters.Realm] = _options.CurrentValue.Realm; parameters[Parameters.Realm] = realm;
} }
foreach (var parameter in context.Transaction.Response.GetParameters()) foreach (var parameter in context.Transaction.Response.GetParameters())
{ {
// Note: the error details are only included if the error was not caused by a missing token, as recommended // Note: the error details are only included if the error was not caused by a missing token, as recommended
// by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1.
if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && if (context.Transaction.Response.Error is Errors.MissingToken &&
(string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || parameter.Key is Parameters.Error or
string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || Parameters.ErrorDescription or
string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) Parameters.ErrorUri)
{ {
continue; continue;
} }
@ -525,7 +544,7 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
parameters[parameter.Key] = value; parameters[parameter.Key] = value;
} }
var builder = new StringBuilder(scheme); var builder = new StringBuilder(Schemes.Bearer);
foreach (var parameter in parameters) foreach (var parameter in parameters)
{ {
@ -592,58 +611,4 @@ public static partial class OpenIddictValidationAspNetCoreHandlers
return default; return default;
} }
} }
/// <summary>
/// Contains the logic responsible for processing OpenID Connect responses that must be returned as JSON.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public class ProcessJsonResponse<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpRequest>()
.UseSingletonHandler<ProcessJsonResponse<TContext>>()
.SetOrder(ProcessChallengeErrorResponse<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007));
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetHttpRequest()?.HttpContext.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0114));
context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response);
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = true
});
context.Transaction.Response.WriteTo(writer);
writer.Flush();
response.ContentLength = stream.Length;
response.ContentType = "application/json;charset=UTF-8";
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
await stream.CopyToAsync(response.Body, 4096, response.HttpContext.RequestAborted);
context.HandleRequest();
}
}
} }

192
src/OpenIddict.Validation.Owin/OpenIddictValidationOwinHandlers.cs

@ -43,16 +43,21 @@ public static partial class OpenIddictValidationOwinHandlers
* Response processing: * Response processing:
*/ */
AttachHttpResponseCode<ProcessChallengeContext>.Descriptor, AttachHttpResponseCode<ProcessChallengeContext>.Descriptor,
AttachOwinResponseChallenge<ProcessChallengeContext>.Descriptor,
AttachCacheControlHeader<ProcessChallengeContext>.Descriptor, AttachCacheControlHeader<ProcessChallengeContext>.Descriptor,
AttachWwwAuthenticateHeader<ProcessChallengeContext>.Descriptor, AttachWwwAuthenticateHeader<ProcessChallengeContext>.Descriptor,
ProcessChallengeErrorResponse<ProcessChallengeContext>.Descriptor, ProcessChallengeErrorResponse<ProcessChallengeContext>.Descriptor,
ProcessJsonResponse<ProcessChallengeContext>.Descriptor,
AttachHttpResponseCode<ProcessErrorContext>.Descriptor, AttachHttpResponseCode<ProcessErrorContext>.Descriptor,
AttachOwinResponseChallenge<ProcessErrorContext>.Descriptor,
AttachCacheControlHeader<ProcessErrorContext>.Descriptor, AttachCacheControlHeader<ProcessErrorContext>.Descriptor,
AttachWwwAuthenticateHeader<ProcessErrorContext>.Descriptor, AttachWwwAuthenticateHeader<ProcessErrorContext>.Descriptor,
ProcessChallengeErrorResponse<ProcessErrorContext>.Descriptor, ProcessChallengeErrorResponse<ProcessErrorContext>.Descriptor,
ProcessJsonResponse<ProcessErrorContext>.Descriptor);
/*
* Error processing:
*/
AttachErrorParameters.Descriptor);
/// <summary> /// <summary>
/// Contains the logic responsible for infering the default issuer from the HTTP request host and validating it. /// Contains the logic responsible for infering the default issuer from the HTTP request host and validating it.
@ -289,7 +294,7 @@ public static partial class OpenIddictValidationOwinHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<AttachHostChallengeError>() .UseSingletonHandler<AttachHostChallengeError>()
.SetOrder(int.MinValue + 50_000) .SetOrder(AttachDefaultChallengeError.Descriptor.Order - 500)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build(); .Build();
@ -302,36 +307,18 @@ public static partial class OpenIddictValidationOwinHandlers
} }
var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!); var properties = context.Transaction.GetProperty<AuthenticationProperties>(typeof(AuthenticationProperties).FullName!);
if (properties is null) if (properties is not null)
{
return default;
}
if (properties.Dictionary.TryGetValue(Properties.Error, out string? error) &&
!string.IsNullOrEmpty(error))
{
context.Parameters[Parameters.Error] = error;
}
if (properties.Dictionary.TryGetValue(Properties.ErrorDescription, out string? description) &&
!string.IsNullOrEmpty(description))
{
context.Parameters[Parameters.ErrorDescription] = description;
}
if (properties.Dictionary.TryGetValue(Properties.ErrorUri, out string? uri) &&
!string.IsNullOrEmpty(uri))
{ {
context.Parameters[Parameters.ErrorUri] = uri; context.Response.Error = GetProperty(properties, Properties.Error);
} context.Response.ErrorDescription = GetProperty(properties, Properties.ErrorDescription);
context.Response.ErrorUri = GetProperty(properties, Properties.ErrorUri);
if (properties.Dictionary.TryGetValue(Properties.Scope, out string? scope) && context.Response.Scope = GetProperty(properties, Properties.Scope);
!string.IsNullOrEmpty(scope))
{
context.Parameters[Parameters.Scope] = scope;
} }
return default; return default;
static string? GetProperty(AuthenticationProperties properties, string name)
=> properties.Dictionary.TryGetValue(name, out string? value) ? value : null;
} }
} }
@ -369,12 +356,15 @@ public static partial class OpenIddictValidationOwinHandlers
response.StatusCode = context.Transaction.Response.Error switch response.StatusCode = context.Transaction.Response.Error switch
{ {
null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects).
null or { Length: 0 } => 200,
Errors.InvalidToken or Errors.MissingToken => 401, Errors.InvalidToken or Errors.MissingToken => 401,
Errors.InsufficientAccess or Errors.InsufficientScope => 403, Errors.InsufficientAccess or Errors.InsufficientScope => 403,
Errors.ServerError => 500,
_ => 400 _ => 400
}; };
@ -382,6 +372,58 @@ public static partial class OpenIddictValidationOwinHandlers
} }
} }
/// <summary>
/// Contains the logic responsible for attaching an OWIN response chalenge to the context, if necessary.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class AttachOwinResponseChallenge<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<AttachOwinResponseChallenge<TContext>>()
.SetOrder(AttachHttpResponseCode<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetOwinRequest()?.Context.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
// OWIN authentication middleware configured to use active authentication (which is the default mode)
// are known to aggressively intercept 401 responses even if the request is already considered fully
// handled. In practice, this behavior is often seen with the cookies authentication middleware,
// that will rewrite the 401 responses returned by OpenIddict and try to redirect the user agent
// to the login page configured in the options. To prevent this undesirable behavior, a fake
// response challenge pointing to a non-existent middleware is manually added to the OWIN context
// to prevent the active authentication middleware from rewriting OpenIddict's 401 HTTP responses.
//
// Note: while 403 responses are generally not intercepted by the built-in OWIN authentication
// middleware, they are treated the same way as 401 responses to account for custom middleware
// that may potentially use the same interception logic for both 401 and 403 HTTP responses.
if (response.StatusCode is 401 or 403 &&
response.Context.Authentication.AuthenticationResponseChallenge is null)
{
response.Context.Authentication.AuthenticationResponseChallenge =
new AuthenticationResponseChallenge(new[] { Guid.NewGuid().ToString() }, null);
}
return default;
}
}
/// <summary> /// <summary>
/// Contains the logic responsible for attaching the appropriate HTTP response cache headers. /// Contains the logic responsible for attaching the appropriate HTTP response cache headers.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN. /// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
@ -395,7 +437,7 @@ public static partial class OpenIddictValidationOwinHandlers
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireOwinRequest>() .AddFilter<RequireOwinRequest>()
.UseSingletonHandler<AttachCacheControlHeader<TContext>>() .UseSingletonHandler<AttachCacheControlHeader<TContext>>()
.SetOrder(AttachHttpResponseCode<TContext>.Descriptor.Order + 1_000) .SetOrder(AttachOwinResponseChallenge<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build(); .Build();
@ -463,37 +505,29 @@ public static partial class OpenIddictValidationOwinHandlers
return default; return default;
} }
var scheme = context.Transaction.Response.Error switch // Note: unlike the server stack, the validation stack doesn't expose any endpoint
{ // and thus never returns responses containing a formatted body (e.g a JSON response).
Errors.InvalidToken or //
Errors.MissingToken or // As such, all errors - even errors indicating an invalid request - are returned
Errors.InsufficientAccess or // as part of the standard WWW-Authenticate header, as defined by the specification.
Errors.InsufficientScope => Schemes.Bearer, // See https://datatracker.ietf.org/doc/html/rfc6750#section-3 for more information.
_ => null
};
if (string.IsNullOrEmpty(scheme))
{
return default;
}
var parameters = new Dictionary<string, string>(StringComparer.Ordinal); var parameters = new Dictionary<string, string>(StringComparer.Ordinal);
// If a realm was configured in the options, attach it to the parameters. // If a realm was configured in the options, attach it to the parameters.
if (!string.IsNullOrEmpty(_options.CurrentValue.Realm)) if (_options.CurrentValue.Realm is string { Length: > 0 } realm)
{ {
parameters[Parameters.Realm] = _options.CurrentValue.Realm; parameters[Parameters.Realm] = realm;
} }
foreach (var parameter in context.Transaction.Response.GetParameters()) foreach (var parameter in context.Transaction.Response.GetParameters())
{ {
// Note: the error details are only included if the error was not caused by a missing token, as recommended // Note: the error details are only included if the error was not caused by a missing token, as recommended
// by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1. // by the OAuth 2.0 bearer specification: https://tools.ietf.org/html/rfc6750#section-3.1.
if (string.Equals(context.Transaction.Response.Error, Errors.MissingToken, StringComparison.Ordinal) && if (context.Transaction.Response.Error is Errors.MissingToken &&
(string.Equals(parameter.Key, Parameters.Error, StringComparison.Ordinal) || parameter.Key is Parameters.Error or
string.Equals(parameter.Key, Parameters.ErrorDescription, StringComparison.Ordinal) || Parameters.ErrorDescription or
string.Equals(parameter.Key, Parameters.ErrorUri, StringComparison.Ordinal))) Parameters.ErrorUri)
{ {
continue; continue;
} }
@ -508,7 +542,7 @@ public static partial class OpenIddictValidationOwinHandlers
parameters[parameter.Key] = value; parameters[parameter.Key] = value;
} }
var builder = new StringBuilder(scheme); var builder = new StringBuilder(Schemes.Bearer);
foreach (var parameter in parameters) foreach (var parameter in parameters)
{ {
@ -575,58 +609,4 @@ public static partial class OpenIddictValidationOwinHandlers
return default; return default;
} }
} }
/// <summary>
/// Contains the logic responsible for processing OpenID Connect responses that must be returned as JSON.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class ProcessJsonResponse<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseRequestContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<ProcessJsonResponse<TContext>>()
.SetOrder(ProcessChallengeErrorResponse<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007));
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var response = context.Transaction.GetOwinRequest()?.Context.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0120));
context.Logger.LogInformation(SR.GetResourceString(SR.ID6142), context.Transaction.Response);
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
Indented = true
});
context.Transaction.Response.WriteTo(writer);
writer.Flush();
response.ContentLength = stream.Length;
response.ContentType = "application/json;charset=UTF-8";
stream.Seek(offset: 0, loc: SeekOrigin.Begin);
await stream.CopyToAsync(response.Body, 4096, response.Context.Request.CallCancelled);
context.HandleRequest();
}
}
} }

60
src/OpenIddict.Validation/OpenIddictValidationHandlers.cs

@ -24,7 +24,8 @@ public static partial class OpenIddictValidationHandlers
/* /*
* Challenge processing: * Challenge processing:
*/ */
AttachDefaultChallengeError.Descriptor) AttachDefaultChallengeError.Descriptor,
AttachChallengeParameters.Descriptor)
.AddRange(Discovery.DefaultHandlers) .AddRange(Discovery.DefaultHandlers)
.AddRange(Introspection.DefaultHandlers) .AddRange(Introspection.DefaultHandlers)
@ -95,11 +96,7 @@ public static partial class OpenIddictValidationHandlers
{ {
// 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),
// As such, sending an access token is not mandatory: API endpoints that require
// authentication can set up an authorization policy to reject such requests later
// in the request processing pipeline (typically, via the authorization middleware).
OpenIddictValidationEndpointType.Unknown => (true, false, true),
_ => (false, false, false) _ => (false, false, false)
}; };
@ -123,7 +120,7 @@ public static partial class OpenIddictValidationHandlers
/// </summary> /// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() = OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.UseSingletonHandler<EvaluateValidatedTokens>() .UseSingletonHandler<ValidateRequiredTokens>()
.SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1_000) .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn) .SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build(); .Build();
@ -240,14 +237,6 @@ public static partial class OpenIddictValidationHandlers
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
// If an error was explicitly set by the application, don't override it.
if (!string.IsNullOrEmpty(context.Response.Error) ||
!string.IsNullOrEmpty(context.Response.ErrorDescription) ||
!string.IsNullOrEmpty(context.Response.ErrorUri))
{
return default;
}
// Try to retrieve the authentication context from the validation transaction and use // Try to retrieve the authentication context from the validation transaction and use
// the error details returned during the authentication processing, if available. // the error details returned during the authentication processing, if available.
// If no error is attached to the authentication context, this likely means that // If no error is attached to the authentication context, this likely means that
@ -258,18 +247,43 @@ public static partial class OpenIddictValidationHandlers
var notification = context.Transaction.GetProperty<ProcessAuthenticationContext>( var notification = context.Transaction.GetProperty<ProcessAuthenticationContext>(
typeof(ProcessAuthenticationContext).FullName!); typeof(ProcessAuthenticationContext).FullName!);
if (!string.IsNullOrEmpty(notification?.Error)) context.Response.Error ??= notification?.Error ?? Errors.InsufficientAccess;
context.Response.ErrorDescription ??= notification?.ErrorDescription ?? SR.GetResourceString(SR.ID2095);
context.Response.ErrorUri ??= notification?.ErrorUri ?? SR.FormatID8000(SR.ID2095);
return default;
}
}
/// <summary>
/// Contains the logic responsible for attaching the appropriate parameters to the challenge response.
/// </summary>
public class AttachChallengeParameters : IOpenIddictValidationHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictValidationHandlerDescriptor Descriptor { get; }
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.UseSingletonHandler<AttachChallengeParameters>()
.SetOrder(AttachDefaultChallengeError.Descriptor.Order + 1_000)
.SetType(OpenIddictValidationHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{ {
context.Response.Error = notification.Error; throw new ArgumentNullException(nameof(context));
context.Response.ErrorDescription = notification.ErrorDescription;
context.Response.ErrorUri = notification.ErrorUri;
} }
else if (context.Parameters.Count > 0)
{ {
context.Response.Error = Errors.InsufficientAccess; foreach (var parameter in context.Parameters)
context.Response.ErrorDescription = SR.GetResourceString(SR.ID2095); {
context.Response.ErrorUri = SR.FormatID8000(SR.ID2095); context.Response.SetParameter(parameter.Key, parameter.Value);
}
} }
return default; return default;

Loading…
Cancel
Save