/* * 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.Diagnostics; using System.Security.Cryptography; using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; namespace OpenIddict.Server; public static partial class OpenIddictServerHandlers { public static class Discovery { public static ImmutableArray DefaultHandlers { get; } = [ /* * Configuration request top-level processing: */ ExtractConfigurationRequest.Descriptor, ValidateConfigurationRequest.Descriptor, HandleConfigurationRequest.Descriptor, ApplyConfigurationResponse.Descriptor, ApplyConfigurationResponse.Descriptor, /* * Configuration request handling: */ AttachIssuer.Descriptor, AttachEndpoints.Descriptor, AttachGrantTypes.Descriptor, AttachResponseTypes.Descriptor, AttachResponseModes.Descriptor, AttachClientAuthenticationMethods.Descriptor, AttachCodeChallengeMethods.Descriptor, AttachScopes.Descriptor, AttachClaims.Descriptor, AttachSubjectTypes.Descriptor, AttachPromptValues.Descriptor, AttachSigningAlgorithms.Descriptor, AttachSecurityRequirements.Descriptor, AttachAdditionalMetadata.Descriptor, /* * JSON Web Key Set request top-level processing: */ ExtractJsonWebKeySetRequest.Descriptor, ValidateJsonWebKeySetRequest.Descriptor, HandleJsonWebKeySetRequest.Descriptor, ApplyJsonWebKeySetResponse.Descriptor, ApplyJsonWebKeySetResponse.Descriptor, /* * JSON Web Key Set request handling: */ AttachSigningKeys.Descriptor ]; /// /// Contains the logic responsible for extracting configuration requests and invoking the corresponding event handlers. /// public sealed class ExtractConfigurationRequest : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ExtractConfigurationRequest(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(100_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { ArgumentNullException.ThrowIfNull(context); var notification = new ExtractConfigurationRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); 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 (notification.Request is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0037)); } context.Logger.LogInformation(6066, SR.GetResourceString(SR.ID6066), notification.Request); } } /// /// Contains the logic responsible for validating configuration requests and invoking the corresponding event handlers. /// public sealed class ValidateConfigurationRequest : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ValidateConfigurationRequest(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ExtractConfigurationRequest.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { ArgumentNullException.ThrowIfNull(context); var notification = new ValidateConfigurationRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); 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; } context.Logger.LogInformation(6067, SR.GetResourceString(SR.ID6067)); } } /// /// Contains the logic responsible for handling configuration requests and invoking the corresponding event handlers. /// public sealed class HandleConfigurationRequest : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public HandleConfigurationRequest(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ValidateConfigurationRequest.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { ArgumentNullException.ThrowIfNull(context); var notification = new HandleConfigurationRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); 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; } var response = new OpenIddictResponse { [Metadata.Issuer] = notification.Issuer?.AbsoluteUri, [Metadata.AuthorizationEndpoint] = notification.AuthorizationEndpoint?.AbsoluteUri, [Metadata.TokenEndpoint] = notification.TokenEndpoint?.AbsoluteUri, [Metadata.IntrospectionEndpoint] = notification.IntrospectionEndpoint?.AbsoluteUri, [Metadata.EndSessionEndpoint] = notification.EndSessionEndpoint?.AbsoluteUri, [Metadata.RevocationEndpoint] = notification.RevocationEndpoint?.AbsoluteUri, [Metadata.UserInfoEndpoint] = notification.UserInfoEndpoint?.AbsoluteUri, [Metadata.DeviceAuthorizationEndpoint] = notification.DeviceAuthorizationEndpoint?.AbsoluteUri, [Metadata.PushedAuthorizationRequestEndpoint] = notification.PushedAuthorizationEndpoint?.AbsoluteUri, [Metadata.MtlsEndpointAliases] = CreateMtlsEndpointAliases(notification), [Metadata.JwksUri] = notification.JsonWebKeySetEndpoint?.AbsoluteUri, [Metadata.GrantTypesSupported] = notification.GrantTypes.ToImmutableArray(), [Metadata.ResponseTypesSupported] = notification.ResponseTypes.ToImmutableArray(), [Metadata.ResponseModesSupported] = notification.ResponseModes.ToImmutableArray(), [Metadata.ScopesSupported] = notification.Scopes.ToImmutableArray(), [Metadata.ClaimsSupported] = notification.Claims.ToImmutableArray(), [Metadata.IdTokenSigningAlgValuesSupported] = notification.IdTokenSigningAlgorithms.ToImmutableArray(), [Metadata.CodeChallengeMethodsSupported] = notification.CodeChallengeMethods.ToImmutableArray(), [Metadata.SubjectTypesSupported] = notification.SubjectTypes.ToImmutableArray(), [Metadata.PromptValuesSupported] = notification.PromptValues.ToImmutableArray(), [Metadata.TokenEndpointAuthMethodsSupported] = notification.TokenEndpointAuthenticationMethods.ToImmutableArray(), [Metadata.IntrospectionEndpointAuthMethodsSupported] = notification.IntrospectionEndpointAuthenticationMethods.ToImmutableArray(), [Metadata.RevocationEndpointAuthMethodsSupported] = notification.RevocationEndpointAuthenticationMethods.ToImmutableArray(), [Metadata.DeviceAuthorizationEndpointAuthMethodsSupported] = notification.DeviceAuthorizationEndpointAuthenticationMethods.ToImmutableArray(), [Metadata.PushedAuthorizationRequestEndpointAuthMethodsSupported] = notification.PushedAuthorizationEndpointAuthenticationMethods.ToImmutableArray(), [Metadata.RequirePushedAuthorizationRequests] = notification.RequirePushedAuthorizationRequests, [Metadata.TlsClientCertificateBoundAccessTokens] = notification.TlsClientCertificateBoundAccessTokens }; foreach (var metadata in notification.Metadata) { response.SetParameter(metadata.Key, metadata.Value); } context.Transaction.Response = response; static JsonObject CreateMtlsEndpointAliases(HandleConfigurationRequestContext context) { var node = new JsonObject(); if (context.MtlsDeviceAuthorizationEndpointAlias is not null) { node.Add(Metadata.DeviceAuthorizationEndpoint, context.MtlsDeviceAuthorizationEndpointAlias.AbsoluteUri); } if (context.MtlsIntrospectionEndpointAlias is not null) { node.Add(Metadata.IntrospectionEndpoint, context.MtlsIntrospectionEndpointAlias.AbsoluteUri); } if (context.MtlsPushedAuthorizationEndpointAlias is not null) { node.Add(Metadata.PushedAuthorizationRequestEndpoint, context.MtlsPushedAuthorizationEndpointAlias.AbsoluteUri); } if (context.MtlsRevocationEndpointAlias is not null) { node.Add(Metadata.RevocationEndpoint, context.MtlsRevocationEndpointAlias.AbsoluteUri); } if (context.MtlsTokenEndpointAlias is not null) { node.Add(Metadata.TokenEndpoint, context.MtlsTokenEndpointAlias.AbsoluteUri); } if (context.MtlsUserInfoEndpointAlias is not null) { node.Add(Metadata.UserInfoEndpoint, context.MtlsUserInfoEndpointAlias.AbsoluteUri); } return node; } } } /// /// Contains the logic responsible for processing configuration responses and invoking the corresponding event handlers. /// public sealed class ApplyConfigurationResponse : IOpenIddictServerHandler where TContext : BaseRequestContext { private readonly IOpenIddictServerDispatcher _dispatcher; public ApplyConfigurationResponse(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler>() .SetOrder(500_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(TContext context) { ArgumentNullException.ThrowIfNull(context); var notification = new ApplyConfigurationResponseContext(context.Transaction); await _dispatcher.DispatchAsync(notification); if (notification.IsRequestHandled) { context.HandleRequest(); return; } else if (notification.IsRequestSkipped) { context.SkipRequest(); return; } throw new InvalidOperationException(SR.GetResourceString(SR.ID0272)); } } /// /// Contains the logic responsible for attaching the issuer to the provider discovery document. /// public sealed class AttachIssuer : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(int.MaxValue - 100_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); context.Issuer = (context.Options.Issuer ?? context.BaseUri) switch { { IsAbsoluteUri: true } uri => uri, // Throw an exception if the issuer cannot be retrieved or is not valid. _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0496)) }; return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the endpoint URIs to the provider discovery document. /// public sealed class AttachEndpoints : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachIssuer.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); // Note: while OpenIddict allows specifying multiple endpoint URIs, the OAuth 2.0 // and OpenID Connect discovery specifications only allow a single URI per endpoint. context.AuthorizationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.AuthorizationEndpointUris.FirstOrDefault()); context.DeviceAuthorizationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.DeviceAuthorizationEndpointUris.FirstOrDefault()); context.EndSessionEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.EndSessionEndpointUris.FirstOrDefault()); context.IntrospectionEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.IntrospectionEndpointUris.FirstOrDefault()); context.JsonWebKeySetEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.JsonWebKeySetEndpointUris.FirstOrDefault()); context.MtlsDeviceAuthorizationEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.MtlsDeviceAuthorizationEndpointAliasUri); context.MtlsIntrospectionEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.MtlsIntrospectionEndpointAliasUri); context.MtlsPushedAuthorizationEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.MtlsPushedAuthorizationEndpointAliasUri); context.MtlsRevocationEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.MtlsRevocationEndpointAliasUri); context.MtlsTokenEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.MtlsTokenEndpointAliasUri); context.MtlsUserInfoEndpointAlias ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.MtlsUserInfoEndpointAliasUri); context.PushedAuthorizationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.PushedAuthorizationEndpointUris.FirstOrDefault()); context.RevocationEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.RevocationEndpointUris.FirstOrDefault()); context.TokenEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.TokenEndpointUris.FirstOrDefault()); context.UserInfoEndpoint ??= OpenIddictHelpers.CreateAbsoluteUri( context.BaseUri, context.Options.UserInfoEndpointUris.FirstOrDefault()); return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported grant types to the provider discovery document. /// public sealed class AttachGrantTypes : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachEndpoints.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); context.GrantTypes.UnionWith(context.Options.GrantTypes); return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported response types to the provider discovery document. /// public sealed class AttachResponseTypes : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachGrantTypes.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); context.ResponseTypes.UnionWith(context.Options.ResponseTypes); return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported response modes to the provider discovery document. /// public sealed class AttachResponseModes : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachResponseTypes.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); // Only include the response modes if at least one response type is returned. if (context.ResponseTypes.Count is 0) { return ValueTask.CompletedTask; } // Note: returning an access or identity token using the query response mode is explicitly disallowed. // // To ensure the query response mode is not returned unless at least one response type that doesn't // include id_token or token is included in the server configuration, a manual check is done here. if (context.Options.ResponseModes.Contains(ResponseModes.Query) && context.ResponseTypes .Select(static types => types.Split(Separators.Space, StringSplitOptions.None).ToHashSet(StringComparer.Ordinal)) .Any(static types => !types.Contains(ResponseTypes.IdToken) && !types.Contains(ResponseTypes.Token))) { context.ResponseModes.Add(ResponseModes.Query); } context.ResponseModes.UnionWith(context.Options.ResponseModes.Where( static mode => mode is not ResponseModes.Query)); return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported client /// authentication methods to the provider discovery document. /// public sealed class AttachClientAuthenticationMethods : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachResponseModes.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); // Note: "device_authorization_endpoint_auth_methods_supported" is not a standard parameter // but is supported by OpenIddict 4.3.0 and higher for consistency with the other endpoints. if (context.DeviceAuthorizationEndpoint is not null) { context.DeviceAuthorizationEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods); } if (context.IntrospectionEndpoint is not null) { context.IntrospectionEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods); } // Note: "pushed_authorization_request_endpoint_auth_methods_supported" is not a standard parameter // but is supported by OpenIddict 6.1.0 and higher for consistency with the other endpoints. if (context.PushedAuthorizationEndpoint is not null) { context.PushedAuthorizationEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods); } if (context.RevocationEndpoint is not null) { context.RevocationEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods); } if (context.TokenEndpoint is not null) { context.TokenEndpointAuthenticationMethods.UnionWith(context.Options.ClientAuthenticationMethods); } return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported /// code challenge methods to the provider discovery document. /// public sealed class AttachCodeChallengeMethods : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachClientAuthenticationMethods.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); // Only include the code challenge methods if the authorization code grant type is enabled. if (context.GrantTypes.Contains(GrantTypes.AuthorizationCode)) { context.CodeChallengeMethods.UnionWith(context.Options.CodeChallengeMethods); } return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported response types to the provider discovery document. /// public sealed class AttachScopes : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachCodeChallengeMethods.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); context.Scopes.UnionWith(context.Options.Scopes); return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported claims to the provider discovery document. /// public sealed class AttachClaims : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachScopes.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); context.Claims.UnionWith(context.Options.Claims); return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported subject types to the provider discovery document. /// public sealed class AttachSubjectTypes : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachClaims.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); context.SubjectTypes.UnionWith(context.Options.SubjectTypes); return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported prompt values to the provider discovery document. /// public sealed class AttachPromptValues : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachSubjectTypes.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); context.PromptValues.UnionWith(context.Options.PromptValues); return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the supported signing algorithms to the provider discovery document. /// public sealed class AttachSigningAlgorithms : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachPromptValues.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); foreach (var credentials in context.Options.SigningCredentials) { // Try to resolve the JWA algorithm short name. var algorithm = credentials.Algorithm switch { #if SUPPORTS_ECDSA SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature => SecurityAlgorithms.EcdsaSha256, SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature => SecurityAlgorithms.EcdsaSha384, SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature => SecurityAlgorithms.EcdsaSha512, #endif SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature => SecurityAlgorithms.RsaSha256, SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature => SecurityAlgorithms.RsaSha384, SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature => SecurityAlgorithms.RsaSha512, SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature => SecurityAlgorithms.RsaSsaPssSha256, SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature => SecurityAlgorithms.RsaSsaPssSha384, SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature => SecurityAlgorithms.RsaSsaPssSha512, _ => null }; // If the algorithm cannot be resolved, ignore it. if (string.IsNullOrEmpty(algorithm)) { continue; } context.IdTokenSigningAlgorithms.Add(algorithm); } return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching the security requirements to the provider discovery document. /// public sealed class AttachSecurityRequirements : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachSigningAlgorithms.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); context.RequirePushedAuthorizationRequests = context.Options.RequirePushedAuthorizationRequests; context.TlsClientCertificateBoundAccessTokens = context.Options.UseClientCertificateBoundAccessTokens; return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for attaching additional metadata to the provider discovery document. /// public sealed class AttachAdditionalMetadata : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachSecurityRequirements.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleConfigurationRequestContext context) { ArgumentNullException.ThrowIfNull(context); // Note: these optional features are not yet supported by OpenIddict, // so "false" is returned to encourage clients not to use them. context.Metadata[Metadata.ClaimsParameterSupported] = false; context.Metadata[Metadata.RequestParameterSupported] = false; context.Metadata[Metadata.RequestUriParameterSupported] = false; // As of 3.2.0, OpenIddict automatically returns an "iss" parameter containing its identity as // part of authorization responses to help clients mitigate mix-up attacks. For more information, // see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-iss-auth-resp-05. context.Metadata[Metadata.AuthorizationResponseIssParameterSupported] = true; return ValueTask.CompletedTask; } } /// /// Contains the logic responsible for extracting JSON Web Key Set requests and invoking the corresponding event handlers. /// public sealed class ExtractJsonWebKeySetRequest : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ExtractJsonWebKeySetRequest(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(100_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { ArgumentNullException.ThrowIfNull(context); var notification = new ExtractJsonWebKeySetRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); 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 (notification.Request is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0038)); } context.Logger.LogInformation(6068, SR.GetResourceString(SR.ID6068), notification.Request); } } /// /// Contains the logic responsible for validating JSON Web Key Set requests and invoking the corresponding event handlers. /// public sealed class ValidateJsonWebKeySetRequest : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ValidateJsonWebKeySetRequest(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ExtractJsonWebKeySetRequest.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { ArgumentNullException.ThrowIfNull(context); var notification = new ValidateJsonWebKeySetRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); 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; } context.Logger.LogInformation(6069, SR.GetResourceString(SR.ID6069)); } } /// /// Contains the logic responsible for handling JSON Web Key Set requests and invoking the corresponding event handlers. /// public sealed class HandleJsonWebKeySetRequest : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public HandleJsonWebKeySetRequest(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ValidateJsonWebKeySetRequest.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessRequestContext context) { ArgumentNullException.ThrowIfNull(context); var notification = new HandleJsonWebKeySetRequestContext(context.Transaction); await _dispatcher.DispatchAsync(notification); 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; } using var stream = new MemoryStream(); using var writer = new Utf8JsonWriter(stream); writer.WriteStartArray(); foreach (var key in notification.Keys) { // Ensure a key type has been provided. // See https://tools.ietf.org/html/rfc7517#section-4.1 if (string.IsNullOrEmpty(key.Kty)) { context.Logger.LogWarning(6070, SR.GetResourceString(SR.ID6070), JsonWebKeyParameterNames.Kty); continue; } writer.WriteStartObject(); if (!string.IsNullOrEmpty(key.Kid)) writer.WriteString(JsonWebKeyParameterNames.Kid, key.Kid); if (!string.IsNullOrEmpty(key.Use)) writer.WriteString(JsonWebKeyParameterNames.Use, key.Use); if (!string.IsNullOrEmpty(key.Kty)) writer.WriteString(JsonWebKeyParameterNames.Kty, key.Kty); if (!string.IsNullOrEmpty(key.Alg)) writer.WriteString(JsonWebKeyParameterNames.Alg, key.Alg); if (!string.IsNullOrEmpty(key.Crv)) writer.WriteString(JsonWebKeyParameterNames.Crv, key.Crv); if (!string.IsNullOrEmpty(key.E)) writer.WriteString(JsonWebKeyParameterNames.E, key.E); if (!string.IsNullOrEmpty(key.N)) writer.WriteString(JsonWebKeyParameterNames.N, key.N); if (!string.IsNullOrEmpty(key.X)) writer.WriteString(JsonWebKeyParameterNames.X, key.X); if (!string.IsNullOrEmpty(key.Y)) writer.WriteString(JsonWebKeyParameterNames.Y, key.Y); if (!string.IsNullOrEmpty(key.X5t)) writer.WriteString(JsonWebKeyParameterNames.X5t, key.X5t); if (!string.IsNullOrEmpty(key.X5u)) writer.WriteString(JsonWebKeyParameterNames.X5u, key.X5u); if (key.KeyOps.Count is not 0) { writer.WritePropertyName(JsonWebKeyParameterNames.KeyOps); writer.WriteStartArray(); for (var index = 0; index < key.KeyOps.Count; index++) { writer.WriteStringValue(key.KeyOps[index]); } writer.WriteEndArray(); } if (key.X5c.Count is not 0) { writer.WritePropertyName(JsonWebKeyParameterNames.X5c); writer.WriteStartArray(); for (var index = 0; index < key.X5c.Count; index++) { writer.WriteStringValue(key.X5c[index]); } writer.WriteEndArray(); } writer.WriteEndObject(); } writer.WriteEndArray(); writer.Flush(); stream.Seek(0L, SeekOrigin.Begin); using var document = JsonDocument.Parse(stream); // Note: AddParameter() is used here to ensure the mandatory "keys" node // is returned to the caller, even if the key set doesn't expose any key. // See https://tools.ietf.org/html/rfc7517#section-5 for more information. var response = new OpenIddictResponse(); response.AddParameter(Parameters.Keys, document.RootElement.Clone()); context.Transaction.Response = response; } } /// /// Contains the logic responsible for processing JSON Web Key Set responses and invoking the corresponding event handlers. /// public sealed class ApplyJsonWebKeySetResponse : IOpenIddictServerHandler where TContext : BaseRequestContext { private readonly IOpenIddictServerDispatcher _dispatcher; public ApplyJsonWebKeySetResponse(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler>() .SetOrder(500_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(TContext context) { ArgumentNullException.ThrowIfNull(context); var notification = new ApplyJsonWebKeySetResponseContext(context.Transaction); await _dispatcher.DispatchAsync(notification); if (notification.IsRequestHandled) { context.HandleRequest(); return; } else if (notification.IsRequestSkipped) { context.SkipRequest(); return; } throw new InvalidOperationException(SR.GetResourceString(SR.ID0039)); } } /// /// Contains the logic responsible for attaching the signing keys to the JSON Web Key Set document. /// public sealed class AttachSigningKeys : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(int.MinValue + 100_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(HandleJsonWebKeySetRequestContext context) { ArgumentNullException.ThrowIfNull(context); foreach (var credentials in context.Options.SigningCredentials) { #if SUPPORTS_ECDSA if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) { context.Logger.LogInformation(6071, SR.GetResourceString(SR.ID6071), credentials.Key.GetType().Name); continue; } #else if (!credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) && !credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256)) { context.Logger.LogInformation(6072, SR.GetResourceString(SR.ID6072), credentials.Key.GetType().Name); continue; } #endif var key = new JsonWebKey { Use = JsonWebKeyUseNames.Sig, // Resolve the JWA identifier from the algorithm specified in the credentials. Alg = credentials.Algorithm switch { #if SUPPORTS_ECDSA SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature => SecurityAlgorithms.EcdsaSha256, SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature => SecurityAlgorithms.EcdsaSha384, SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature => SecurityAlgorithms.EcdsaSha512, #endif SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature => SecurityAlgorithms.RsaSha256, SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature => SecurityAlgorithms.RsaSha384, SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature => SecurityAlgorithms.RsaSha512, SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature => SecurityAlgorithms.RsaSsaPssSha256, SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature => SecurityAlgorithms.RsaSsaPssSha384, SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature => SecurityAlgorithms.RsaSsaPssSha512, _ => null }, // Use the key identifier specified in the signing credentials. Kid = credentials.Kid }; if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSha256) || credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.RsaSsaPssSha256)) { // Note: IdentityModel 5 doesn't expose a method allowing to retrieve the underlying algorithm // from a generic asymmetric security key. To work around this limitation, try to cast // the security key to the built-in IdentityModel types to extract the required RSA instance. // See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/395. var parameters = credentials.Key switch { X509SecurityKey { PublicKey: RSA algorithm } => algorithm.ExportParameters(includePrivateParameters: false), RsaSecurityKey { Rsa: RSA algorithm } => algorithm.ExportParameters(includePrivateParameters: false), RsaSecurityKey { Parameters: RSAParameters value } => value, _ => (RSAParameters?) null }; if (parameters is null) { context.Logger.LogWarning(6073, SR.GetResourceString(SR.ID6073), credentials.Key.GetType().Name); continue; } Debug.Assert(parameters.Value.Exponent is not null && parameters.Value.Modulus is not null, SR.GetResourceString(SR.ID4003)); key.Kty = JsonWebAlgorithmsKeyTypes.RSA; // Note: both E and N must be base64url-encoded. // See https://tools.ietf.org/html/rfc7518#section-6.3.1.1. key.E = Base64UrlEncoder.Encode(parameters.Value.Exponent); key.N = Base64UrlEncoder.Encode(parameters.Value.Modulus); } #if SUPPORTS_ECDSA else if (credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha256) || credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha384) || credentials.Key.IsSupportedAlgorithm(SecurityAlgorithms.EcdsaSha512)) { var parameters = credentials.Key switch { X509SecurityKey { PublicKey: ECDsa algorithm } => algorithm.ExportParameters(includePrivateParameters: false), ECDsaSecurityKey { ECDsa: ECDsa algorithm } => algorithm.ExportParameters(includePrivateParameters: false), _ => (ECParameters?) null }; if (parameters is null) { context.Logger.LogWarning(6074, SR.GetResourceString(SR.ID6074), credentials.Key.GetType().Name); continue; } var curve = OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP256) ? JsonWebKeyECTypes.P256 : OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP384) ? JsonWebKeyECTypes.P384 : OpenIddictHelpers.IsEcCurve(parameters.Value, ECCurve.NamedCurves.nistP521) ? JsonWebKeyECTypes.P521 : null; if (string.IsNullOrEmpty(curve)) { context.Logger.LogWarning(6167, SR.GetResourceString(SR.ID6167), credentials.Key.GetType().Name); continue; } Debug.Assert(parameters.Value.Q.X is not null && parameters.Value.Q.Y is not null, SR.GetResourceString(SR.ID4004)); Debug.Assert(parameters.Value.Curve.Oid is not null, SR.GetResourceString(SR.ID4011)); Debug.Assert(parameters.Value.Curve.IsNamed, SR.GetResourceString(SR.ID4005)); key.Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve; key.Crv = curve; // Note: both X and Y must be base64url-encoded. // See https://tools.ietf.org/html/rfc7518#section-6.2.1.2. key.X = Base64UrlEncoder.Encode(parameters.Value.Q.X); key.Y = Base64UrlEncoder.Encode(parameters.Value.Q.Y); } #endif // If the signing key is embedded in a X.509 certificate, set // the x5t and x5c parameters using the certificate details. var certificate = (credentials.Key as X509SecurityKey)?.Certificate; if (certificate is not null) { // x5t must be base64url-encoded. // See https://tools.ietf.org/html/rfc7517#section-4.8. key.X5t = Base64UrlEncoder.Encode(certificate.GetCertHash()); // x5t#S256 must be base64url-encoded. // See https://tools.ietf.org/html/rfc7517#section-4.9. key.X5tS256 = Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash(certificate.RawData)); // Unlike E or N, the certificates contained in x5c // must be base64-encoded and not base64url-encoded. // See https://tools.ietf.org/html/rfc7517#section-4.7. key.X5c.Add(Convert.ToBase64String(certificate.RawData)); } context.Keys.Add(key); } return ValueTask.CompletedTask; } } } }