| File: AuthorizationMiddleware.cs | Web Access |
| Project: Microsoft.AspNetCore.Authorization.Policy.csproj (Microsoft.AspNetCore.Authorization.Policy) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features.Authentication; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Authorization; // This middleware exists to force the correct constructor overload to be called when the user calls UseAuthorization(). // Since we already expose the AuthorizationMiddleware type, we can't change the constructor signature without breaking it. internal sealed class AuthorizationMiddlewareInternal( RequestDelegate next, IServiceProvider services, IAuthorizationPolicyProvider policyProvider, ILogger<AuthorizationMiddleware> logger) : AuthorizationMiddleware(next, policyProvider, services, logger) { } /// <summary> /// A middleware that enables authorization capabilities. /// </summary> public class AuthorizationMiddleware { // AppContext switch used to control whether HttpContext or endpoint is passed as a resource to AuthZ private const string SuppressUseHttpContextAsAuthorizationResource = "Microsoft.AspNetCore.Authorization.SuppressUseHttpContextAsAuthorizationResource"; private static readonly bool _suppressUseHttpContextAsAuthorizationResource = AppContext.TryGetSwitch(SuppressUseHttpContextAsAuthorizationResource, out var enabled) && enabled; // Property key is used by Endpoint routing to determine if Authorization has run private const string AuthorizationMiddlewareInvokedWithEndpointKey = "__AuthorizationMiddlewareWithEndpointInvoked"; private static readonly object AuthorizationMiddlewareWithEndpointInvokedValue = new object(); // Marker stored in the policy cache for endpoints without authorization metadata. It is never evaluated. private static readonly AuthorizationPolicy NoAuthorizationMetadataPolicy = new( new[] { new AssertionRequirement(static _ => false) }, Array.Empty<string>()); private readonly RequestDelegate _next; private readonly IAuthorizationPolicyProvider _policyProvider; private readonly bool _canCache; private readonly AuthorizationPolicyCache? _policyCache; private readonly ILogger<AuthorizationMiddleware>? _logger; /// <summary> /// Initializes a new instance of <see cref="AuthorizationMiddleware"/>. /// </summary> /// <param name="next">The next middleware in the application middleware pipeline.</param> /// <param name="policyProvider">The <see cref="IAuthorizationPolicyProvider"/>.</param> public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider) { _next = next ?? throw new ArgumentNullException(nameof(next)); _policyProvider = policyProvider ?? throw new ArgumentNullException(nameof(policyProvider)); _canCache = false; } /// <summary> /// Initializes a new instance of <see cref="AuthorizationMiddleware"/>. /// </summary> /// <param name="next">The next middleware in the application middleware pipeline.</param> /// <param name="policyProvider">The <see cref="IAuthorizationPolicyProvider"/>.</param> /// <param name="services">The <see cref="IServiceProvider"/>.</param> /// <param name="logger">The <see cref="ILogger"/>.</param> public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider, IServiceProvider services, ILogger<AuthorizationMiddleware> logger) : this(next, policyProvider, services) { _logger = logger; } /// <summary> /// Initializes a new instance of <see cref="AuthorizationMiddleware"/>. /// </summary> /// <param name="next">The next middleware in the application middleware pipeline.</param> /// <param name="policyProvider">The <see cref="IAuthorizationPolicyProvider"/>.</param> /// <param name="services">The <see cref="IServiceProvider"/>.</param> public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider, IServiceProvider services) : this(next, policyProvider) { ArgumentNullException.ThrowIfNull(services); if (_policyProvider.AllowsCachingPolicies) { _policyCache = services.GetService<AuthorizationPolicyCache>(); _canCache = _policyCache != null; } } /// <summary> /// Invokes the middleware performing authorization. /// </summary> /// <param name="context">The <see cref="HttpContext"/>.</param> public async Task Invoke(HttpContext context) { ArgumentNullException.ThrowIfNull(context); var endpoint = context.GetEndpoint(); if (endpoint != null) { // EndpointRoutingMiddleware uses this flag to check if the Authorization middleware processed auth metadata on the endpoint. // The Authorization middleware can only make this claim if it observes an actual endpoint. context.Items[AuthorizationMiddlewareInvokedWithEndpointKey] = AuthorizationMiddlewareWithEndpointInvokedValue; } // Use the computed policy for this endpoint if we can AuthorizationPolicy? policy = null; var canCachePolicy = _canCache && endpoint != null; var hasNoAuthorizationMetadata = false; if (canCachePolicy) { policy = _policyCache!.Lookup(endpoint!); if (ReferenceEquals(policy, NoAuthorizationMetadataPolicy)) { // The endpoint has no authorization metadata, so the combined policy is just the fallback policy. // The fallback policy isn't cached while it is null so that it can still be picked up if it changes. hasNoAuthorizationMetadata = true; policy = await _policyProvider.GetFallbackPolicyAsync(); if (policy != null) { _policyCache.Store(endpoint!, policy); } } } if (policy == null && !hasNoAuthorizationMetadata) { // The middleware evaluates all the authorization metadata associated with the endpoint at once. // IMPORTANT: Changes to authorization logic should be mirrored in MVC's AuthorizeFilter var metadata = (IEnumerable<object>?)endpoint?.Metadata ?? Array.Empty<object>(); policy = await AuthorizationPolicy.CombineAsync(_policyProvider, metadata); // Cache the computed policy if (canCachePolicy) { if (policy != null) { _policyCache!.Store(endpoint!, policy); } else { // A null policy means the endpoint has no authorization metadata and there is no fallback policy. // Remember that the endpoint has no authorization metadata to avoid rescanning its metadata on every request. // Only add the marker if nothing is cached yet so a policy cached by a concurrent request isn't replaced. _policyCache!.TryAdd(endpoint!, NoAuthorizationMetadataPolicy); } } } if (policy == null) { await _next(context); return; } // Policy evaluator has transient lifetime so it's fetched from request services instead of injecting in constructor var policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>(); var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, context); if (authenticateResult?.Succeeded ?? false) { if (context.Features.Get<IAuthenticateResultFeature>() is IAuthenticateResultFeature authenticateResultFeature) { authenticateResultFeature.AuthenticateResult = authenticateResult; } else { var authFeatures = new AuthenticationFeatures(authenticateResult); context.Features.Set<IHttpAuthenticationFeature>(authFeatures); context.Features.Set<IAuthenticateResultFeature>(authFeatures); } } // Allow Anonymous still wants to run authorization to populate the User but skips any failure/challenge handling if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null) { await _next(context); return; } if (authenticateResult != null && !authenticateResult.Succeeded && _logger is ILogger log && log.IsEnabled(LogLevel.Debug)) { log.LogDebug("Policy authentication schemes {policyName} did not succeed", String.Join(", ", policy.AuthenticationSchemes)); } object? resource; if (_suppressUseHttpContextAsAuthorizationResource) { resource = endpoint; } else { resource = context; } var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult!, context, resource); var authorizationMiddlewareResultHandler = context.RequestServices.GetRequiredService<IAuthorizationMiddlewareResultHandler>(); await authorizationMiddlewareResultHandler.HandleAsync(_next, context, policy, authorizeResult); } }