Versatile OpenID Connect stack for ASP.NET Core and Microsoft.Owin (compatible with ASP.NET 4.6.1)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2597 lines
122 KiB

/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using OpenIddict.Extensions;
using static OpenIddict.Client.SystemIntegration.OpenIddictClientSystemIntegrationConstants;
#if !SUPPORTS_HOST_APPLICATION_LIFETIME
using IHostApplicationLifetime = Microsoft.Extensions.Hosting.IApplicationLifetime;
#endif
#if SUPPORTS_FOUNDATION
using Foundation;
#endif
#if SUPPORTS_WINDOWS_RUNTIME
using Windows.Security.Authentication.Web;
#endif
namespace OpenIddict.Client.SystemIntegration;
[EditorBrowsable(EditorBrowsableState.Never)]
public static partial class OpenIddictClientSystemIntegrationHandlers
{
public static ImmutableArray<OpenIddictClientHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create([
/*
* Top-level request processing:
*/
ResolveRequestUriFromHttpListenerRequest.Descriptor,
ResolveRequestUriFromProtocolActivation.Descriptor,
ResolveRequestUriFromASWebAuthenticationCallbackUrl.Descriptor,
ResolveRequestUriFromWebAuthenticationResult.Descriptor,
InferEndpointTypeFromDynamicAddress.Descriptor,
RejectUnknownHttpRequests.Descriptor,
/*
* Authentication processing:
*/
WaitMarshalledAuthentication.Descriptor,
RestoreClientRegistrationFromMarshalledContext.Descriptor,
RestoreStateTokenFromMarshalledAuthentication.Descriptor,
RestoreStateTokenPrincipalFromMarshalledAuthentication.Descriptor,
RestoreHostAuthenticationPropertiesFromMarshalledAuthentication.Descriptor,
RedirectProtocolActivation.Descriptor,
ResolveRequestForgeryProtection.Descriptor,
RestoreFrontchannelTokensFromMarshalledAuthentication.Descriptor,
RestoreFrontchannelIdentityTokenPrincipalFromMarshalledAuthentication.Descriptor,
RestoreFrontchannelAccessTokenPrincipalFromMarshalledAuthentication.Descriptor,
RestoreAuthorizationCodePrincipalFromMarshalledAuthentication.Descriptor,
RestoreTokenResponseFromMarshalledAuthentication.Descriptor,
RestoreBackchannelTokensFromMarshalledAuthentication.Descriptor,
RestoreBackchannelIdentityTokenPrincipalFromMarshalledAuthentication.Descriptor,
RestoreBackchannelAccessTokenPrincipalFromMarshalledAuthentication.Descriptor,
RestoreRefreshTokenPrincipalFromMarshalledAuthentication.Descriptor,
RestoreUserinfoDetailsFromMarshalledAuthentication.Descriptor,
RestoreMergedPrincipalFromMarshalledAuthentication.Descriptor,
CompleteAuthenticationOperation.Descriptor,
UntrackMarshalledAuthenticationOperation.Descriptor,
/*
* Challenge processing:
*/
InferBaseUriFromClientUri.Descriptor,
AttachDynamicPortToRedirectUri.Descriptor,
AttachNonDefaultResponseMode.Descriptor,
AttachInstanceIdentifier.Descriptor,
TrackAuthenticationOperation.Descriptor,
/*
* Sign-out processing:
*/
InferLogoutBaseUriFromClientUri.Descriptor,
AttachDynamicPortToPostLogoutRedirectUri.Descriptor,
AttachLogoutInstanceIdentifier.Descriptor,
TrackLogoutOperation.Descriptor,
/*
* Error processing:
*/
AbortAuthenticationDemand.Descriptor,
.. Authentication.DefaultHandlers,
.. Session.DefaultHandlers
]);
/// <summary>
/// Contains the logic responsible for resolving the request URI from the HTTP listener request.
/// Note: this handler is not used when the OpenID Connect request is not handled by the embedded web server.
/// </summary>
public sealed class ResolveRequestUriFromHttpListenerRequest : IOpenIddictClientHandler<ProcessRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireHttpListenerContext>()
.UseSingletonHandler<ResolveRequestUriFromHttpListenerRequest>()
.SetOrder(int.MinValue + 50_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// When using the OpenIddict client system integration, requests can originate from multiple sources:
//
// - A proper HTTP GET request handled by the embedded web server, when the authorization server
// returns an HTTP 302 response pointing to the local machine (e.g an authorization response).
// In this case, the handling is very similar to what's performed by the web-based OWIN or
// ASP.NET Core hosts and a proper HTTP response can be returned and rendered by the browser.
//
// - A protocol activation triggered when the authorization server returns a HTTP 302 response
// with a redirection address associated with the client application (e.g using a manifest
// or a registry entry). In this case, the redirection is handled by the operating system
// that instantiates the application process and no response can be returned to the browser.
//
// - A protocol activation redirected by another instance of the application using inter-process
// communication. The handling of such activations is similar to direct protocol activations
// and no response can be returned to the browser (that typically stays on the same page).
//
// - A redirection handled transparently by a web-view component (e.g the web authentication
// broker on Windows). In this case, the modal window created by the application or the
// operating system is automatically closed when the specified callback URI is reached
// and there is no way to return a response that would be visible by the user.
//
// OpenIddict unifies these request models by sharing the same request processing pipeline and
// by adapting the logic based on the request type (e.g only protocol activations are redirected
// to other instances and can result in the current instance being terminated by OpenIddict).
(context.BaseUri, context.RequestUri) = context.Transaction.GetHttpListenerContext() switch
{
// Note: unlike the equivalent handler in the ASP.NET Core and OWIN hosts, the URI is
// expected to be always present and absolute, as the embedded web server is configured
// to use "localhost" as the registered prefix, which forces HTTP.sys (or the managed
// .NET implementation on non-Windows operating systems) to automatically reject requests
// that don't include a Host header (e.g HTTP/1.0 requests) or specify an invalid value.
{ Request.Url: { IsAbsoluteUri: true } uri } => (
BaseUri: new UriBuilder(uri) { Path = null, Query = null, Fragment = null }.Uri,
RequestUri: uri),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0390))
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for resolving the request URI from the protocol activation details.
/// Note: this handler is not used when the OpenID Connect request is not a protocol activation.
/// </summary>
public sealed class ResolveRequestUriFromProtocolActivation : IOpenIddictClientHandler<ProcessRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireProtocolActivation>()
.UseSingletonHandler<ResolveRequestUriFromProtocolActivation>()
.SetOrder(ResolveRequestUriFromHttpListenerRequest.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
(context.BaseUri, context.RequestUri) = context.Transaction.GetProtocolActivation() switch
{
{ ActivationUri: Uri uri } => (
BaseUri: new UriBuilder(uri) { Path = null, Query = null, Fragment = null }.Uri,
RequestUri: uri),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0375))
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for resolving the request URI from the AS web authentication session callback URL.
/// Note: this handler is not used when the OpenID Connect request is not an AS web authentication session callback URL.
/// </summary>
public sealed class ResolveRequestUriFromASWebAuthenticationCallbackUrl : IOpenIddictClientHandler<ProcessRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireASWebAuthenticationCallbackUrl>()
.UseSingletonHandler<ResolveRequestUriFromASWebAuthenticationCallbackUrl>()
.SetOrder(ResolveRequestUriFromProtocolActivation.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
[SupportedOSPlatform("ios12.0")]
[SupportedOSPlatform("maccatalyst13.1")]
[SupportedOSPlatform("macos10.15")]
public ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION
(context.BaseUri, context.RequestUri) = context.Transaction.GetASWebAuthenticationCallbackUrl() switch
{
NSUrl url when Uri.TryCreate(url.AbsoluteString, UriKind.Absolute, out Uri? uri) => (
BaseUri: new UriBuilder(uri) { Path = null, Query = null, Fragment = null }.Uri,
RequestUri: uri),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0393))
};
return default;
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
#endif
}
}
/// <summary>
/// Contains the logic responsible for resolving the request URI from the web authentication result.
/// Note: this handler is not used when the OpenID Connect request is not a web authentication result.
/// </summary>
public sealed class ResolveRequestUriFromWebAuthenticationResult : IOpenIddictClientHandler<ProcessRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireWebAuthenticationResult>()
.UseSingletonHandler<ResolveRequestUriFromWebAuthenticationResult>()
.SetOrder(ResolveRequestUriFromASWebAuthenticationCallbackUrl.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
[SupportedOSPlatform("windows10.0.17763")]
public ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_WINDOWS_RUNTIME
(context.BaseUri, context.RequestUri) = context.Transaction.GetWebAuthenticationResult() switch
{
{ ResponseStatus: WebAuthenticationStatus.Success, ResponseData: string data } when
Uri.TryCreate(data, UriKind.Absolute, out Uri? uri) => (
BaseUri: new UriBuilder(uri) { Path = null, Query = null, Fragment = null }.Uri,
RequestUri: uri),
_ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0393))
};
return default;
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392));
#endif
}
}
/// <summary>
/// Contains the logic responsible for inferring the endpoint type from the request URI, ignoring
/// the port when comparing the request URI with the endpoint URIs configured in the options.
/// Note: this handler is not used when the OpenID Connect request is not handled by the embedded web server.
/// </summary>
public sealed class InferEndpointTypeFromDynamicAddress : IOpenIddictClientHandler<ProcessRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireHttpListenerContext>()
.UseSingletonHandler<InferEndpointTypeFromDynamicAddress>()
.SetOrder(InferEndpointType.Descriptor.Order + 250)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context is not { BaseUri.IsAbsoluteUri: true, RequestUri.IsAbsoluteUri: true })
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0127));
}
// If an endpoint was already inferred by the generic handler, don't override it.
if (context.EndpointType is not OpenIddictClientEndpointType.Unknown)
{
return default;
}
context.EndpointType =
Matches(context.Options.RedirectionEndpointUris) ? OpenIddictClientEndpointType.Redirection :
Matches(context.Options.PostLogoutRedirectionEndpointUris) ? OpenIddictClientEndpointType.PostLogoutRedirection :
OpenIddictClientEndpointType.Unknown;
return default;
bool Matches(IReadOnlyList<Uri> uris)
{
for (var index = 0; index < uris.Count; index++)
{
var uri = uris[index];
if (uri.IsAbsoluteUri && uri.IsLoopback && uri.IsDefaultPort && Equals(uri, context.RequestUri))
{
return true;
}
}
return false;
}
static bool Equals(Uri left, Uri right) =>
string.Equals(left.Scheme, right.Scheme, StringComparison.OrdinalIgnoreCase) &&
string.Equals(left.Host, right.Host, StringComparison.OrdinalIgnoreCase) &&
//
// Deliberately ignore the port when doing comparisons in this specialized handler.
//
// Note: paths are considered equivalent even if the casing isn't identical or if one of the two
// paths only differs by a trailing slash, which matches the classical behavior seen on ASP.NET,
// Microsoft.Owin/Katana and ASP.NET Core. Developers who prefer a different behavior can remove
// this handler and replace it by a custom version implementing a more strict comparison logic.
(string.Equals(left.AbsolutePath, right.AbsolutePath, StringComparison.OrdinalIgnoreCase) ||
(left.AbsolutePath.Length == right.AbsolutePath.Length + 1 &&
left.AbsolutePath.StartsWith(right.AbsolutePath, StringComparison.OrdinalIgnoreCase) &&
left.AbsolutePath[^1] is '/') ||
(right.AbsolutePath.Length == left.AbsolutePath.Length + 1 &&
right.AbsolutePath.StartsWith(left.AbsolutePath, StringComparison.OrdinalIgnoreCase) &&
right.AbsolutePath[^1] is '/'));
}
}
/// <summary>
/// Contains the logic responsible for rejecting unknown requests handled by the embedded web server, if applicable.
/// Note: this handler is not used when the OpenID Connect request is not handled by the embedded web server.
/// </summary>
public sealed class RejectUnknownHttpRequests : IOpenIddictClientHandler<ProcessRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireHttpListenerContext>()
.UseSingletonHandler<RejectUnknownHttpRequests>()
.SetOrder(InferEndpointTypeFromDynamicAddress.Descriptor.Order + 250)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to HTTP listener 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.GetHttpListenerContext()?.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0390));
// Unlike the ASP.NET Core or OWIN hosts, the embedded server instantiated by the system
// integration is not meant to handle requests pointing to user-defined HTTP endpoints.
// At such, reject all HTTP requests whose address doesn't match an OpenIddict endpoint.
if (context.EndpointType is OpenIddictClientEndpointType.Unknown)
{
response.StatusCode = (int) HttpStatusCode.NotFound;
context.HandleRequest();
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for extracting OpenID Connect requests from GET HTTP listener requests.
/// Note: this handler is not used when the OpenID Connect request is not handled by the embedded web server.
/// </summary>
public sealed class ExtractGetHttpListenerRequest<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseValidatingContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpListenerContext>()
.UseSingletonHandler<ExtractGetHttpListenerRequest<TContext>>()
.SetOrder(int.MinValue + 100_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 HTTP listener requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetHttpListenerContext()?.Request ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0390));
// If the incoming request doesn't use GET, reject it.
if (!string.Equals(request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.HttpMethod);
context.Reject(
error: Errors.InvalidRequest,
description: SR.GetResourceString(SR.ID2084),
uri: SR.FormatID8000(SR.ID2084));
return default;
}
context.Transaction.Request = request.QueryString.AllKeys.Length switch
{
0 => new OpenIddictRequest(),
_ => new OpenIddictRequest(request.QueryString)
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for extracting OpenID Connect requests from GET or POST HTTP listener requests.
/// Note: this handler is not used when the OpenID Connect request is not handled by the embedded web server.
/// </summary>
public sealed class ExtractGetOrPostHttpListenerRequest<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseValidatingContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireHttpListenerContext>()
.UseSingletonHandler<ExtractGetOrPostHttpListenerRequest<TContext>>()
.SetOrder(ExtractGetHttpListenerRequest<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to HTTP listener requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetHttpListenerContext()?.Request ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0390));
if (string.Equals(request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
context.Transaction.Request = request.QueryString.AllKeys.Length switch
{
0 => new OpenIddictRequest(),
_ => new OpenIddictRequest(request.QueryString)
};
}
else if (string.Equals(request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase))
{
// See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization for more information.
if (!MediaTypeHeaderValue.TryParse(request.ContentType, out MediaTypeHeaderValue? type) ||
StringSegment.IsNullOrEmpty(type.MediaType))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6138), "Content-Type");
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2081("Content-Type"),
uri: SR.FormatID8000(SR.ID2081));
return;
}
if (!StringSegment.Equals(type.MediaType, "application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6139), "Content-Type", request.ContentType);
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2082("Content-Type"),
uri: SR.FormatID8000(SR.ID2082));
return;
}
// Note: do not allow the unsafe UTF-7 encoding to be used, even if explicitly set.
// If no encoding was set or if the received value is not valid, fall back to UTF-8.
context.Transaction.Request = new OpenIddictRequest(await OpenIddictHelpers.ParseFormAsync(
stream : request.InputStream,
encoding : type.Encoding is { CodePage: not 65000 } encoding ? encoding : Encoding.UTF8,
cancellationToken: CancellationToken.None));
}
else
{
context.Logger.LogInformation(SR.GetResourceString(SR.ID6137), request.HttpMethod);
context.Reject(
error: Errors.InvalidRequest,
description: SR.GetResourceString(SR.ID2084),
uri: SR.FormatID8000(SR.ID2084));
return;
}
}
}
/// <summary>
/// Contains the logic responsible for extracting OpenID Connect requests
/// from the URI of an initial or redirected protocol activation.
/// Note: this handler is not used when the OpenID Connect request is not a protocol activation.
/// </summary>
public sealed class ExtractProtocolActivationParameters<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseValidatingContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireProtocolActivation>()
.UseSingletonHandler<ExtractProtocolActivationParameters<TContext>>()
.SetOrder(ExtractGetOrPostHttpListenerRequest<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Transaction.GetProtocolActivation() is not { ActivationUri: Uri uri })
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0375));
}
var parameters = new Dictionary<string, StringValues>(StringComparer.Ordinal);
if (!string.IsNullOrEmpty(uri.Query))
{
foreach (var parameter in OpenIddictHelpers.ParseQuery(uri.Query))
{
parameters[parameter.Key] = parameter.Value;
}
}
// Note: the fragment is always processed after the query string to ensure that
// parameters extracted from the fragment are preferred to parameters extracted
// from the query string when they are present in both parts.
if (!string.IsNullOrEmpty(uri.Fragment))
{
foreach (var parameter in OpenIddictHelpers.ParseFragment(uri.Fragment))
{
parameters[parameter.Key] = parameter.Value;
}
}
context.Transaction.Request = new OpenIddictRequest(parameters);
return default;
}
}
/// <summary>
/// Contains the logic responsible for extracting OpenID Connect requests from the callback URL of an AS session.
/// Note: this handler is not used when the OpenID Connect request is not an AS web authentication session callback.
/// </summary>
public sealed class ExtractASWebAuthenticationCallbackUrlData<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseValidatingContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireASWebAuthenticationCallbackUrl>()
.UseSingletonHandler<ExtractASWebAuthenticationCallbackUrlData<TContext>>()
.SetOrder(ExtractProtocolActivationParameters<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
[SupportedOSPlatform("ios12.0")]
[SupportedOSPlatform("maccatalyst13.1")]
[SupportedOSPlatform("macos10.15")]
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_AUTHENTICATION_SERVICES && SUPPORTS_FOUNDATION
if (context.Transaction.GetASWebAuthenticationCallbackUrl()
is not NSUrl url || !Uri.TryCreate(url.AbsoluteString, UriKind.Absolute, out Uri? uri))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0393));
}
var parameters = new Dictionary<string, StringValues>(StringComparer.Ordinal);
if (!string.IsNullOrEmpty(uri.Query))
{
foreach (var parameter in OpenIddictHelpers.ParseQuery(uri.Query))
{
parameters[parameter.Key] = parameter.Value;
}
}
// Note: the fragment is always processed after the query string to ensure that
// parameters extracted from the fragment are preferred to parameters extracted
// from the query string when they are present in both parts.
if (!string.IsNullOrEmpty(uri.Fragment))
{
foreach (var parameter in OpenIddictHelpers.ParseFragment(uri.Fragment))
{
parameters[parameter.Key] = parameter.Value;
}
}
context.Transaction.Request = new OpenIddictRequest(parameters);
return default;
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0446));
#endif
}
}
/// <summary>
/// Contains the logic responsible for extracting OpenID Connect
/// requests from the response data of a web authentication result.
/// Note: this handler is not used when the OpenID Connect request is not a web authentication result.
/// </summary>
public sealed class ExtractWebAuthenticationResultData<TContext> : IOpenIddictClientHandler<TContext> where TContext : BaseValidatingContext
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireWebAuthenticationResult>()
.UseSingletonHandler<ExtractWebAuthenticationResultData<TContext>>()
.SetOrder(ExtractASWebAuthenticationCallbackUrlData<TContext>.Descriptor.Order + 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
[SupportedOSPlatform("windows10.0.17763")]
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#if SUPPORTS_WINDOWS_RUNTIME
if (context.Transaction.GetWebAuthenticationResult()
is not { ResponseStatus: WebAuthenticationStatus.Success, ResponseData: string data } ||
!Uri.TryCreate(data, UriKind.Absolute, out Uri? uri))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0393));
}
var parameters = new Dictionary<string, StringValues>(StringComparer.Ordinal);
if (!string.IsNullOrEmpty(uri.Query))
{
foreach (var parameter in OpenIddictHelpers.ParseQuery(uri.Query))
{
parameters[parameter.Key] = parameter.Value;
}
}
// Note: the fragment is always processed after the query string to ensure that
// parameters extracted from the fragment are preferred to parameters extracted
// from the query string when they are present in both parts.
if (!string.IsNullOrEmpty(uri.Fragment))
{
foreach (var parameter in OpenIddictHelpers.ParseFragment(uri.Fragment))
{
parameters[parameter.Key] = parameter.Value;
}
}
context.Transaction.Request = new OpenIddictRequest(parameters);
return default;
#else
throw new PlatformNotSupportedException(SR.GetResourceString(SR.ID0392));
#endif
}
}
/// <summary>
/// Contains the logic responsible for waiting for the marshalled authentication operation to complete, if applicable.
/// </summary>
public sealed class WaitMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options;
public WaitMarshalledAuthentication(
OpenIddictClientSystemIntegrationMarshal marshal,
IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options)
{
_marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<WaitMarshalledAuthentication>()
.SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 250)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
// Skip the marshalling logic entirely if the operation is not tracked.
if (!_marshal.IsTracked(context.Nonce))
{
return;
}
// Allow a single authentication operation at the same time with the same nonce.
if (!await _marshal.TryAcquireLockAsync(context.Nonce, context.CancellationToken))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0379));
}
// At this point, user authentication demands cannot complete until the authorization response has been
// returned to the redirection endpoint (materialized as a registered protocol activation URI) and handled
// by OpenIddict via the ProcessRequest event. Since it is asynchronous by nature, this process requires
// using a signal mechanism to unblock the authentication operation once it is complete. For that, the
// marshal uses a TaskCompletionSource (one per authentication) that will be automatically completed or
// aborted by a specialized event handler as part of the ProcessRequest/ProcessError events processing.
try
{
// To ensure pending authentication operations for which no response is received are not tracked
// indefinitely, a CancellationTokenSource with a static timeout is used even if the cancellation
// token specified by the user is never marked as canceled: if the authentication is not completed
// when the timeout is reached, the operation will be considered canceled and removed from the list.
using var source = CancellationTokenSource.CreateLinkedTokenSource(context.CancellationToken);
source.CancelAfter(_options.CurrentValue.AuthenticationTimeout);
if (!await _marshal.TryWaitForCompletionAsync(context.Nonce, source.Token) ||
!_marshal.TryGetResult(context.Nonce, out ProcessAuthenticationContext? notification))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0383));
}
if (notification.IsRequestHandled)
{
context.HandleRequest();
return;
}
else if (notification.IsRequestSkipped)
{
context.SkipRequest();
return;
}
else if (notification.IsRejected)
{
context.Reject(
error: notification.Error ?? Errors.InvalidRequest,
description: notification.ErrorDescription,
uri: notification.ErrorUri);
return;
}
}
// If the operation failed due to the timeout, it's likely the TryRemove() method
// won't be called, so the tracked context is manually removed before re-throwing.
catch (OperationCanceledException) when (_marshal.TryRemove(context.Nonce))
{
throw;
}
}
}
/// <summary>
/// Contains the logic responsible for restoring the client registration and
/// configuration from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreClientRegistrationFromMarshalledContext : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreClientRegistrationFromMarshalledContext(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreClientRegistrationFromMarshalledContext>()
.SetOrder(ResolveClientRegistrationFromAuthenticationContext.Descriptor.Order - 250)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
(context.Configuration, context.Registration) = context.EndpointType switch
{
// When the authentication context is marshalled, restore the
// issuer registration and configuration from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> (notification.Configuration, notification.Registration),
_ => (context.Configuration, context.Registration)
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the state token
/// from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreStateTokenFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreStateTokenFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreStateTokenFromMarshalledAuthentication>()
.SetOrder(ResolveValidatedStateToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.StateToken = context.EndpointType switch
{
// When the authentication context is marshalled, restore the state token from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.StateToken,
// Otherwise, don't alter the current context.
_ => context.StateToken
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the state token
/// principal from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreStateTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreStateTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreStateTokenPrincipalFromMarshalledAuthentication>()
.SetOrder(ValidateStateToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.StateTokenPrincipal = context.EndpointType switch
{
// When the authentication context is marshalled, restore
// the state token principal from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.StateTokenPrincipal,
// Otherwise, don't alter the current context.
_ => context.StateTokenPrincipal
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the host authentication
/// properties from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreHostAuthenticationPropertiesFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreHostAuthenticationPropertiesFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreHostAuthenticationPropertiesFromMarshalledAuthentication>()
.SetOrder(ResolveHostAuthenticationPropertiesFromStateToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
// When the authentication context is marshalled, restore the
// host authentication properties from the other instance.
if (context.EndpointType is OpenIddictClientEndpointType.Unknown &&
_marshal.TryGetResult(context.Nonce, out var notification))
{
foreach (var property in notification.Properties)
{
context.Properties[property.Key] = property.Value;
}
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for redirecting the protocol activation to
/// the instance that initially started the authentication demand, if applicable.
/// Note: this handler is not used when the OpenID Connect request is not a protocol activation.
/// </summary>
public sealed class RedirectProtocolActivation : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly IHostApplicationLifetime _lifetime;
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options;
private readonly OpenIddictClientSystemIntegrationService _service;
public RedirectProtocolActivation(
IHostApplicationLifetime lifetime,
IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options,
OpenIddictClientSystemIntegrationService service)
{
_lifetime = lifetime ?? throw new ArgumentNullException(nameof(lifetime));
_options = options ?? throw new ArgumentNullException(nameof(options));
_service = service ?? throw new ArgumentNullException(nameof(service));
}
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireProtocolActivation>()
.AddFilter<RequireStateTokenPrincipal>()
.UseSingletonHandler<RedirectProtocolActivation>()
.SetOrder(ResolveNonceFromStateToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.StateTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
var activation = context.Transaction.GetProtocolActivation() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0375));
var identifier = context.StateTokenPrincipal.GetClaim(Claims.Private.InstanceId);
if (string.IsNullOrEmpty(identifier))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0376));
}
// If the identifier stored in the state token doesn't match the identifier of the
// current instance, stop processing the authentication demand in this process and
// redirect the protocol activation to the correct instance. Once the redirection
// has been received by the other instance, ask the host to stop the application.
if (string.Equals(identifier, _options.CurrentValue.InstanceIdentifier, StringComparison.OrdinalIgnoreCase))
{
return;
}
// If protocol activation redirection was not enabled, reject the request
// as there's no additional processing that can be made at this stage.
if (_options.CurrentValue.EnableActivationRedirection is not true)
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.GetResourceString(SR.ID2166),
uri: SR.FormatID8000(SR.ID2166));
return;
}
// Try to redirect the protocol activation to the correct instance.
try
{
using var source = new CancellationTokenSource(delay: TimeSpan.FromSeconds(10));
await _service.RedirectProtocolActivationAsync(activation, identifier, source.Token);
}
catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
{
context.Logger.LogWarning(SR.GetResourceString(SR.ID6215), identifier);
}
// Inform the host that the application should stop and mark the authentication context as handled
// to prevent the other event handlers from being invoked while the application is shutting down.
_lifetime.StopApplication();
context.HandleRequest();
}
}
/// <summary>
/// Contains the logic responsible for resolving the request forgery protection that serves as a
/// protection against state token injection, forged requests and session fixation attacks.
/// </summary>
public sealed class ResolveRequestForgeryProtection : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public ResolveRequestForgeryProtection(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.AddFilter<RequireStateTokenPrincipal>()
.AddFilter<RequireStateTokenValidated>()
.UseSingletonHandler<ResolveRequestForgeryProtection>()
.SetOrder(ValidateRequestForgeryProtection.Descriptor.Order - 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
// Ensure the authentication demand is tracked by the OpenIddict client system integration
// marshal and resolve the corresponding request forgery protection. If it can't be found,
// this may indicate a session fixation attack: in this case, reject the authentication demand.
if (!_marshal.TryGetRequestForgeryProtection(context.Nonce, out string? protection))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.GetResourceString(SR.ID2139),
uri: SR.FormatID8000(SR.ID2139));
return default;
}
context.RequestForgeryProtection = protection;
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the frontchannel tokens
/// from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreFrontchannelTokensFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreFrontchannelTokensFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreFrontchannelTokensFromMarshalledAuthentication>()
.SetOrder(ResolveValidatedFrontchannelTokens.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
(context.AuthorizationCode,
context.FrontchannelAccessToken,
context.FrontchannelIdentityToken) = context.EndpointType switch
{
// When the authentication context is marshalled, restore the tokens from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> (notification.AuthorizationCode, notification.FrontchannelAccessToken, notification.FrontchannelIdentityToken),
// Otherwise, don't alter the current context.
_ => (context.AuthorizationCode, context.FrontchannelAccessToken, context.FrontchannelIdentityToken)
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the frontchannel identity
/// token principal from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreFrontchannelIdentityTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreFrontchannelIdentityTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreFrontchannelIdentityTokenPrincipalFromMarshalledAuthentication>()
.SetOrder(ValidateFrontchannelIdentityToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.FrontchannelIdentityTokenPrincipal = context.EndpointType switch
{
// When the authentication context is marshalled, restore the
// frontchannel identity token principal from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.FrontchannelIdentityTokenPrincipal,
// Otherwise, don't alter the current context.
_ => context.FrontchannelIdentityTokenPrincipal
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the frontchannel access
/// token principal from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreFrontchannelAccessTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreFrontchannelAccessTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreFrontchannelAccessTokenPrincipalFromMarshalledAuthentication>()
.SetOrder(ValidateFrontchannelAccessToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.FrontchannelAccessTokenPrincipal = context.EndpointType switch
{
// When the authentication context is marshalled, restore the
// frontchannel access token principal from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.FrontchannelAccessTokenPrincipal,
// Otherwise, don't alter the current context.
_ => context.FrontchannelAccessTokenPrincipal
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the authorization code
/// principal from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreAuthorizationCodePrincipalFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreAuthorizationCodePrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreAuthorizationCodePrincipalFromMarshalledAuthentication>()
.SetOrder(ValidateAuthorizationCode.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.AuthorizationCodePrincipal = context.EndpointType switch
{
// When the authentication context is marshalled, restore the
// authorization code principal from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.AuthorizationCodePrincipal,
// Otherwise, don't alter the current context.
_ => context.AuthorizationCodePrincipal
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the token response
/// from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreTokenResponseFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreTokenResponseFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreTokenResponseFromMarshalledAuthentication>()
.SetOrder(SendTokenRequest.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.TokenResponse = context.EndpointType switch
{
// When the authentication context is marshalled, restore the token response from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.TokenResponse,
// Otherwise, don't alter the current context.
_ => context.TokenResponse
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the backchannel tokens
/// from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreBackchannelTokensFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreBackchannelTokensFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreBackchannelTokensFromMarshalledAuthentication>()
.SetOrder(ResolveValidatedBackchannelTokens.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
(context.BackchannelAccessToken,
context.BackchannelIdentityToken,
context.RefreshToken) = context.EndpointType switch
{
// When the authentication context is marshalled, restore the tokens from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> (notification.BackchannelAccessToken, notification.BackchannelIdentityToken, notification.RefreshToken),
// Otherwise, don't alter the current context.
_ => (context.BackchannelAccessToken, context.BackchannelIdentityToken, context.RefreshToken)
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the backchannel identity
/// token principal from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreBackchannelIdentityTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreBackchannelIdentityTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreBackchannelIdentityTokenPrincipalFromMarshalledAuthentication>()
.SetOrder(ValidateBackchannelIdentityToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.BackchannelIdentityTokenPrincipal = context.EndpointType switch
{
// When the authentication context is marshalled, restore the
// frontchannel identity token principal from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.BackchannelIdentityTokenPrincipal,
// Otherwise, don't alter the current context.
_ => context.BackchannelIdentityTokenPrincipal
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the frontchannel access
/// token principal from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreBackchannelAccessTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreBackchannelAccessTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreBackchannelAccessTokenPrincipalFromMarshalledAuthentication>()
.SetOrder(ValidateBackchannelAccessToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.BackchannelAccessTokenPrincipal = context.EndpointType switch
{
// When the authentication context is marshalled, restore the
// frontchannel access token principal from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.BackchannelAccessTokenPrincipal,
// Otherwise, don't alter the current context.
_ => context.BackchannelAccessTokenPrincipal
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the refresh token
/// principal from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreRefreshTokenPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreRefreshTokenPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreRefreshTokenPrincipalFromMarshalledAuthentication>()
.SetOrder(ValidateRefreshToken.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.RefreshTokenPrincipal = context.EndpointType switch
{
// When the authentication context is marshalled, restore
// the refresh token principal from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.RefreshTokenPrincipal,
// Otherwise, don't alter the current context.
_ => context.RefreshTokenPrincipal
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the userinfo details
/// from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreUserinfoDetailsFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreUserinfoDetailsFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreUserinfoDetailsFromMarshalledAuthentication>()
.SetOrder(ValidateUserinfoTokenSubject.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
(context.UserinfoResponse, context.UserinfoTokenPrincipal, context.UserinfoToken) = context.EndpointType switch
{
// When the authentication context is marshalled, restore the userinfo details from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> (notification.UserinfoResponse, notification.UserinfoTokenPrincipal, notification.UserinfoToken),
// Otherwise, don't alter the current context.
_ => (context.UserinfoResponse, context.UserinfoTokenPrincipal, context.UserinfoToken)
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for restoring the merged principal from the marshalled authentication context, if applicable.
/// </summary>
public sealed class RestoreMergedPrincipalFromMarshalledAuthentication : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public RestoreMergedPrincipalFromMarshalledAuthentication(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<RestoreMergedPrincipalFromMarshalledAuthentication>()
.SetOrder(PopulateMergedPrincipal.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
context.MergedPrincipal = context.EndpointType switch
{
// When the authentication context is marshalled, restore the merged principal from the other instance.
OpenIddictClientEndpointType.Unknown when _marshal.TryGetResult(context.Nonce, out var notification)
=> notification.MergedPrincipal,
// Otherwise, don't alter the current context.
_ => context.MergedPrincipal
};
return default;
}
}
/// <summary>
/// Contains the logic responsible for informing the authentication service the operation is complete.
/// </summary>
public sealed class CompleteAuthenticationOperation : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public CompleteAuthenticationOperation(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.AddFilter<RequireStateTokenPrincipal>()
.AddFilter<RequireStateTokenValidated>()
.UseSingletonHandler<CompleteAuthenticationOperation>()
.SetOrder(int.MaxValue - 50_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
// Inform the marshal that the authentication demand is complete.
if (!_marshal.TryComplete(context.Nonce, context))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0380));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for informing the marshal that the context
/// associated with the authentication operation can be discarded, if applicable.
/// </summary>
public sealed class UntrackMarshalledAuthenticationOperation : IOpenIddictClientHandler<ProcessAuthenticationContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public UntrackMarshalledAuthenticationOperation(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireAuthenticationNonce>()
.UseSingletonHandler<UntrackMarshalledAuthenticationOperation>()
.SetOrder(int.MaxValue)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.Nonce), SR.GetResourceString(SR.ID4019));
// If applicable, inform the marshal that the authentication demand can be discarded.
if (context.EndpointType is OpenIddictClientEndpointType.Unknown &&
_marshal.IsTracked(context.Nonce) && !_marshal.TryRemove(context.Nonce))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0381));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for inferring the base URI from the client URI set in the options.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public sealed class InferBaseUriFromClientUri : IOpenIddictClientHandler<ProcessChallengeContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireInteractiveSession>()
.UseSingletonHandler<InferBaseUriFromClientUri>()
.SetOrder(ValidateChallengeDemand.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
context.BaseUri ??= context.Options.ClientUri;
return default;
}
}
/// <summary>
/// Contains the logic responsible for attaching the listening port
/// of the embedded web server to the redirect_uri, if applicable.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public sealed class AttachDynamicPortToRedirectUri : IOpenIddictClientHandler<ProcessChallengeContext>
{
private readonly OpenIddictClientSystemIntegrationHttpListener _listener;
public AttachDynamicPortToRedirectUri(OpenIddictClientSystemIntegrationHttpListener listener)
=> _listener = listener ?? throw new ArgumentNullException(nameof(listener));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireInteractiveGrantType>()
.AddFilter<RequireEmbeddedWebServerEnabled>()
// Note: only apply the dynamic port replacement logic if the callback request
// is going to be received by the system browser to ensure it doesn't apply to
// challenge demands handled via a web authentication broker.
.AddFilter<RequireSystemBrowser>()
.UseSingletonHandler<AttachDynamicPortToRedirectUri>()
.SetOrder(AttachRedirectUri.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If the redirect_uri uses a loopback host/IP as the authority and doesn't include a non-default port,
// determine whether the embedded web server is running: if so, override the port in the redirect_uri
// by the port used by the embedded web server (guaranteed to be running if a value is returned).
if (!string.IsNullOrEmpty(context.RedirectUri) &&
Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) &&
string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) &&
uri.IsLoopback &&
uri.IsDefaultPort &&
await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) is int port)
{
var builder = new UriBuilder(context.RedirectUri)
{
Port = port
};
context.RedirectUri = builder.Uri.AbsoluteUri;
}
}
}
/// <summary>
/// Contains the logic responsible for attaching a non-default response mode to the challenge request, if applicable.
/// </summary>
public sealed class AttachNonDefaultResponseMode : IOpenIddictClientHandler<ProcessChallengeContext>
{
private readonly OpenIddictClientSystemIntegrationHttpListener _listener;
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options;
public AttachNonDefaultResponseMode(
OpenIddictClientSystemIntegrationHttpListener listener,
IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options)
{
_listener = listener ?? throw new ArgumentNullException(nameof(listener));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireInteractiveGrantType>()
.UseSingletonHandler<AttachNonDefaultResponseMode>()
.SetOrder(AttachResponseMode.Descriptor.Order - 500)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If an explicit response type was specified, don't overwrite it.
if (!string.IsNullOrEmpty(context.ResponseMode))
{
return;
}
// Some specific response_type/response_mode combinations are not allowed (e.g response_mode=query
// can never be used with a response type containing id_token or token, as required by the OAuth 2.0
// multiple response types specification. To prevent invalid combinations from being sent to the
// remote server, the response types are taken into account when selecting the best response mode.
if (context.ResponseType?.Split(Separators.Space) is not IList<string> { Count: > 0 } types)
{
return;
}
context.ResponseMode = (
// Note: if response modes are explicitly listed in the client registration, only use
// the response modes that are both listed and enabled in the global client options.
// Otherwise, always default to the response modes that have been enabled globally.
SupportedClientResponseModes: context.Registration.ResponseModes.Count switch
{
0 => context.Options.ResponseModes as ICollection<string>,
_ => context.Options.ResponseModes.Intersect(context.Registration.ResponseModes, StringComparer.Ordinal).ToList()
},
SupportedServerResponseModes: context.Configuration.ResponseModesSupported) switch
{
#if SUPPORTS_WINDOWS_RUNTIME
// When using the web authentication broker on Windows, if both the client and
// the server support response_mode=fragment, use it if the response types contain
// a value that prevents response_mode=query from being used (token/id_token).
({ Count: > 0 } client, { Count: > 0 } server) when
OpenIddictClientSystemIntegrationHelpers.IsWebAuthenticationBrokerSupported() &&
IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.WebAuthenticationBroker) &&
client.Contains(ResponseModes.Fragment) && server.Contains(ResponseModes.Fragment) &&
(types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token))
=> ResponseModes.Fragment,
// When using the web authentication broker on Windows, if the client supports
// response_mode=fragment and the server doesn't specify a list of response modes,
// assume it is supported and use it if the response types contain a value that
// prevents response_mode=query from being used (token/id_token).
({ Count: > 0 } client, { Count: 0 }) when
OpenIddictClientSystemIntegrationHelpers.IsWebAuthenticationBrokerSupported() &&
IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.WebAuthenticationBroker) &&
client.Contains(ResponseModes.Fragment) &&
(types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token))
=> ResponseModes.Fragment,
#endif
// When using browser-based authentication with a redirect_uri not pointing to the embedded server,
// if both the client and the server support response_mode=fragment, use it if the response types
// contain a value that prevents response_mode=query from being used (token/id_token).
({ Count: > 0 } client, { Count: > 0 } server) when
IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) &&
!await IsEmbeddedWebServerRedirectUriAsync() &&
client.Contains(ResponseModes.Fragment) && server.Contains(ResponseModes.Fragment) &&
(types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token))
=> ResponseModes.Fragment,
// When using browser-based authentication with a redirect_uri not pointing to the embedded server,
// if the client supports response_mode=fragment and the server doesn't specify a list of response
// modes, assume it is supported and use it if the response types contain a value that prevents
// response_mode=query from being used (token/id_token).
({ Count: > 0 } client, { Count: 0 }) when
IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) &&
!await IsEmbeddedWebServerRedirectUriAsync() &&
client.Contains(ResponseModes.Fragment) &&
(types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token))
=> ResponseModes.Fragment,
// When using browser-based authentication with a redirect_uri pointing to the embedded server,
// if both the client and the server support response_mode=form_post, use it if the response
// types contain a value that prevents response_mode=query from being used (token/id_token).
({ Count: > 0 } client, { Count: > 0 } server) when
IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) &&
await IsEmbeddedWebServerRedirectUriAsync() &&
client.Contains(ResponseModes.FormPost) && server.Contains(ResponseModes.FormPost) &&
(types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token))
=> ResponseModes.FormPost,
// When using browser-based authentication with a redirect_uri pointing to the embedded server,
// if the client supports response_mode=form_post and the server doesn't specify a list
// of response modes, assume it is supported and use it if the response types contain
// a value that prevents response_mode=query from being used (token/id_token).
({ Count: > 0 } client, { Count: 0 }) when
IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) &&
await IsEmbeddedWebServerRedirectUriAsync() &&
client.Contains(ResponseModes.FormPost) &&
(types.Contains(ResponseTypes.IdToken) || types.Contains(ResponseTypes.Token))
=> ResponseModes.FormPost,
// If both the client and the server support response_mode=query, use it.
({ Count: > 0 } client, { Count: > 0 } server) when
client.Contains(ResponseModes.Query) && server.Contains(ResponseModes.Query)
=> ResponseModes.Query,
// If the client supports response_mode=query and the server doesn't
// specify a list of response modes, assume it is supported.
({ Count: > 0 } client, { Count: 0 }) when client.Contains(ResponseModes.Query)
=> ResponseModes.Query,
#if SUPPORTS_WINDOWS_RUNTIME
// When using the web authentication broker on Windows, if both
// the client and the server support response_mode=fragment, use it.
({ Count: > 0 } client, { Count: > 0 } server) when
OpenIddictClientSystemIntegrationHelpers.IsWebAuthenticationBrokerSupported() &&
IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.WebAuthenticationBroker) &&
client.Contains(ResponseModes.Fragment) && server.Contains(ResponseModes.Fragment)
=> ResponseModes.Fragment,
#endif
// When using browser-based authentication with a redirect_uri not pointing to the embedded
// server, if both the client and the server support response_mode=fragment, use it.
({ Count: > 0 } client, { Count: > 0 } server) when
IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) &&
!await IsEmbeddedWebServerRedirectUriAsync() &&
client.Contains(ResponseModes.Fragment) && server.Contains(ResponseModes.Fragment)
=> ResponseModes.Fragment,
// When using browser-based authentication with a redirect_uri pointing to the embedded
// server, if both the client and the server support response_mode=form_post, use it.
({ Count: > 0 } client, { Count: > 0 } server) when
IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode.SystemBrowser) &&
await IsEmbeddedWebServerRedirectUriAsync() &&
client.Contains(ResponseModes.FormPost) && server.Contains(ResponseModes.FormPost)
=> ResponseModes.FormPost,
// Assign a null value to allow the generic handler present in
// the base client package to negotiate other response modes.
_ => null
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
bool IsAuthenticationMode(OpenIddictClientSystemIntegrationAuthenticationMode mode)
{
if (context.Transaction.Properties.TryGetValue(
typeof(OpenIddictClientSystemIntegrationAuthenticationMode).FullName!, out var result) &&
result is OpenIddictClientSystemIntegrationAuthenticationMode value)
{
return mode == value;
}
return mode == _options.CurrentValue.AuthenticationMode;
}
async ValueTask<bool> IsEmbeddedWebServerRedirectUriAsync()
=> _options.CurrentValue.EnableEmbeddedWebServer is true &&
Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) &&
string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) &&
uri.IsLoopback &&
uri.Port == await _listener.GetEmbeddedServerPortAsync(context.CancellationToken);
}
}
/// <summary>
/// Contains the logic responsible for storing the identifier of the current instance in the state token.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public sealed class AttachInstanceIdentifier : IOpenIddictClientHandler<ProcessChallengeContext>
{
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options;
public AttachInstanceIdentifier(IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireLoginStateTokenGenerated>()
.UseSingletonHandler<AttachInstanceIdentifier>()
.SetOrder(PrepareLoginStateTokenPrincipal.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.StateTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
// Most applications (except Windows UWP applications) are multi-instanced. As such, any protocol activation
// triggered by launching one of the URI schemes associated with the application will create a new instance,
// different from the one that initially started the authentication flow. To deal with that without having to
// share persistent state between instances, OpenIddict stores the identifier of the instance that starts the
// authentication process and uses it when handling the callback to determine whether the protocol activation
// should be redirected to a different instance using inter-process communication.
context.StateTokenPrincipal.SetClaim(Claims.Private.InstanceId, _options.CurrentValue.InstanceIdentifier);
return default;
}
}
/// <summary>
/// Contains the logic responsible for asking the marshal to track the authentication operation.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public sealed class TrackAuthenticationOperation : IOpenIddictClientHandler<ProcessChallengeContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public TrackAuthenticationOperation(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessChallengeContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireLoginStateTokenGenerated>()
.UseSingletonHandler<TrackAuthenticationOperation>()
.SetOrder(100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessChallengeContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (string.IsNullOrEmpty(context.Nonce))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0352));
}
if (string.IsNullOrEmpty(context.RequestForgeryProtection))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0343));
}
if (!_marshal.TryAdd(context.Nonce, context.RequestForgeryProtection))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0378));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for inferring the base URI from the client URI set in the options.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public sealed class InferLogoutBaseUriFromClientUri : IOpenIddictClientHandler<ProcessSignOutContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireInteractiveSession>()
.UseSingletonHandler<InferLogoutBaseUriFromClientUri>()
.SetOrder(ValidateSignOutDemand.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignOutContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
context.BaseUri ??= context.Options.ClientUri;
return default;
}
}
/// <summary>
/// Contains the logic responsible for attaching the listening port of the
/// embedded web server to the post_logout_redirect_uri, if applicable.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public sealed class AttachDynamicPortToPostLogoutRedirectUri : IOpenIddictClientHandler<ProcessSignOutContext>
{
private readonly OpenIddictClientSystemIntegrationHttpListener _listener;
public AttachDynamicPortToPostLogoutRedirectUri(OpenIddictClientSystemIntegrationHttpListener listener)
=> _listener = listener ?? throw new ArgumentNullException(nameof(listener));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireEmbeddedWebServerEnabled>()
// Note: only apply the dynamic port replacement logic if the callback request
// is going to be received by the system browser to ensure it doesn't apply to
// sign-out demands handled via a web authentication broker are not affected.
.AddFilter<RequireSystemBrowser>()
.UseSingletonHandler<AttachDynamicPortToPostLogoutRedirectUri>()
.SetOrder(AttachPostLogoutRedirectUri.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessSignOutContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If the post_logout_redirect_uri uses a loopback host/IP as the authority and doesn't include a non-default port,
// determine whether the embedded web server is running: if so, override the port in the post_logout_redirect_uri
// by the port used by the embedded web server (guaranteed to be running if a value is returned).
if (!string.IsNullOrEmpty(context.PostLogoutRedirectUri) &&
Uri.TryCreate(context.PostLogoutRedirectUri, UriKind.Absolute, out Uri? uri) &&
string.Equals(uri.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) &&
uri.IsLoopback &&
uri.IsDefaultPort &&
await _listener.GetEmbeddedServerPortAsync(context.CancellationToken) is int port)
{
var builder = new UriBuilder(context.PostLogoutRedirectUri)
{
Port = port
};
context.PostLogoutRedirectUri = builder.Uri.AbsoluteUri;
}
}
}
/// <summary>
/// Contains the logic responsible for storing the identifier of the current instance in the state token.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public sealed class AttachLogoutInstanceIdentifier : IOpenIddictClientHandler<ProcessSignOutContext>
{
private readonly IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> _options;
public AttachLogoutInstanceIdentifier(IOptionsMonitor<OpenIddictClientSystemIntegrationOptions> options)
=> _options = options ?? throw new ArgumentNullException(nameof(options));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireLogoutStateTokenGenerated>()
.UseSingletonHandler<AttachLogoutInstanceIdentifier>()
.SetOrder(PrepareLogoutStateTokenPrincipal.Descriptor.Order + 500)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignOutContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(context.StateTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
// Most applications (except Windows UWP applications) are multi-instanced. As such, any protocol activation
// triggered by launching one of the URI schemes associated with the application will create a new instance,
// different from the one that initially started the logout flow. To deal with that without having to share
// persistent state between instances, OpenIddict stores the identifier of the instance that starts the
// logout process and uses it when handling the callback to determine whether the protocol activation
// should be redirected to a different instance using inter-process communication.
context.StateTokenPrincipal.SetClaim(Claims.Private.InstanceId, _options.CurrentValue.InstanceIdentifier);
return default;
}
}
/// <summary>
/// Contains the logic responsible for asking the marshal to track the logout operation.
/// Note: this handler is not used when the user session is not interactive.
/// </summary>
public sealed class TrackLogoutOperation : IOpenIddictClientHandler<ProcessSignOutContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
public TrackLogoutOperation(OpenIddictClientSystemIntegrationMarshal marshal)
=> _marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessSignOutContext>()
.AddFilter<RequireInteractiveSession>()
.AddFilter<RequireLogoutStateTokenGenerated>()
.UseSingletonHandler<TrackLogoutOperation>()
.SetOrder(100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessSignOutContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (string.IsNullOrEmpty(context.Nonce))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0352));
}
if (string.IsNullOrEmpty(context.RequestForgeryProtection))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0343));
}
if (!_marshal.TryAdd(context.Nonce, context.RequestForgeryProtection))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0378));
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for informing the authentication service the demand is aborted.
/// </summary>
public sealed class AbortAuthenticationDemand : IOpenIddictClientHandler<ProcessErrorContext>
{
private readonly OpenIddictClientSystemIntegrationMarshal _marshal;
private readonly IHostApplicationLifetime _lifetime;
public AbortAuthenticationDemand(
OpenIddictClientSystemIntegrationMarshal marshal,
IHostApplicationLifetime lifetime)
{
_marshal = marshal ?? throw new ArgumentNullException(nameof(marshal));
_lifetime = lifetime ?? throw new ArgumentNullException(nameof(lifetime));
}
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessErrorContext>()
.UseSingletonHandler<AbortAuthenticationDemand>()
.SetOrder(100_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ProcessErrorContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Try to resolve the authentication context from the transaction, if available.
var notification = context.Transaction.GetProperty<ProcessAuthenticationContext>(
typeof(ProcessAuthenticationContext).FullName!);
// If the context is available, resolve the nonce used to track the marshalled authentication
// and inform the marshal so that the context can be marshalled back to the initiator.
if (!string.IsNullOrEmpty(notification?.Nonce) && !_marshal.TryComplete(notification.Nonce, notification))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0382));
}
// If the current application instance was created to react to a protocol activation (assumed to be
// managed by OpenIddict at this stage), terminate it to prevent the UI thread from being started.
// By doing that, unsolicited requests will be discarded without the user seeing flashing windows.
if (context.Transaction.GetProtocolActivation() is { IsActivationRedirected: false })
{
_lifetime.StopApplication();
context.HandleRequest();
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible for attaching an appropriate HTTP status code.
/// Note: this handler is not used when the OpenID Connect request is not handled by the embedded web server.
/// </summary>
public sealed class AttachHttpResponseCode<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<RequireHttpListenerContext>()
.UseSingletonHandler<AttachHttpResponseCode<TContext>>()
.SetOrder(100_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 HTTP listener 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.GetHttpListenerContext()?.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0390));
Debug.Assert(context.Transaction.Response is not null, SR.GetResourceString(SR.ID4007));
response.StatusCode = context.Transaction.Response.Error switch
{
null => 200, // Note: the default code may be replaced by another handler (e.g when doing redirects).
_ => 400
};
return default;
}
}
/// <summary>
/// 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 handled by the embedded web server.
/// </summary>
public sealed class AttachCacheControlHeader<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<RequireHttpListenerContext>()
.UseSingletonHandler<AttachCacheControlHeader<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 HTTP listener 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.GetHttpListenerContext()?.Response ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0390));
// Prevent the response from being cached.
response.Headers[Headers.CacheControl] = "no-store";
response.Headers[Headers.Pragma] = "no-cache";
response.Headers[Headers.Expires] = "Thu, 01 Jan 1970 00:00:00 GMT";
return default;
}
}
/// <summary>
/// Contains the logic responsible for marking OpenID Connect
/// responses returned via protocol activations as processed.
/// </summary>
public sealed class ProcessProtocolActivationResponse<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<RequireProtocolActivation>()
.UseSingletonHandler<ProcessProtocolActivationResponse<TContext>>()
.SetOrder(ProcessWebAuthenticationResultResponse<TContext>.Descriptor.Order - 1_000)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// For both protocol activations (initial or redirected) and web-view-like results,
// no proper response can be generated and eventually displayed to the user. In this
// case, simply stop processing the response and mark the request as fully handled.
//
// Note: this logic applies to both successful and errored responses.
context.HandleRequest();
return default;
}
}
/// <summary>
/// Contains the logic responsible for marking OpenID Connect responses
/// returned via AS web authentication callback URLs as processed.
/// </summary>
public sealed class ProcessASWebAuthenticationSessionResponse<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<RequireASWebAuthenticationCallbackUrl>()
.UseSingletonHandler<ProcessASWebAuthenticationSessionResponse<TContext>>()
.SetOrder(int.MaxValue)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// For both protocol activations (initial or redirected) and web-view-like results,
// no proper response can be generated and eventually displayed to the user. In this
// case, simply stop processing the response and mark the request as fully handled.
//
// Note: this logic applies to both successful and errored responses.
context.HandleRequest();
return default;
}
}
/// <summary>
/// Contains the logic responsible for marking OpenID Connect
/// responses returned via web authentication results as processed.
/// </summary>
public sealed class ProcessWebAuthenticationResultResponse<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<RequireWebAuthenticationResult>()
.UseSingletonHandler<ProcessWebAuthenticationResultResponse<TContext>>()
.SetOrder(int.MaxValue)
.SetType(OpenIddictClientHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// For both protocol activations (initial or redirected) and web-view-like results,
// no proper response can be generated and eventually displayed to the user. In this
// case, simply stop processing the response and mark the request as fully handled.
//
// Note: this logic applies to both successful and errored responses.
context.HandleRequest();
return default;
}
}
}