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

1703 lines
83 KiB

/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
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
{
public static partial class OpenIddictServerHandlers
{
public static class Authentication
{
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Authorization request top-level processing:
*/
ExtractAuthorizationRequest.Descriptor,
ValidateAuthorizationRequest.Descriptor,
HandleAuthorizationRequest.Descriptor,
ApplyAuthorizationResponse<ProcessChallengeContext>.Descriptor,
ApplyAuthorizationResponse<ProcessErrorContext>.Descriptor,
ApplyAuthorizationResponse<ProcessRequestContext>.Descriptor,
ApplyAuthorizationResponse<ProcessSignInContext>.Descriptor,
/*
* Authorization request validation:
*/
ValidateRequestParameter.Descriptor,
ValidateRequestUriParameter.Descriptor,
ValidateClientIdParameter.Descriptor,
ValidateRedirectUriParameter.Descriptor,
ValidateResponseTypeParameter.Descriptor,
ValidateResponseModeParameter.Descriptor,
ValidateScopeParameter.Descriptor,
ValidateNonceParameter.Descriptor,
ValidatePromptParameter.Descriptor,
ValidateProofKeyForCodeExchangeParameters.Descriptor,
ValidateClientId.Descriptor,
ValidateClientType.Descriptor,
ValidateClientRedirectUri.Descriptor,
ValidateScopes.Descriptor,
ValidateEndpointPermissions.Descriptor,
ValidateGrantTypePermissions.Descriptor,
ValidateResponseTypePermissions.Descriptor,
ValidateScopePermissions.Descriptor,
ValidateProofKeyForCodeExchangeRequirement.Descriptor,
/*
* Authorization response processing:
*/
AttachRedirectUri.Descriptor,
InferResponseMode.Descriptor,
AttachResponseState.Descriptor);
/// <summary>
/// Contains the logic responsible of extracting authorization requests and invoking the corresponding event handlers.
/// </summary>
public class ExtractAuthorizationRequest : IOpenIddictServerHandler<ProcessRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ExtractAuthorizationRequest(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireAuthorizationRequest>()
.UseScopedHandler<ExtractAuthorizationRequest>()
.SetOrder(100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ExtractAuthorizationRequestContext(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.ID0027));
}
context.Logger.LogInformation(SR.GetResourceString(SR.ID6030), notification.Request);
}
}
/// <summary>
/// Contains the logic responsible of validating authorization requests and invoking the corresponding event handlers.
/// </summary>
public class ValidateAuthorizationRequest : IOpenIddictServerHandler<ProcessRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ValidateAuthorizationRequest(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireAuthorizationRequest>()
.UseScopedHandler<ValidateAuthorizationRequest>()
.SetOrder(ExtractAuthorizationRequest.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ValidateAuthorizationRequestContext(context.Transaction);
await _dispatcher.DispatchAsync(notification);
// Store the context object in the transaction so it can be later retrieved by handlers
// that want to access the redirect_uri without triggering a new validation process.
context.Transaction.SetProperty(typeof(ValidateAuthorizationRequestContext).FullName!, 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 (string.IsNullOrEmpty(notification.RedirectUri))
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0028));
}
context.Logger.LogInformation(SR.GetResourceString(SR.ID6031));
}
}
/// <summary>
/// Contains the logic responsible of handling authorization requests and invoking the corresponding event handlers.
/// </summary>
public class HandleAuthorizationRequest : IOpenIddictServerHandler<ProcessRequestContext>
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public HandleAuthorizationRequest(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>()
.AddFilter<RequireAuthorizationRequest>()
.UseScopedHandler<HandleAuthorizationRequest>()
.SetOrder(ValidateAuthorizationRequest.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ProcessRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new HandleAuthorizationRequestContext(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.Principal is not null)
{
var @event = new ProcessSignInContext(context.Transaction)
{
Principal = notification.Principal,
Response = new OpenIddictResponse()
};
await _dispatcher.DispatchAsync(@event);
if (@event.IsRequestHandled)
{
context.HandleRequest();
return;
}
else if (@event.IsRequestSkipped)
{
context.SkipRequest();
return;
}
else if (@event.IsRejected)
{
context.Reject(
error: @event.Error ?? Errors.InvalidRequest,
description: @event.ErrorDescription,
uri: @event.ErrorUri);
return;
}
}
throw new InvalidOperationException(SR.GetResourceString(SR.ID0029));
}
}
/// <summary>
/// Contains the logic responsible of processing sign-in responses and invoking the corresponding event handlers.
/// </summary>
public class ApplyAuthorizationResponse<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext
{
private readonly IOpenIddictServerDispatcher _dispatcher;
public ApplyAuthorizationResponse(IOpenIddictServerDispatcher dispatcher)
=> _dispatcher = dispatcher;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>()
.AddFilter<RequireAuthorizationRequest>()
.UseScopedHandler<ApplyAuthorizationResponse<TContext>>()
.SetOrder(int.MaxValue - 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(TContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
var notification = new ApplyAuthorizationResponseContext(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.ID0030));
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that specify the unsupported request parameter.
/// </summary>
public class ValidateRequestParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateRequestParameter>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reject requests using the unsupported request parameter.
if (!string.IsNullOrEmpty(context.Request.Request))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6032), Parameters.Request);
context.Reject(
error: Errors.RequestNotSupported,
description: context.Localizer[SR.ID2028, Parameters.Request]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that specify the unsupported request_uri parameter.
/// </summary>
public class ValidateRequestUriParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateRequestUriParameter>()
.SetOrder(ValidateRequestParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reject requests using the unsupported request_uri parameter.
if (!string.IsNullOrEmpty(context.Request.RequestUri))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6032), Parameters.RequestUri);
context.Reject(
error: Errors.RequestUriNotSupported,
description: context.Localizer[SR.ID2028, Parameters.RequestUri]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that lack the mandatory client_id parameter.
/// </summary>
public class ValidateClientIdParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateClientIdParameter>()
.SetOrder(ValidateRequestUriParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// client_id is a required parameter and MUST cause an error when missing.
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest.
if (string.IsNullOrEmpty(context.ClientId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6033), Parameters.ClientId);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2029, Parameters.ClientId]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that lack the mandatory redirect_uri parameter.
/// </summary>
public class ValidateRedirectUriParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateRedirectUriParameter>()
.SetOrder(ValidateClientIdParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// While redirect_uri was not mandatory in OAuth 2.0, this parameter
// is now declared as REQUIRED and MUST cause an error when missing.
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest.
// To keep OpenIddict compatible with pure OAuth 2.0 clients, an error
// is only returned if the request was made by an OpenID Connect client.
if (string.IsNullOrEmpty(context.RedirectUri))
{
if (context.Request.HasScope(Scopes.OpenId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6033), Parameters.RedirectUri);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2029, Parameters.RedirectUri]);
return default;
}
return default;
}
// Note: when specified, redirect_uri MUST be an absolute URI.
// See http://tools.ietf.org/html/rfc6749#section-3.1.2
// and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest.
//
// Note: on Linux/macOS, "/path" URLs are treated as valid absolute file URLs.
// To ensure relative redirect_uris are correctly rejected on these platforms,
// an additional check using IsWellFormedOriginalString() is made here.
// See https://github.com/dotnet/corefx/issues/22098 for more information.
if (!Uri.TryCreate(context.RedirectUri, UriKind.Absolute, out Uri? uri) || !uri.IsWellFormedOriginalString())
{
context.Logger.LogError(SR.GetResourceString(SR.ID6034), Parameters.RedirectUri, context.RedirectUri);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2030, Parameters.RedirectUri]);
return default;
}
// Note: when specified, redirect_uri MUST NOT include a fragment component.
// See http://tools.ietf.org/html/rfc6749#section-3.1.2
// and http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
if (!string.IsNullOrEmpty(uri.Fragment))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6035), Parameters.RedirectUri, context.RedirectUri);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2031, Parameters.RedirectUri]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that specify an invalid response_type parameter.
/// </summary>
public class ValidateResponseTypeParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateResponseTypeParameter>()
.SetOrder(ValidateRedirectUriParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reject requests missing the mandatory response_type parameter.
if (string.IsNullOrEmpty(context.Request.ResponseType))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6033), Parameters.ResponseType);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2029, Parameters.ResponseType]);
return default;
}
// Reject code flow requests if the server is not configured to allow the authorization code grant type.
if (context.Request.IsAuthorizationCodeFlow() && !context.Options.GrantTypes.Contains(GrantTypes.AuthorizationCode))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6036), context.Request.ResponseType);
context.Reject(
error: Errors.UnsupportedResponseType,
description: context.Localizer[SR.ID2032, Parameters.ResponseType]);
return default;
}
// Reject implicit flow requests if the server is not configured to allow the implicit grant type.
if (context.Request.IsImplicitFlow() && !context.Options.GrantTypes.Contains(GrantTypes.Implicit))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6036), context.Request.ResponseType);
context.Reject(
error: Errors.UnsupportedResponseType,
description: context.Localizer[SR.ID2032, Parameters.ResponseType]);
return default;
}
// Reject hybrid flow requests if the server is not configured to allow the authorization code or implicit grant types.
if (context.Request.IsHybridFlow() && (!context.Options.GrantTypes.Contains(GrantTypes.AuthorizationCode) ||
!context.Options.GrantTypes.Contains(GrantTypes.Implicit)))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6036), context.Request.ResponseType);
context.Reject(
error: Errors.UnsupportedResponseType,
description: context.Localizer[SR.ID2032, Parameters.ResponseType]);
return default;
}
// Reject requests that specify an unsupported response_type.
var types = new HashSet<string>(context.Request.GetResponseTypes(), StringComparer.Ordinal);
if (!context.Options.ResponseTypes.Any(type =>
types.SetEquals(type.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries))))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6036), context.Request.ResponseType);
context.Reject(
error: Errors.UnsupportedResponseType,
description: context.Localizer[SR.ID2032, Parameters.ResponseType]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that specify an invalid response_mode parameter.
/// </summary>
public class ValidateResponseModeParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateResponseModeParameter>()
.SetOrder(ValidateResponseTypeParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// response_mode=query (explicit or not) and a response_type containing id_token
// or token are not considered as a safe combination and MUST be rejected.
// See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security.
if (context.Request.IsQueryResponseMode() && (context.Request.HasResponseType(ResponseTypes.IdToken) ||
context.Request.HasResponseType(ResponseTypes.Token)))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6037), context.Request.ResponseType, context.Request.ResponseMode);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2033, Parameters.ResponseType, Parameters.ResponseMode]);
return default;
}
// Reject requests that specify an unsupported response_mode or don't specify a different response_mode
// if the default response_mode inferred from the response_type was explicitly disabled in the options.
if (!ValidateResponseMode(context.Request, context.Options))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6038), context.Request.ResponseMode);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2032, Parameters.ResponseMode]);
return default;
}
return default;
static bool ValidateResponseMode(OpenIddictRequest request, OpenIddictServerOptions options)
{
// Note: both the fragment and query response modes are used as default response modes
// when using the implicit/hybrid and code flows if no explicit value was set.
// To ensure requests are rejected if the default response mode was manually disabled,
// the fragment and query response modes are checked first using the appropriate extensions.
if (request.IsFragmentResponseMode())
{
return options.ResponseModes.Contains(ResponseModes.Fragment);
}
if (request.IsQueryResponseMode())
{
return options.ResponseModes.Contains(ResponseModes.Query);
}
if (string.IsNullOrEmpty(request.ResponseMode))
{
return true;
}
return options.ResponseModes.Contains(request.ResponseMode);
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that don't specify a valid scope parameter.
/// </summary>
public class ValidateScopeParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateScopeParameter>()
.SetOrder(ValidateResponseModeParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reject authorization requests containing the id_token response_type if no openid scope has been received.
if (context.Request.HasResponseType(ResponseTypes.IdToken) && !context.Request.HasScope(Scopes.OpenId))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6039), Scopes.OpenId);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2034, Scopes.OpenId]);
return default;
}
// Reject authorization requests that specify scope=offline_access if the refresh token flow is not enabled.
if (context.Request.HasScope(Scopes.OfflineAccess) && !context.Options.GrantTypes.Contains(GrantTypes.RefreshToken))
{
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2035, Scopes.OfflineAccess]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that don't specify a nonce.
/// </summary>
public class ValidateNonceParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateNonceParameter>()
.SetOrder(ValidateScopeParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reject OpenID Connect implicit/hybrid requests missing the mandatory nonce parameter.
// See http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest,
// http://openid.net/specs/openid-connect-implicit-1_0.html#RequestParameters
// and http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken.
if (!string.IsNullOrEmpty(context.Request.Nonce) || !context.Request.HasScope(Scopes.OpenId))
{
return default;
}
if (context.Request.IsImplicitFlow() || context.Request.IsHybridFlow())
{
context.Logger.LogError(SR.GetResourceString(SR.ID6033), Parameters.Nonce);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2029, Parameters.Nonce]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that don't specify a valid prompt parameter.
/// </summary>
public class ValidatePromptParameter : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidatePromptParameter>()
.SetOrder(ValidateNonceParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Reject requests specifying prompt=none with consent/login or select_account.
if (context.Request.HasPrompt(Prompts.None) && (context.Request.HasPrompt(Prompts.Consent) ||
context.Request.HasPrompt(Prompts.Login) ||
context.Request.HasPrompt(Prompts.SelectAccount)))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6040));
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2052, Parameters.Prompt]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that don't specify valid PKCE parameters.
/// </summary>
public class ValidateProofKeyForCodeExchangeParameters : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.UseSingletonHandler<ValidateProofKeyForCodeExchangeParameters>()
.SetOrder(ValidatePromptParameter.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If OpenIddict was configured to require PKCE, reject the request if the code challenge
// is missing and if an authorization code was requested by the client application.
if (context.Options.RequireProofKeyForCodeExchange &&
context.Request.HasResponseType(ResponseTypes.Code) &&
string.IsNullOrEmpty(context.Request.CodeChallenge))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6033), Parameters.CodeChallenge);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2029, Parameters.CodeChallenge]);
return default;
}
// At this point, stop validating the PKCE parameters if both the
// code_challenge and code_challenge_method parameter are missing.
if (string.IsNullOrEmpty(context.Request.CodeChallenge) &&
string.IsNullOrEmpty(context.Request.CodeChallengeMethod))
{
return default;
}
// Ensure a code_challenge was specified if a code_challenge_method was used.
if (string.IsNullOrEmpty(context.Request.CodeChallenge))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6033), Parameters.CodeChallenge);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2037, Parameters.CodeChallengeMethod, Parameters.CodeChallenge]);
return default;
}
// If the plain code challenge method was not explicitly enabled,
// reject the request indicating that a method must be set.
if (string.IsNullOrEmpty(context.Request.CodeChallengeMethod) &&
!context.Options.CodeChallengeMethods.Contains(CodeChallengeMethods.Plain))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6033), Parameters.CodeChallengeMethod);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2029, Parameters.CodeChallengeMethod]);
return default;
}
// If a code_challenge_method was specified, ensure the algorithm is supported.
if (!string.IsNullOrEmpty(context.Request.CodeChallengeMethod) &&
!context.Options.CodeChallengeMethods.Contains(context.Request.CodeChallengeMethod))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6041));
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2032, Parameters.CodeChallengeMethod]);
return default;
}
// When code_challenge or code_challenge_method is specified, ensure the response_type includes "code".
if (!context.Request.HasResponseType(ResponseTypes.Code))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6042));
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2040, Parameters.CodeChallenge,
Parameters.CodeChallengeMethod, ResponseTypes.Code]);
return default;
}
// Reject authorization requests that contain response_type=token when a code_challenge is specified.
if (context.Request.HasResponseType(ResponseTypes.Token))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6043));
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2041, Parameters.ResponseType]);
return default;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that use an invalid client_id.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateClientId : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientId() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientId(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientId>()
.SetOrder(ValidateProofKeyForCodeExchangeParameters.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
context.Logger.LogError(SR.GetResourceString(SR.ID6044), context.ClientId);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2052, Parameters.ClientId]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that use a
/// response_type containing token if the application is a confidential client.
/// Note: this handler is not used when the degraded mode is enabled
/// or when response type permissions enforcement is not disabled.
/// </summary>
public class ValidateClientType : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientType() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientType(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientType>()
.SetOrder(ValidateClientId.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
}
// To prevent downgrade attacks, ensure that authorization requests returning an access token directly
// from the authorization endpoint are rejected if the client_id corresponds to a confidential application
// and if response type permissions enforcement was explicitly disabled in the server options.
// Users who want to enable this advanced scenario are encouraged to re-enable permissions validation.
//
// Alternatively, this handler can be removed from the handlers list using the events model APIs.
if (!context.Options.IgnoreResponseTypePermissions || !context.Request.HasResponseType(ResponseTypes.Token))
{
return;
}
if (await _applicationManager.HasClientTypeAsync(application, ClientTypes.Confidential))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6045), context.ClientId);
context.Reject(
error: Errors.UnauthorizedClient,
description: context.Localizer[SR.ID2043, Parameters.ResponseType]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that use an invalid redirect_uri.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateClientRedirectUri : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateClientRedirectUri() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateClientRedirectUri(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateClientRedirectUri>()
.SetOrder(ValidateClientType.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
}
// If no explicit redirect_uri was specified, retrieve the addresses associated with
// the client and ensure exactly one redirect_uri was attached to the client definition.
if (string.IsNullOrEmpty(context.RedirectUri))
{
var addresses = await _applicationManager.GetRedirectUrisAsync(application);
if (addresses.Length != 1)
{
context.Logger.LogError(SR.GetResourceString(SR.ID6033), Parameters.RedirectUri);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2029, Parameters.RedirectUri]);
return;
}
context.SetRedirectUri(addresses[0]);
return;
}
// Otherwise, ensure that the specified redirect_uri is valid and is associated with the client application.
if (!await _applicationManager.ValidateRedirectUriAsync(application, context.RedirectUri))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6046), context.RedirectUri);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2043, Parameters.RedirectUri]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests that use unregistered scopes.
/// Note: this handler partially works with the degraded mode but is not used when scope validation is disabled.
/// </summary>
public class ValidateScopes : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictScopeManager? _scopeManager;
public ValidateScopes(IOpenIddictScopeManager? scopeManager = null)
=> _scopeManager = scopeManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireScopeValidationEnabled>()
.UseScopedHandler<ValidateScopes>(static provider =>
{
// Note: the scope manager is only resolved if the degraded mode was not enabled to ensure
// invalid core configuration exceptions are not thrown even if the managers were registered.
var options = provider.GetRequiredService<IOptionsMonitor<OpenIddictServerOptions>>().CurrentValue;
return options.EnableDegradedMode ?
new ValidateScopes() :
new ValidateScopes(provider.GetService<IOpenIddictScopeManager>() ??
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016)));
})
.SetOrder(ValidateClientRedirectUri.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// If all the specified scopes are registered in the options, avoid making a database lookup.
var scopes = new HashSet<string>(context.Request.GetScopes(), StringComparer.Ordinal);
scopes.ExceptWith(context.Options.Scopes);
// Note: the remaining scopes are only checked if the degraded mode was not enabled,
// as this requires using the scope manager, which is never used with the degraded mode,
// even if the service was registered and resolved from the dependency injection container.
if (scopes.Count != 0 && !context.Options.EnableDegradedMode)
{
if (_scopeManager is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
}
await foreach (var scope in _scopeManager.FindByNamesAsync(scopes.ToImmutableArray()))
{
var name = await _scopeManager.GetNameAsync(scope);
if (!string.IsNullOrEmpty(name))
{
scopes.Remove(name);
}
}
}
// If at least one scope was not recognized, return an error.
if (scopes.Count != 0)
{
context.Logger.LogError(SR.GetResourceString(SR.ID6047), scopes);
context.Reject(
error: Errors.InvalidScope,
description: context.Localizer[SR.ID2052, Parameters.Scope]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests made by unauthorized applications.
/// Note: this handler is not used when the degraded mode is enabled or when endpoint permissions are disabled.
/// </summary>
public class ValidateEndpointPermissions : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateEndpointPermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateEndpointPermissions(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireEndpointPermissionsEnabled>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateEndpointPermissions>()
.SetOrder(ValidateScopes.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
}
// Reject the request if the application is not allowed to use the authorization endpoint.
if (!await _applicationManager.HasPermissionAsync(application, Permissions.Endpoints.Authorization))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6048), context.ClientId);
context.Reject(
error: Errors.UnauthorizedClient,
description: context.Localizer[SR.ID2046]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests made by unauthorized applications.
/// Note: this handler is not used when the degraded mode is enabled or when grant type permissions are disabled.
/// </summary>
public class ValidateGrantTypePermissions : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateGrantTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateGrantTypePermissions(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireGrantTypePermissionsEnabled>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateGrantTypePermissions>()
.SetOrder(ValidateEndpointPermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
}
// Reject the request if the application is not allowed to use the authorization code grant.
if (context.Request.IsAuthorizationCodeFlow() &&
!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.AuthorizationCode))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6049), context.ClientId);
context.Reject(
error: Errors.UnauthorizedClient,
description: context.Localizer[SR.ID2047]);
return;
}
// Reject the request if the application is not allowed to use the implicit grant.
if (context.Request.IsImplicitFlow() &&
!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.Implicit))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6050), context.ClientId);
context.Reject(
error: Errors.UnauthorizedClient,
description: context.Localizer[SR.ID2048]);
return;
}
// Reject the request if the application is not allowed to use the authorization code/implicit grants.
if (context.Request.IsHybridFlow() &&
(!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.AuthorizationCode) ||
!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.Implicit)))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6051), context.ClientId);
context.Reject(
error: Errors.UnauthorizedClient,
description: context.Localizer[SR.ID2049]);
return;
}
// Reject the request if the offline_access scope was request and
// if the application is not allowed to use the refresh token grant.
if (context.Request.HasScope(Scopes.OfflineAccess) &&
!await _applicationManager.HasPermissionAsync(application, Permissions.GrantTypes.RefreshToken))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6052), context.ClientId, Scopes.OfflineAccess);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2065, Scopes.OfflineAccess]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests made by unauthorized applications.
/// Note: this handler is not used when the degraded mode is enabled or when grant type permissions are disabled.
/// </summary>
public class ValidateResponseTypePermissions : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateResponseTypePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateResponseTypePermissions(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireResponseTypePermissionsEnabled>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateResponseTypePermissions>()
.SetOrder(ValidateGrantTypePermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
}
// Reject requests that specify a response_type for which no permission was granted.
if (!await HasPermissionAsync(context.Request.GetResponseTypes()))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6081), context.ClientId, context.Request.ResponseType);
context.Reject(
error: Errors.UnauthorizedClient,
description: context.Localizer[SR.ID2043, Parameters.ResponseType]);
return;
}
async ValueTask<bool> HasPermissionAsync(IEnumerable<string> types)
{
// Note: response type permissions are always prefixed with "rst:".
const string prefix = Permissions.Prefixes.ResponseType;
foreach (var permission in await _applicationManager.GetPermissionsAsync(application))
{
// Ignore permissions that are not response type permissions.
if (!permission.StartsWith(prefix, StringComparison.Ordinal))
{
continue;
}
// Note: response types can be specified in any order. To ensure permissions are correctly
// checked even if the order differs from the one specified in the request, a HashSet is used.
var values = permission.Substring(prefix.Length, permission.Length - prefix.Length)
.Split(Separators.Space, StringSplitOptions.RemoveEmptyEntries);
if (values.Length != 0 && new HashSet<string>(values, StringComparer.Ordinal).SetEquals(types))
{
return true;
}
}
return false;
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests made by unauthorized applications.
/// Note: this handler is not used when the degraded mode is enabled or when scope permissions are disabled.
/// </summary>
public class ValidateScopePermissions : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateScopePermissions() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateScopePermissions(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireScopePermissionsEnabled>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateScopePermissions>()
.SetOrder(ValidateResponseTypePermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
}
foreach (var scope in context.Request.GetScopes())
{
// Avoid validating the "openid" and "offline_access" scopes as they represent protocol scopes.
if (string.Equals(scope, Scopes.OfflineAccess, StringComparison.Ordinal) ||
string.Equals(scope, Scopes.OpenId, StringComparison.Ordinal))
{
continue;
}
// Reject the request if the application is not allowed to use the iterated scope.
if (!await _applicationManager.HasPermissionAsync(application, Permissions.Prefixes.Scope + scope))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6052), context.ClientId, scope);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2051]);
return;
}
}
}
}
/// <summary>
/// Contains the logic responsible of rejecting authorization requests made by
/// applications for which proof key for code exchange (PKCE) was enforced.
/// Note: this handler is not used when the degraded mode is enabled.
/// </summary>
public class ValidateProofKeyForCodeExchangeRequirement : IOpenIddictServerHandler<ValidateAuthorizationRequestContext>
{
private readonly IOpenIddictApplicationManager _applicationManager;
public ValidateProofKeyForCodeExchangeRequirement() => throw new InvalidOperationException(SR.GetResourceString(SR.ID0016));
public ValidateProofKeyForCodeExchangeRequirement(IOpenIddictApplicationManager applicationManager)
=> _applicationManager = applicationManager;
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateAuthorizationRequestContext>()
.AddFilter<RequireDegradedModeDisabled>()
.UseScopedHandler<ValidateProofKeyForCodeExchangeRequirement>()
.SetOrder(ValidateScopePermissions.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public async ValueTask HandleAsync(ValidateAuthorizationRequestContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
Debug.Assert(!string.IsNullOrEmpty(context.ClientId), SR.FormatID4000(Parameters.ClientId));
// If a code_challenge was provided, the request is always considered valid,
// whether the proof key for code exchange requirement is enforced or not.
if (!string.IsNullOrEmpty(context.Request.CodeChallenge))
{
return;
}
var application = await _applicationManager.FindByClientIdAsync(context.ClientId);
if (application is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0032));
}
if (await _applicationManager.HasRequirementAsync(application, Requirements.Features.ProofKeyForCodeExchange))
{
context.Logger.LogError(SR.GetResourceString(SR.ID6033), Parameters.CodeChallenge);
context.Reject(
error: Errors.InvalidRequest,
description: context.Localizer[SR.ID2054, Parameters.CodeChallenge]);
return;
}
}
}
/// <summary>
/// Contains the logic responsible of inferring the redirect URL
/// used to send the response back to the client application.
/// </summary>
public class AttachRedirectUri : IOpenIddictServerHandler<ApplyAuthorizationResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ApplyAuthorizationResponseContext>()
.UseSingletonHandler<AttachRedirectUri>()
.SetOrder(int.MinValue + 100_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ApplyAuthorizationResponseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Request is null)
{
return default;
}
var notification = context.Transaction.GetProperty<ValidateAuthorizationRequestContext>(
typeof(ValidateAuthorizationRequestContext).FullName!);
// Note: at this stage, the validated redirect URI property may be null (e.g if an error
// is returned from the ExtractAuthorizationRequest/ValidateAuthorizationRequest events).
if (notification is not null && !notification.IsRejected)
{
context.RedirectUri = notification.RedirectUri;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of inferring the response mode
/// used to send the response back to the client application.
/// </summary>
public class InferResponseMode : IOpenIddictServerHandler<ApplyAuthorizationResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ApplyAuthorizationResponseContext>()
.UseSingletonHandler<InferResponseMode>()
.SetOrder(AttachRedirectUri.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ApplyAuthorizationResponseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Request is null)
{
return default;
}
context.ResponseMode = context.Request.ResponseMode;
// If the response_mode parameter was not specified, try to infer it.
if (string.IsNullOrEmpty(context.ResponseMode) && !string.IsNullOrEmpty(context.RedirectUri))
{
context.ResponseMode = context.Request.IsFormPostResponseMode() ? ResponseModes.FormPost :
context.Request.IsFragmentResponseMode() ? ResponseModes.Fragment :
context.Request.IsQueryResponseMode() ? ResponseModes.Query : null;
}
return default;
}
}
/// <summary>
/// Contains the logic responsible of attaching the state to the response.
/// </summary>
public class AttachResponseState : IOpenIddictServerHandler<ApplyAuthorizationResponseContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<ApplyAuthorizationResponseContext>()
.UseSingletonHandler<AttachResponseState>()
.SetOrder(InferResponseMode.Descriptor.Order + 1_000)
.SetType(OpenIddictServerHandlerType.BuiltIn)
.Build();
/// <inheritdoc/>
public ValueTask HandleAsync(ApplyAuthorizationResponseContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
// Attach the request state to the authorization response.
if (string.IsNullOrEmpty(context.Response.State))
{
context.Response.State = context.Request?.State;
}
return default;
}
}
}
}
}