/* * 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; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using OpenIddict.Abstractions; using static OpenIddict.Abstractions.OpenIddictConstants; using static OpenIddict.Server.OpenIddictServerEvents; using static OpenIddict.Server.OpenIddictServerHandlerFilters; using SR = OpenIddict.Abstractions.OpenIddictResources; namespace OpenIddict.Server { [EditorBrowsable(EditorBrowsableState.Never)] public static partial class OpenIddictServerHandlers { public static ImmutableArray DefaultHandlers { get; } = ImmutableArray.Create( /* * Authentication processing: */ ValidateAuthenticationDemand.Descriptor, EvaluateValidatedTokens.Descriptor, ResolveValidatedTokens.Descriptor, ValidateAccessToken.Descriptor, ValidateAuthorizationCode.Descriptor, ValidateDeviceCode.Descriptor, ValidateGenericToken.Descriptor, ValidateIdentityToken.Descriptor, ValidateRefreshToken.Descriptor, ValidateUserCode.Descriptor, /* * Challenge processing: */ ValidateChallengeDemand.Descriptor, AttachDefaultChallengeError.Descriptor, RejectDeviceCodeEntry.Descriptor, RejectUserCodeEntry.Descriptor, /* * Sign-in processing: */ ValidateSignInDemand.Descriptor, RestoreInternalClaims.Descriptor, AttachDefaultScopes.Descriptor, AttachDefaultPresenters.Descriptor, InferResources.Descriptor, EvaluateGeneratedTokens.Descriptor, AttachAuthorization.Descriptor, PrepareAccessTokenPrincipal.Descriptor, PrepareAuthorizationCodePrincipal.Descriptor, PrepareDeviceCodePrincipal.Descriptor, PrepareRefreshTokenPrincipal.Descriptor, PrepareIdentityTokenPrincipal.Descriptor, PrepareUserCodePrincipal.Descriptor, RedeemTokenEntry.Descriptor, GenerateAccessToken.Descriptor, GenerateAuthorizationCode.Descriptor, GenerateDeviceCode.Descriptor, GenerateRefreshToken.Descriptor, AttachDeviceCodeIdentifier.Descriptor, UpdateReferenceDeviceCodeEntry.Descriptor, AttachTokenDigests.Descriptor, GenerateUserCode.Descriptor, GenerateIdentityToken.Descriptor, AttachSignInParameters.Descriptor, /* * Sign-out processing: */ ValidateSignOutDemand.Descriptor, AttachSignOutParameters.Descriptor) .AddRange(Authentication.DefaultHandlers) .AddRange(Device.DefaultHandlers) .AddRange(Discovery.DefaultHandlers) .AddRange(Exchange.DefaultHandlers) .AddRange(Introspection.DefaultHandlers) .AddRange(Protection.DefaultHandlers) .AddRange(Revocation.DefaultHandlers) .AddRange(Session.DefaultHandlers) .AddRange(Userinfo.DefaultHandlers); /// /// Contains the logic responsible of rejecting authentication demands made from unsupported endpoints. /// public class ValidateAuthenticationDemand : 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(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } switch (context.EndpointType) { case OpenIddictServerEndpointType.Authorization: case OpenIddictServerEndpointType.Introspection: case OpenIddictServerEndpointType.Logout: case OpenIddictServerEndpointType.Revocation: case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType(): case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType(): case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType(): case OpenIddictServerEndpointType.Userinfo: case OpenIddictServerEndpointType.Verification: return default; case OpenIddictServerEndpointType.Token: throw new InvalidOperationException(SR.GetResourceString(SR.ID0001)); default: throw new InvalidOperationException(SR.GetResourceString(SR.ID0002)); } } } /// /// Contains the logic responsible of selecting the token types that should be validated. /// public class EvaluateValidatedTokens : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(ValidateAuthenticationDemand.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } (context.ValidateAccessToken, context.RequireAccessToken) = context.EndpointType switch { // The userinfo endpoint requires sending a valid access token. OpenIddictServerEndpointType.Userinfo => (true, true), _ => (false, false) }; (context.ValidateAuthorizationCode, context.RequireAuthorizationCode) = context.EndpointType switch { // The authorization code grant requires sending a valid authorization code. OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() => (true, true), _ => (false, false) }; (context.ValidateDeviceCode, context.RequireDeviceCode) = context.EndpointType switch { // The device code grant requires sending a valid device code. OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() => (true, true), _ => (false, false) }; (context.ValidateGenericToken, context.RequireGenericToken) = context.EndpointType switch { // Tokens received by the introspection and revocation endpoints can be of any type. // Additional token type filtering is made by the endpoint themselves, if needed. OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation => (true, true), _ => (false, false) }; (context.ValidateIdentityToken, context.RequireIdentityToken) = context.EndpointType switch { // The identity token received by the authorization and logout // endpoints are not required and serve as optional hints. OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout => (true, false), _ => (false, false) }; (context.ValidateRefreshToken, context.RequireRefreshToken) = context.EndpointType switch { // The refresh token grant requires sending a valid refresh token. OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() => (true, true), _ => (false, false) }; (context.ValidateUserCode, context.RequireUserCode) = context.EndpointType switch { // Note: the verification endpoint can be accessed without specifying a // user code (that can be later set by the user using a form, for instance). OpenIddictServerEndpointType.Verification => (true, false), _ => (false, false) }; return default; } } /// /// Contains the logic responsible of resolving the token from the incoming request. /// public class ResolveValidatedTokens : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(EvaluateValidatedTokens.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } context.AccessToken = context.EndpointType switch { OpenIddictServerEndpointType.Userinfo when context.ValidateAccessToken => context.Request.AccessToken, _ => null }; context.AuthorizationCode = context.EndpointType switch { OpenIddictServerEndpointType.Token when context.ValidateAuthorizationCode => context.Request.Code, _ => null }; context.DeviceCode = context.EndpointType switch { OpenIddictServerEndpointType.Token when context.ValidateDeviceCode => context.Request.DeviceCode, _ => null }; (context.GenericToken, context.GenericTokenTypeHint) = context.EndpointType switch { OpenIddictServerEndpointType.Introspection or OpenIddictServerEndpointType.Revocation when context.ValidateGenericToken => (context.Request.Token, context.Request.TokenTypeHint), _ => (null, null) }; context.IdentityToken = context.EndpointType switch { OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout when context.ValidateIdentityToken => context.Request.IdTokenHint, _ => null }; context.RefreshToken = context.EndpointType switch { OpenIddictServerEndpointType.Token when context.ValidateRefreshToken => context.Request.RefreshToken, _ => null }; context.UserCode = context.EndpointType switch { OpenIddictServerEndpointType.Verification when context.ValidateUserCode => context.Request.UserCode, _ => null }; return default; } } /// /// Contains the logic responsible of validating the access token resolved from the context. /// public class ValidateAccessToken : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ValidateAccessToken(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ResolveValidatedTokens.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.AccessTokenPrincipal is not null) { return; } if (string.IsNullOrEmpty(context.AccessToken)) { if (context.RequireAccessToken) { context.Reject( error: Errors.MissingToken, description: SR.GetResourceString(SR.ID2000), uri: SR.FormatID8000(SR.ID2000)); return; } return; } var notification = new ValidateTokenContext(context.Transaction) { Token = context.AccessToken, ValidTokenTypes = { TokenTypeHints.AccessToken } }; 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.AccessTokenPrincipal = notification.Principal; } } /// /// Contains the logic responsible of validating the authorization code resolved from the context. /// public class ValidateAuthorizationCode : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ValidateAuthorizationCode(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ValidateAccessToken.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.AuthorizationCodePrincipal is not null) { return; } if (string.IsNullOrEmpty(context.AuthorizationCode)) { if (context.RequireAuthorizationCode) { context.Reject( error: Errors.MissingToken, description: SR.GetResourceString(SR.ID2000), uri: SR.FormatID8000(SR.ID2000)); return; } return; } var notification = new ValidateTokenContext(context.Transaction) { Token = context.AuthorizationCode, ValidTokenTypes = { TokenTypeHints.AuthorizationCode } }; 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.AuthorizationCodePrincipal = notification.Principal; } } /// /// Contains the logic responsible of validating the device code resolved from the context. /// public class ValidateDeviceCode : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ValidateDeviceCode(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ValidateAuthorizationCode.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.DeviceCodePrincipal is not null) { return; } if (string.IsNullOrEmpty(context.DeviceCode)) { if (context.RequireDeviceCode) { context.Reject( error: Errors.MissingToken, description: SR.GetResourceString(SR.ID2000), uri: SR.FormatID8000(SR.ID2000)); return; } return; } var notification = new ValidateTokenContext(context.Transaction) { Token = context.DeviceCode, ValidTokenTypes = { TokenTypeHints.DeviceCode } }; 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.DeviceCodePrincipal = notification.Principal; } } /// /// Contains the logic responsible of validating tokens of unknown types resolved from the context. /// public class ValidateGenericToken : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ValidateGenericToken(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ValidateDeviceCode.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.GenericTokenPrincipal is not null) { return; } if (string.IsNullOrEmpty(context.GenericToken)) { if (context.RequireGenericToken) { context.Reject( error: Errors.MissingToken, description: SR.GetResourceString(SR.ID2000), uri: SR.FormatID8000(SR.ID2000)); return; } return; } var notification = new ValidateTokenContext(context.Transaction) { Token = context.GenericToken, TokenTypeHint = context.GenericTokenTypeHint, // By default, only access tokens and refresh tokens can be introspected/revoked but // tokens received by the introspection and revocation endpoints can be of any type. // // Additional token type filtering is made by the endpoint themselves, if needed. // As such, the valid token types list is deliberately left empty in this case. ValidTokenTypes = { } }; 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.GenericTokenPrincipal = notification.Principal; } } /// /// Contains the logic responsible of validating the identity token resolved from the context. /// public class ValidateIdentityToken : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ValidateIdentityToken(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ValidateGenericToken.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.IdentityTokenPrincipal is not null) { return; } if (string.IsNullOrEmpty(context.IdentityToken)) { if (context.RequireIdentityToken) { context.Reject( error: Errors.MissingToken, description: SR.GetResourceString(SR.ID2000), uri: SR.FormatID8000(SR.ID2000)); return; } return; } var notification = new ValidateTokenContext(context.Transaction) { // Don't validate the lifetime of id_tokens used as id_token_hints. DisableLifetimeValidation = context.EndpointType is OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Logout, Token = context.IdentityToken, ValidTokenTypes = { TokenTypeHints.IdToken } }; 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.IdentityTokenPrincipal = notification.Principal; } } /// /// Contains the logic responsible of validating the refresh token resolved from the context. /// public class ValidateRefreshToken : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ValidateRefreshToken(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ValidateIdentityToken.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.RefreshTokenPrincipal is not null) { return; } if (string.IsNullOrEmpty(context.RefreshToken)) { if (context.RequireRefreshToken) { context.Reject( error: Errors.MissingToken, description: SR.GetResourceString(SR.ID2000), uri: SR.FormatID8000(SR.ID2000)); return; } return; } var notification = new ValidateTokenContext(context.Transaction) { Token = context.RefreshToken, ValidTokenTypes = { TokenTypeHints.RefreshToken } }; 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.RefreshTokenPrincipal = notification.Principal; } } /// /// Contains the logic responsible of validating the user code resolved from the context. /// public class ValidateUserCode : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public ValidateUserCode(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(ValidateRefreshToken.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessAuthenticationContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.UserCodePrincipal is not null) { return; } if (string.IsNullOrEmpty(context.UserCode)) { if (context.RequireUserCode) { context.Reject( error: Errors.MissingToken, description: SR.GetResourceString(SR.ID2000), uri: SR.FormatID8000(SR.ID2000)); return; } return; } var notification = new ValidateTokenContext(context.Transaction) { Token = context.UserCode, ValidTokenTypes = { TokenTypeHints.UserCode } }; 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.UserCodePrincipal = notification.Principal; } } /// /// Contains the logic responsible of rejecting challenge demands made from unsupported endpoints. /// public class ValidateChallengeDemand : 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(ProcessChallengeContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Token or OpenIddictServerEndpointType.Userinfo or OpenIddictServerEndpointType.Verification)) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)); } return default; } } /// /// Contains the logic responsible of ensuring that the challenge response contains an appropriate error. /// public class AttachDefaultChallengeError : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(ValidateChallengeDemand.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessChallengeContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } context.Response.Error ??= context.EndpointType switch { OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification => Errors.AccessDenied, OpenIddictServerEndpointType.Token => Errors.InvalidGrant, OpenIddictServerEndpointType.Userinfo => Errors.InsufficientAccess, _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) }; context.Response.ErrorDescription ??= context.EndpointType switch { OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification => SR.GetResourceString(SR.ID2015), OpenIddictServerEndpointType.Token => SR.GetResourceString(SR.ID2024), OpenIddictServerEndpointType.Userinfo => SR.GetResourceString(SR.ID2025), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) }; context.Response.ErrorUri ??= context.EndpointType switch { OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Verification => SR.FormatID8000(SR.ID2015), OpenIddictServerEndpointType.Token => SR.FormatID8000(SR.ID2024), OpenIddictServerEndpointType.Userinfo => SR.FormatID8000(SR.ID2025), _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0006)) }; return default; } } /// /// Contains the logic responsible of rejecting the device code entry associated with the user code. /// Note: this handler is not used when the degraded mode is enabled. /// public class RejectDeviceCodeEntry : IOpenIddictServerHandler { private readonly IOpenIddictTokenManager _tokenManager; public RejectDeviceCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); public RejectDeviceCodeEntry(IOpenIddictTokenManager tokenManager) => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() .UseScopedHandler() .SetOrder(AttachDefaultChallengeError.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessChallengeContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.EndpointType != OpenIddictServerEndpointType.Verification) { return; } var notification = context.Transaction.GetProperty( typeof(ProcessAuthenticationContext).FullName!) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); Debug.Assert(notification.UserCodePrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Extract the device code identifier from the user code principal. var identifier = notification.UserCodePrincipal.GetClaim(Claims.Private.DeviceCodeId); if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0008)); } var token = await _tokenManager.FindByIdAsync(identifier); if (token is not null) { await _tokenManager.TryRejectAsync(token); } } } /// /// Contains the logic responsible of rejecting the user code entry, if applicable. /// Note: this handler is not used when the degraded mode is enabled. /// public class RejectUserCodeEntry : IOpenIddictServerHandler { private readonly IOpenIddictTokenManager _tokenManager; public RejectUserCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); public RejectUserCodeEntry(IOpenIddictTokenManager tokenManager) => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() .UseScopedHandler() .SetOrder(RejectDeviceCodeEntry.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessChallengeContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.EndpointType != OpenIddictServerEndpointType.Verification) { return; } var notification = context.Transaction.GetProperty( typeof(ProcessAuthenticationContext).FullName!) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); Debug.Assert(notification.UserCodePrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Extract the device code identifier from the authentication principal. var identifier = notification.UserCodePrincipal.GetTokenId(); if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0009)); } var token = await _tokenManager.FindByIdAsync(identifier); if (token is not null) { await _tokenManager.TryRejectAsync(token); } } } /// /// Contains the logic responsible of ensuring that the sign-in demand /// is compatible with the type of the endpoint that handled the request. /// public class ValidateSignInDemand : 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(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.EndpointType is not (OpenIddictServerEndpointType.Authorization or OpenIddictServerEndpointType.Device or OpenIddictServerEndpointType.Token or OpenIddictServerEndpointType.Verification)) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0010)); } if (context.Principal is not { Identity: ClaimsIdentity }) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0011)); } // Note: sign-in operations triggered from the device endpoint can't be associated to specific users // as users' identity is not known until they reach the verification endpoint and validate the user code. // As such, the principal used in this case cannot contain an authenticated identity or a subject claim. if (context.EndpointType == OpenIddictServerEndpointType.Device) { if (context.Principal.Identity.IsAuthenticated) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0012)); } if (!string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject))) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0013)); } } else { if (!context.Principal.Identity.IsAuthenticated) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0014)); } if (string.IsNullOrEmpty(context.Principal.GetClaim(Claims.Subject))) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0015)); } } return default; } } /// /// Contains the logic responsible of re-attaching internal claims to the authentication principal. /// public class RestoreInternalClaims : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(ValidateSignInDemand.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); switch (context.EndpointType) { case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType(): case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType(): case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType(): case OpenIddictServerEndpointType.Verification: break; default: return default; } var identity = (ClaimsIdentity) context.Principal.Identity; var notification = context.Transaction.GetProperty( typeof(ProcessAuthenticationContext).FullName!) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); var principal = context.EndpointType switch { OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType() => notification.AuthorizationCodePrincipal, OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType() => notification.DeviceCodePrincipal, OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() => notification.RefreshTokenPrincipal, OpenIddictServerEndpointType.Verification => notification.UserCodePrincipal, _ => null }; if (principal is null) { return default; } // Restore the internal claims resolved from the token. foreach (var claims in principal.Claims .Where(claim => claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) .GroupBy(claim => claim.Type)) { // If the specified principal already contains one claim of the iterated type, ignore them. if (context.Principal.Claims.Any(claim => claim.Type == claims.Key)) { continue; } // When the request is a verification request, don't flow the scopes from the user code. if (context.EndpointType == OpenIddictServerEndpointType.Verification && string.Equals(claims.Key, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase)) { continue; } identity.AddClaims(claims); } return default; } } /// /// Contains the logic responsible of attaching default scopes to the authentication principal. /// public class AttachDefaultScopes : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(RestoreInternalClaims.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Always include the "openid" scope when the developer doesn't explicitly call SetScopes. // Note: the application is allowed to specify a different "scopes": in this case, // don't replace the "scopes" property stored in the authentication ticket. if (!context.Principal.HasClaim(Claims.Private.Scope) && context.Request.HasScope(Scopes.OpenId)) { context.Principal.SetScopes(Scopes.OpenId); } return default; } } /// /// Contains the logic responsible of attaching default presenters to the authentication principal. /// public class AttachDefaultPresenters : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachDefaultScopes.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Add the validated client_id to the list of authorized presenters, // unless the presenters were explicitly set by the developer. if (!context.Principal.HasClaim(Claims.Private.Presenter) && !string.IsNullOrEmpty(context.ClientId)) { context.Principal.SetPresenters(context.ClientId); } return default; } } /// /// Contains the logic responsible of inferring resources from the audience claims if necessary. /// public class InferResources : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(AttachDefaultPresenters.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // When a "resources" property cannot be found in the ticket, infer it from the "audiences" property. if (context.Principal.HasClaim(Claims.Private.Audience) && !context.Principal.HasClaim(Claims.Private.Resource)) { context.Principal.SetResources(context.Principal.GetAudiences()); } // Reset the audiences collection, as it's later set, based on the token type. context.Principal.SetAudiences(ImmutableArray.Create()); return default; } } /// /// Contains the logic responsible of selecting the token types that /// should be generated and optionally returned in the response. /// public class EvaluateGeneratedTokens : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(InferResources.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); (context.GenerateAccessToken, context.IncludeAccessToken) = context.EndpointType switch { // For authorization requests, generate and return an access token // if a response type containing the "token" value was specified. OpenIddictServerEndpointType.Authorization when context.Request.HasResponseType(ResponseTypes.Token) => (true, true), // For token requests, always generate and return an access token. OpenIddictServerEndpointType.Token => (true, true), _ => (false, false) }; (context.GenerateAuthorizationCode, context.IncludeAuthorizationCode) = context.EndpointType switch { // For authorization requests, generate and return an authorization code // if a response type containing the "code" value was specified. OpenIddictServerEndpointType.Authorization when context.Request.HasResponseType(ResponseTypes.Code) => (true, true), _ => (false, false) }; (context.GenerateDeviceCode, context.IncludeDeviceCode) = context.EndpointType switch { // For device requests, always generate and return a device code. OpenIddictServerEndpointType.Device => (true, true), // Note: a device code is not directly returned by the verification endpoint (that generally // returns an empty response or redirects the user agent to another page), but a device code // must be generated to replace the payload of the device code initially returned to the client. // In this case, the device code is not returned as part of the response but persisted in the DB. OpenIddictServerEndpointType.Verification => (true, false), _ => (false, false) }; (context.GenerateIdentityToken, context.IncludeIdentityToken) = context.EndpointType switch { // For authorization requests, generate and return an identity token if a response type // containing code was specified and if the openid scope was explicitly or implicitly granted. OpenIddictServerEndpointType.Authorization when context.Principal.HasScope(Scopes.OpenId) && context.Request.HasResponseType(ResponseTypes.IdToken) => (true, true), // For token requests, only generate and return an identity token if the openid scope was granted. OpenIddictServerEndpointType.Token when context.Principal.HasScope(Scopes.OpenId) => (true, true), _ => (false, false) }; (context.GenerateRefreshToken, context.IncludeRefreshToken) = context.EndpointType switch { // For token requests, allow a refresh token to be returned // if the special offline_access protocol scope was granted. OpenIddictServerEndpointType.Token when context.Principal.HasScope(Scopes.OfflineAccess) => (true, true), _ => (false, false) }; (context.GenerateUserCode, context.IncludeUserCode) = context.EndpointType switch { // Only generate and return a user code if the request is a device authorization request. OpenIddictServerEndpointType.Device => (true, true), _ => (false, false) }; return default; } } /// /// Contains the logic responsible of creating an ad-hoc authorization, if necessary. /// Note: this handler is not used when the degraded mode is enabled. /// public class AttachAuthorization : IOpenIddictServerHandler { private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictAuthorizationManager _authorizationManager; public AttachAuthorization() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); public AttachAuthorization( IOpenIddictApplicationManager applicationManager, IOpenIddictAuthorizationManager authorizationManager) { _applicationManager = applicationManager; _authorizationManager = authorizationManager; } /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() .UseScopedHandler() .SetOrder(EvaluateGeneratedTokens.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // If no authorization code, device code or refresh token is returned, don't create an authorization. if (!context.GenerateAuthorizationCode && !context.GenerateDeviceCode && !context.GenerateRefreshToken) { return; } // If an authorization identifier was explicitly specified, don't create an ad-hoc authorization. if (!string.IsNullOrEmpty(context.Principal.GetAuthorizationId())) { return; } var descriptor = new OpenIddictAuthorizationDescriptor { CreationDate = DateTimeOffset.UtcNow, Principal = context.Principal, Status = Statuses.Valid, Subject = context.Principal.GetClaim(Claims.Subject), Type = AuthorizationTypes.AdHoc }; descriptor.Scopes.UnionWith(context.Principal.GetScopes()); // If the client application is known, associate it to the authorization. if (!string.IsNullOrEmpty(context.Request.ClientId)) { var application = await _applicationManager.FindByClientIdAsync(context.Request.ClientId); if (application is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0017)); } descriptor.ApplicationId = await _applicationManager.GetIdAsync(application); } var authorization = await _authorizationManager.CreateAsync(descriptor); if (authorization is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0018)); } var identifier = await _authorizationManager.GetIdAsync(authorization); if (string.IsNullOrEmpty(context.Request.ClientId)) { context.Logger.LogInformation(SR.GetResourceString(SR.ID6007), identifier); } else { context.Logger.LogInformation(SR.GetResourceString(SR.ID6008), context.Request.ClientId, identifier); } // Attach the unique identifier of the ad hoc authorization to the authentication principal // so that it is attached to all the derived tokens, allowing batched revocations support. context.Principal.SetAuthorizationId(identifier); } } /// /// Contains the logic responsible of preparing and attaching the claims principal /// used to generate the access token, if one is going to be returned. /// public class PrepareAccessTokenPrincipal : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() .SetOrder(AttachAuthorization.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Create a new principal containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). var principal = context.Principal.Clone(claim => { // Never exclude the subject and authorization identifier claims. if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase)) { return true; } // Never exclude the presenters and scope private claims. if (string.Equals(claim.Type, Claims.Private.Presenter, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.Scope, StringComparison.OrdinalIgnoreCase)) { return true; } // Never include the public or internal token identifiers to ensure the identifiers // that are automatically inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { return false; } // Never include the creation and expiration dates that are automatically // inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { return false; } // Always exclude private claims, whose values must generally be kept secret. if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) { return false; } // Claims whose destination is not explicitly referenced or doesn't // contain "access_token" are not included in the access token. if (!claim.HasDestination(Destinations.AccessToken)) { context.Logger.LogDebug(SR.GetResourceString(SR.ID6009), claim.Type); return false; } return true; }); // Remove the destinations from the claim properties. foreach (var claim in principal.Claims) { claim.Properties.Remove(Properties.Destinations); } principal.SetCreationDate(DateTimeOffset.UtcNow); var lifetime = context.Principal.GetAccessTokenLifetime() ?? context.Options.AccessTokenLifetime; if (lifetime.HasValue) { principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); } // Set the audiences based on the resource claims stored in the principal. principal.SetAudiences(context.Principal.GetResources()); // Store the client identifier in the public client_id claim, if available. // See https://tools.ietf.org/html/draft-ietf-oauth-access-token-jwt-04 for more information. principal.SetClaim(Claims.ClientId, context.ClientId); // When receiving a grant_type=refresh_token request, determine whether the client application // requests a limited set of scopes and immediately replace the scopes collection if necessary. if (context.EndpointType == OpenIddictServerEndpointType.Token && context.Request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(context.Request.Scope)) { var scopes = context.Request.GetScopes(); principal.SetScopes(scopes.Intersect(context.Principal.GetScopes())); context.Logger.LogDebug(SR.GetResourceString(SR.ID6010), scopes); } context.AccessTokenPrincipal = principal; return default; } } /// /// Contains the logic responsible of preparing and attaching the claims principal /// used to generate the authorization code, if one is going to be returned. /// public class PrepareAuthorizationCodePrincipal : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() .SetOrder(PrepareAccessTokenPrincipal.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Create a new principal containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). var principal = context.Principal.Clone(claim => { // Never include the public or internal token identifiers to ensure the identifiers // that are automatically inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { return false; } // Never include the creation and expiration dates that are automatically // inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { return false; } // Other claims are always included in the authorization code, even private claims. return true; }); principal.SetCreationDate(DateTimeOffset.UtcNow); var lifetime = context.Principal.GetAuthorizationCodeLifetime() ?? context.Options.AuthorizationCodeLifetime; if (lifetime.HasValue) { principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); } // Attach the redirect_uri to allow for later comparison when // receiving a grant_type=authorization_code token request. principal.SetClaim(Claims.Private.RedirectUri, context.Request.RedirectUri); // Attach the code challenge and the code challenge methods to allow the ValidateCodeVerifier // handler to validate the code verifier sent by the client as part of the token request. if (!string.IsNullOrEmpty(context.Request.CodeChallenge)) { principal.SetClaim(Claims.Private.CodeChallenge, context.Request.CodeChallenge); // Default to plain if no explicit code challenge method was specified. principal.SetClaim(Claims.Private.CodeChallengeMethod, !string.IsNullOrEmpty(context.Request.CodeChallengeMethod) ? context.Request.CodeChallengeMethod : CodeChallengeMethods.Plain); } // Attach the nonce so that it can be later returned by // the token endpoint as part of the JWT identity token. principal.SetClaim(Claims.Private.Nonce, context.Request.Nonce); context.AuthorizationCodePrincipal = principal; return default; } } /// /// Contains the logic responsible of preparing and attaching the claims principal /// used to generate the device code, if one is going to be returned. /// public class PrepareDeviceCodePrincipal : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() .SetOrder(PrepareAuthorizationCodePrincipal.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Create a new principal containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). var principal = context.Principal.Clone(claim => { // Never include the public or internal token identifiers to ensure the identifiers // that are automatically inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { return false; } // Never include the creation and expiration dates that are automatically // inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { return false; } // Other claims are always included in the device code, even private claims. return true; }); principal.SetCreationDate(DateTimeOffset.UtcNow); var lifetime = context.Principal.GetDeviceCodeLifetime() ?? context.Options.DeviceCodeLifetime; if (lifetime.HasValue) { principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); } // Restore the device code internal token identifier from the principal // resolved from the user code used in the user code verification request. if (context.EndpointType == OpenIddictServerEndpointType.Verification) { principal.SetClaim(Claims.Private.TokenId, context.Principal.GetClaim(Claims.Private.DeviceCodeId)); } context.DeviceCodePrincipal = principal; return default; } } /// /// Contains the logic responsible of preparing and attaching the claims principal /// used to generate the refresh token, if one is going to be returned. /// public class PrepareRefreshTokenPrincipal : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() .SetOrder(PrepareDeviceCodePrincipal.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Create a new principal containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). var principal = context.Principal.Clone(claim => { // Never include the public or internal token identifiers to ensure the identifiers // that are automatically inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { return false; } // Never include the creation and expiration dates that are automatically // inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { return false; } // Other claims are always included in the refresh token, even private claims. return true; }); principal.SetCreationDate(DateTimeOffset.UtcNow); // When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed // and must exactly match the expiration date of the refresh token used in the token request. if (context.EndpointType == OpenIddictServerEndpointType.Token && context.Request.IsRefreshTokenGrantType() && context.Options.DisableSlidingRefreshTokenExpiration) { var notification = context.Transaction.GetProperty( typeof(ProcessAuthenticationContext).FullName!) ?? throw new InvalidOperationException(SR.GetResourceString(SR.ID0007)); Debug.Assert(notification.RefreshTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); principal.SetExpirationDate(notification.RefreshTokenPrincipal.GetExpirationDate()); } else { var lifetime = context.Principal.GetRefreshTokenLifetime() ?? context.Options.RefreshTokenLifetime; if (lifetime.HasValue) { principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); } } context.RefreshTokenPrincipal = principal; return default; } } /// /// Contains the logic responsible of preparing and attaching the claims principal /// used to generate the identity token, if one is going to be returned. /// public class PrepareIdentityTokenPrincipal : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() .SetOrder(PrepareRefreshTokenPrincipal.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Replace the principal by a new one containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). var principal = context.Principal.Clone(claim => { // Never exclude the subject and authorization identifier claims. if (string.Equals(claim.Type, Claims.Subject, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.AuthorizationId, StringComparison.OrdinalIgnoreCase)) { return true; } // Never include the public or internal token identifiers to ensure the identifiers // that are automatically inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { return false; } // Never include the creation and expiration dates that are automatically // inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { return false; } // Always exclude private claims by default, whose values must generally be kept secret. if (claim.Type.StartsWith(Claims.Prefixes.Private, StringComparison.OrdinalIgnoreCase)) { return false; } // Claims whose destination is not explicitly referenced or doesn't // contain "id_token" are not included in the identity token. if (!claim.HasDestination(Destinations.IdentityToken)) { context.Logger.LogDebug(SR.GetResourceString(SR.ID6011), claim.Type); return false; } return true; }); // Remove the destinations from the claim properties. foreach (var claim in principal.Claims) { claim.Properties.Remove(Properties.Destinations); } principal.SetCreationDate(DateTimeOffset.UtcNow); var lifetime = context.Principal.GetIdentityTokenLifetime() ?? context.Options.IdentityTokenLifetime; if (lifetime.HasValue) { principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); } if (!string.IsNullOrEmpty(context.ClientId)) { principal.SetAudiences(context.ClientId); } // Use the client_id as the authorized party, if available. // See https://openid.net/specs/openid-connect-core-1_0.html#IDToken for more information. principal.SetClaim(Claims.AuthorizedParty, context.ClientId); // If a nonce was present in the authorization request, it MUST be included in the id_token generated // by the token endpoint. For that, OpenIddict simply flows the nonce as an authorization code claim. // See http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation for more information. principal.SetClaim(Claims.Nonce, context.EndpointType switch { OpenIddictServerEndpointType.Authorization => context.Request.Nonce, OpenIddictServerEndpointType.Token => context.Principal.GetClaim(Claims.Private.Nonce), _ => null }); context.IdentityTokenPrincipal = principal; return default; } } /// /// Contains the logic responsible of preparing and attaching the claims principal /// used to generate the user code, if one is going to be returned. /// public class PrepareUserCodePrincipal : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() .SetOrder(PrepareIdentityTokenPrincipal.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Create a new principal containing only the filtered claims. // Actors identities are also filtered (delegation scenarios). var principal = context.Principal.Clone(claim => { // Never include the public or internal token identifiers to ensure the identifiers // that are automatically inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.JwtId, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.Private.TokenId, StringComparison.OrdinalIgnoreCase)) { return false; } // Never include the creation and expiration dates that are automatically // inherited from the parent token are not reused for the new token. if (string.Equals(claim.Type, Claims.ExpiresAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.IssuedAt, StringComparison.OrdinalIgnoreCase) || string.Equals(claim.Type, Claims.NotBefore, StringComparison.OrdinalIgnoreCase)) { return false; } // Other claims are always included in the authorization code, even private claims. return true; }); principal.SetCreationDate(DateTimeOffset.UtcNow); var lifetime = context.Principal.GetUserCodeLifetime() ?? context.Options.UserCodeLifetime; if (lifetime.HasValue) { principal.SetExpirationDate(principal.GetCreationDate() + lifetime.Value); } // Store the client_id as a public client_id claim. principal.SetClaim(Claims.ClientId, context.Request.ClientId); context.UserCodePrincipal = principal; return default; } } /// /// Contains the logic responsible of redeeming the token entry corresponding to /// the received authorization code, device code, user code or refresh token. /// Note: this handler is not used when the degraded mode is enabled. /// public class RedeemTokenEntry : IOpenIddictServerHandler { private readonly IOpenIddictTokenManager _tokenManager; public RedeemTokenEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); public RedeemTokenEntry(IOpenIddictTokenManager tokenManager) => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() .UseScopedHandler() .SetOrder(100_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } switch (context.EndpointType) { case OpenIddictServerEndpointType.Token when context.Request.IsAuthorizationCodeGrantType(): case OpenIddictServerEndpointType.Token when context.Request.IsDeviceCodeGrantType(): case OpenIddictServerEndpointType.Token when context.Request.IsRefreshTokenGrantType() && !context.Options.DisableRollingRefreshTokens: case OpenIddictServerEndpointType.Verification: break; default: return; } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); // Extract the token identifier from the authentication principal. // If no token identifier can be found, this indicates that the token has no backing database entry. var identifier = context.Principal.GetTokenId(); if (string.IsNullOrEmpty(identifier)) { return; } // If rolling tokens are enabled or if the request is a a code or device code token request // or a user code verification request, mark the token as redeemed to prevent future reuses. var token = await _tokenManager.FindByIdAsync(identifier); if (token is not null) { await _tokenManager.TryRedeemAsync(token); } } } /// /// Contains the logic responsible of generating an access token for the current sign-in operation. /// public class GenerateAccessToken : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public GenerateAccessToken(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(RedeemTokenEntry.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new GenerateTokenContext(context.Transaction) { ClientId = context.ClientId, CreateTokenEntry = !context.Options.DisableTokenStorage, // Access tokens can be converted to reference tokens if the // corresponding option was enabled in the server options. PersistTokenPayload = context.Options.UseReferenceAccessTokens, Principal = context.AccessTokenPrincipal!, TokenType = TokenTypeHints.AccessToken }; 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.AccessToken = notification.Token; } } /// /// Contains the logic responsible of generating an authorization code for the current sign-in operation. /// public class GenerateAuthorizationCode : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public GenerateAuthorizationCode(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(GenerateAccessToken.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new GenerateTokenContext(context.Transaction) { ClientId = context.ClientId, CreateTokenEntry = !context.Options.DisableTokenStorage, PersistTokenPayload = !context.Options.DisableTokenStorage, Principal = context.AuthorizationCodePrincipal!, TokenType = TokenTypeHints.AuthorizationCode }; 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.AuthorizationCode = notification.Token; } } /// /// Contains the logic responsible of generating a device code for the current sign-in operation. /// public class GenerateDeviceCode : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public GenerateDeviceCode(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(GenerateAuthorizationCode.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new GenerateTokenContext(context.Transaction) { ClientId = context.ClientId, CreateTokenEntry = !context.Options.DisableTokenStorage, // Device codes can be converted to reference tokens if they are not generated // as part of a device code swap made by the user code verification endpoint. PersistTokenPayload = context.EndpointType switch { OpenIddictServerEndpointType.Verification => false, _ => !context.Options.DisableTokenStorage }, Principal = context.DeviceCodePrincipal!, TokenType = TokenTypeHints.DeviceCode }; 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.DeviceCode = notification.Token; } } /// /// Contains the logic responsible of generating a refresh token for the current sign-in operation. /// public class GenerateRefreshToken : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public GenerateRefreshToken(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(GenerateDeviceCode.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new GenerateTokenContext(context.Transaction) { ClientId = context.ClientId, CreateTokenEntry = !context.Options.DisableTokenStorage, // Refresh tokens can be converted to reference tokens if the // corresponding option was enabled in the server options. PersistTokenPayload = context.Options.UseReferenceRefreshTokens, Principal = context.RefreshTokenPrincipal!, TokenType = TokenTypeHints.RefreshToken }; 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.RefreshToken = notification.Token; } } /// /// Contains the logic responsible of generating and attaching the device code identifier to the user code principal. /// public class AttachDeviceCodeIdentifier : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() .UseSingletonHandler() .SetOrder(GenerateRefreshToken.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.UserCodePrincipal is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0020)); } var identifier = context.DeviceCodePrincipal?.GetTokenId(); if (!string.IsNullOrEmpty(identifier)) { context.UserCodePrincipal.SetClaim(Claims.Private.DeviceCodeId, identifier); } return default; } } /// /// Contains the logic responsible of updating the existing reference device code entry. /// Note: this handler is not used when the degraded mode is enabled. /// public class UpdateReferenceDeviceCodeEntry : IOpenIddictServerHandler { private readonly IOpenIddictTokenManager _tokenManager; public UpdateReferenceDeviceCodeEntry() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)); public UpdateReferenceDeviceCodeEntry(IOpenIddictTokenManager tokenManager) => _tokenManager = tokenManager; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .AddFilter() .AddFilter() .UseScopedHandler() .SetOrder(AttachDeviceCodeIdentifier.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.EndpointType != OpenIddictServerEndpointType.Verification || string.IsNullOrEmpty(context.DeviceCode)) { return; } Debug.Assert(context.Principal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006)); if (context.DeviceCodePrincipal is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0020)); } // Extract the token identifier from the authentication principal. var identifier = context.Principal.GetClaim(Claims.Private.DeviceCodeId); if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0008)); } var token = await _tokenManager.FindByIdAsync(identifier); if (token is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0265)); } // Replace the device code details by the payload derived from the new device code principal, // that includes all the user claims populated by the application after authenticating the user. var descriptor = new OpenIddictTokenDescriptor(); await _tokenManager.PopulateAsync(descriptor, token); // Note: the lifetime is deliberately extended to give more time to the client to redeem the code. descriptor.ExpirationDate = context.DeviceCodePrincipal.GetExpirationDate(); descriptor.Payload = context.DeviceCode; descriptor.Principal = context.DeviceCodePrincipal; descriptor.Status = Statuses.Valid; descriptor.Subject = context.DeviceCodePrincipal.GetClaim(Claims.Subject); await _tokenManager.UpdateAsync(token, descriptor); context.Logger.LogTrace(SR.GetResourceString(SR.ID6021), await _tokenManager.GetIdAsync(token)); } } /// /// Contains the logic responsible of generating and attaching the hashes of /// the access token and authorization code to the identity token principal. /// public class AttachTokenDigests : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseSingletonHandler() .SetOrder(UpdateReferenceDeviceCodeEntry.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.IdentityTokenPrincipal is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0022)); } if (string.IsNullOrEmpty(context.AccessToken) && string.IsNullOrEmpty(context.AuthorizationCode)) { return default; } var credentials = context.Options.SigningCredentials.FirstOrDefault( credentials => credentials.Key is AsymmetricSecurityKey); if (credentials is null) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0266)); } using var hash = GetHashAlgorithm(credentials); if (hash is null || hash is KeyedHashAlgorithm) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)); } if (!string.IsNullOrEmpty(context.AccessToken)) { var digest = hash.ComputeHash(Encoding.ASCII.GetBytes(context.AccessToken)); // Note: only the left-most half of the hash is used. // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken context.IdentityTokenPrincipal.SetClaim(Claims.AccessTokenHash, Base64UrlEncoder.Encode(digest, 0, digest.Length / 2)); } if (!string.IsNullOrEmpty(context.AuthorizationCode)) { var digest = hash.ComputeHash(Encoding.ASCII.GetBytes(context.AuthorizationCode)); // Note: only the left-most half of the hash is used. // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken context.IdentityTokenPrincipal.SetClaim(Claims.CodeHash, Base64UrlEncoder.Encode(digest, 0, digest.Length / 2)); } return default; static HashAlgorithm? GetHashAlgorithm(SigningCredentials credentials) { HashAlgorithm? hash = null; if (!string.IsNullOrEmpty(credentials.Digest)) { hash = CryptoConfig.CreateFromName(credentials.Digest) as HashAlgorithm; } if (hash is null) { var algorithm = credentials.Digest switch { SecurityAlgorithms.Sha256 or SecurityAlgorithms.Sha256Digest => HashAlgorithmName.SHA256, SecurityAlgorithms.Sha384 or SecurityAlgorithms.Sha384Digest => HashAlgorithmName.SHA384, SecurityAlgorithms.Sha512 or SecurityAlgorithms.Sha512Digest => HashAlgorithmName.SHA512, _ => credentials.Algorithm switch { #if SUPPORTS_ECDSA SecurityAlgorithms.EcdsaSha256 or SecurityAlgorithms.EcdsaSha256Signature => HashAlgorithmName.SHA256, SecurityAlgorithms.EcdsaSha384 or SecurityAlgorithms.EcdsaSha384Signature => HashAlgorithmName.SHA384, SecurityAlgorithms.EcdsaSha512 or SecurityAlgorithms.EcdsaSha512Signature => HashAlgorithmName.SHA512, #endif SecurityAlgorithms.HmacSha256 or SecurityAlgorithms.HmacSha256Signature => HashAlgorithmName.SHA256, SecurityAlgorithms.HmacSha384 or SecurityAlgorithms.HmacSha384Signature => HashAlgorithmName.SHA384, SecurityAlgorithms.HmacSha512 or SecurityAlgorithms.HmacSha512Signature => HashAlgorithmName.SHA512, SecurityAlgorithms.RsaSha256 or SecurityAlgorithms.RsaSha256Signature => HashAlgorithmName.SHA256, SecurityAlgorithms.RsaSha384 or SecurityAlgorithms.RsaSha384Signature => HashAlgorithmName.SHA384, SecurityAlgorithms.RsaSha512 or SecurityAlgorithms.RsaSha512Signature => HashAlgorithmName.SHA512, SecurityAlgorithms.RsaSsaPssSha256 or SecurityAlgorithms.RsaSsaPssSha256Signature => HashAlgorithmName.SHA256, SecurityAlgorithms.RsaSsaPssSha384 or SecurityAlgorithms.RsaSsaPssSha384Signature => HashAlgorithmName.SHA384, SecurityAlgorithms.RsaSsaPssSha512 or SecurityAlgorithms.RsaSsaPssSha512Signature => HashAlgorithmName.SHA512, _ => throw new InvalidOperationException(SR.GetResourceString(SR.ID0267)) } }; hash = CryptoConfig.CreateFromName(algorithm.Name!) as HashAlgorithm; } return hash; } } } /// /// Contains the logic responsible of generating a user code for the current sign-in operation. /// public class GenerateUserCode : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public GenerateUserCode(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(AttachTokenDigests.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new GenerateTokenContext(context.Transaction) { ClientId = context.ClientId, CreateTokenEntry = !context.Options.DisableTokenStorage, PersistTokenPayload = !context.Options.DisableTokenStorage, Principal = context.UserCodePrincipal!, TokenType = TokenTypeHints.UserCode }; 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.UserCode = notification.Token; } } /// /// Contains the logic responsible of generating an identity token for the current sign-in operation. /// public class GenerateIdentityToken : IOpenIddictServerHandler { private readonly IOpenIddictServerDispatcher _dispatcher; public GenerateIdentityToken(IOpenIddictServerDispatcher dispatcher) => _dispatcher = dispatcher; /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .AddFilter() .UseScopedHandler() .SetOrder(GenerateUserCode.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public async ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } var notification = new GenerateTokenContext(context.Transaction) { ClientId = context.ClientId, CreateTokenEntry = !context.Options.DisableTokenStorage, // Identity tokens cannot never be reference tokens. PersistTokenPayload = false, Principal = context.IdentityTokenPrincipal!, TokenType = TokenTypeHints.IdToken }; 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.IdentityToken = notification.Token; } } /// /// Contains the logic responsible of attaching the appropriate parameters to the sign-in response. /// public class AttachSignInParameters : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(GenerateIdentityToken.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignInContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.IncludeAccessToken) { context.Response.AccessToken = context.AccessToken; context.Response.TokenType = TokenTypes.Bearer; // If the principal is available, attach additional metadata. if (context.AccessTokenPrincipal is not null) { // If an expiration date was set on the access token principal, return it to the client application. var date = context.AccessTokenPrincipal.GetExpirationDate(); if (date.HasValue && date.Value > DateTimeOffset.UtcNow) { context.Response.ExpiresIn = (long) ((date.Value - DateTimeOffset.UtcNow).TotalSeconds + .5); } // If the granted access token scopes differ from the requested scopes, return the granted scopes // list as a parameter to inform the client application of the fact the scopes set will be reduced. var scopes = new HashSet(context.AccessTokenPrincipal.GetScopes(), StringComparer.Ordinal); if ((context.EndpointType == OpenIddictServerEndpointType.Token && context.Request.IsAuthorizationCodeGrantType()) || !scopes.SetEquals(context.Request.GetScopes())) { context.Response.Scope = string.Join(" ", scopes); } } } if (context.IncludeAuthorizationCode) { context.Response.Code = context.AuthorizationCode; } if (context.IncludeDeviceCode) { context.Response.DeviceCode = context.DeviceCode; // If the principal is available, attach additional metadata. if (context.DeviceCodePrincipal is not null) { // If an expiration date was set on the device code principal, return it to the client application. var date = context.DeviceCodePrincipal.GetExpirationDate(); if (date.HasValue && date.Value > DateTimeOffset.UtcNow) { context.Response.ExpiresIn = (long) ((date.Value - DateTimeOffset.UtcNow).TotalSeconds + .5); } } } if (context.IncludeIdentityToken) { context.Response.IdToken = context.IdentityToken; } if (context.IncludeRefreshToken) { context.Response.RefreshToken = context.RefreshToken; } if (context.IncludeUserCode) { context.Response.UserCode = context.UserCode; var address = GetEndpointAbsoluteUri(context.Issuer, context.Options.VerificationEndpointUris.FirstOrDefault()); if (address is not null) { var builder = new UriBuilder(address) { Query = string.Concat(Parameters.UserCode, "=", context.UserCode) }; context.Response[Parameters.VerificationUri] = address.AbsoluteUri; context.Response[Parameters.VerificationUriComplete] = builder.Uri.AbsoluteUri; } } if (context.Parameters.Count > 0) { foreach (var parameter in context.Parameters) { context.Response.SetParameter(parameter.Key, parameter.Value); } } return default; static Uri? GetEndpointAbsoluteUri(Uri? issuer, Uri? endpoint) { // If the endpoint is disabled (i.e a null address is specified), return null. if (endpoint is null) { return null; } // If the endpoint address is already an absolute URL, return it as-is. if (endpoint.IsAbsoluteUri) { return endpoint; } // At this stage, throw an exception if the issuer cannot be retrieved. if (issuer is null || !issuer.IsAbsoluteUri) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0023)); } // Ensure the issuer ends with a trailing slash, as it is necessary // for Uri's constructor to correctly compute correct absolute URLs. if (!issuer.OriginalString.EndsWith("/", StringComparison.Ordinal)) { issuer = new Uri(issuer.OriginalString + "/", UriKind.Absolute); } // Ensure the endpoint does not start with a leading slash, as it is necessary // for Uri's constructor to correctly compute correct absolute URLs. if (endpoint.OriginalString.StartsWith("/", StringComparison.Ordinal)) { endpoint = new Uri(endpoint.OriginalString.Substring(1, endpoint.OriginalString.Length - 1), UriKind.Relative); } return new Uri(issuer, endpoint); } } } /// /// Contains the logic responsible of ensuring that the sign-out demand /// is compatible with the type of the endpoint that handled the request. /// public class ValidateSignOutDemand : 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(ProcessSignOutContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.EndpointType != OpenIddictServerEndpointType.Logout) { throw new InvalidOperationException(SR.GetResourceString(SR.ID0024)); } return default; } } /// /// Contains the logic responsible of attaching the appropriate parameters to the sign-out response. /// public class AttachSignOutParameters : IOpenIddictServerHandler { /// /// Gets the default descriptor definition assigned to this handler. /// public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder() .UseSingletonHandler() .SetOrder(ValidateSignOutDemand.Descriptor.Order + 1_000) .SetType(OpenIddictServerHandlerType.BuiltIn) .Build(); /// public ValueTask HandleAsync(ProcessSignOutContext context) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (context.Parameters.Count > 0) { foreach (var parameter in context.Parameters) { context.Response.SetParameter(parameter.Key, parameter.Value); } } return default; } } } }