From 4bc4d0e0d15cd785fb00f6e5e8c4f475a4f38963 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:29:07 -0400 Subject: [PATCH 01/11] Policy-based authorization article overhaul --- aspnetcore/migration/22-to-30.md | 2 +- .../security/authorization/resource-based.md | 2 +- .../security/authorization/resource-based.md | 2 +- aspnetcore/release-notes/aspnetcore-5.0.md | 2 +- ...cies-with-iauthorizationrequirementdata.md | 4 +- .../authorization/dependencyinjection.md | 4 +- .../iauthorizationpolicyprovider.md | 2 +- aspnetcore/security/authorization/policies.md | 399 +++++++++++++++--- .../policies/includes/policies5.md | 245 ----------- .../stubs/copy_of_IAuthorizationService.cs | 64 --- .../security/authorization/resource-based.md | 2 +- .../how-to-choose-identity-solution.md | 2 +- 12 files changed, 355 insertions(+), 375 deletions(-) delete mode 100644 aspnetcore/security/authorization/policies/includes/policies5.md delete mode 100644 aspnetcore/security/authorization/policies/samples/stubs/copy_of_IAuthorizationService.cs diff --git a/aspnetcore/migration/22-to-30.md b/aspnetcore/migration/22-to-30.md index c1bc3cde7fb4..61d0c201acd5 100644 --- a/aspnetcore/migration/22-to-30.md +++ b/aspnetcore/migration/22-to-30.md @@ -841,7 +841,7 @@ Protection is implemented for some scenarios. Endpoints Middleware throws an exc #### Custom authorization handlers -If the app uses custom [authorization handlers](xref:security/authorization/policies#security-authorization-policies-based-authorization-handler), endpoint routing passes a different resource type to handlers than MVC. Handlers that expect the authorization handler context resource to be of type (the resource type [provided by MVC filters](xref:security/authorization/policies#access-mvc-request-context-in-handlers)) will need to be updated to handle resources of type (the resource type given to authorization handlers by endpoint routing). +If the app uses custom [authorization handlers](xref:security/authorization/policies#authorization-handlers), endpoint routing passes a different resource type to handlers than MVC. Handlers that expect the authorization handler context resource to be of type (the resource type [provided by MVC filters](xref:security/authorization/policies#access-mvc-request-context-in-handlers)) will need to be updated to handle resources of type (the resource type given to authorization handlers by endpoint routing). MVC still uses `AuthorizationFilterContext` resources, so if the app uses MVC authorization filters along with endpoint routing authorization, it may be necessary to handle both types of resources. diff --git a/aspnetcore/mvc/security/authorization/resource-based.md b/aspnetcore/mvc/security/authorization/resource-based.md index a126a037e2c8..e4efce7e33b7 100644 --- a/aspnetcore/mvc/security/authorization/resource-based.md +++ b/aspnetcore/mvc/security/authorization/resource-based.md @@ -84,7 +84,7 @@ public async Task Edit(Guid documentId) ## Create a resource-based handler -Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#security-authorization-policies-based-authorization-handler). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see the [Policy-based authorization: Requirements](xref:security/authorization/policies#requirements). +Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#authorization-handlers). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see the [Policy-based authorization: Requirements](xref:security/authorization/policies#requirements). The handler class specifies the requirement and resource type. The following example demonstrates a handler utilizing a `SameAuthorRequirement` requirement and a `Document` resource: diff --git a/aspnetcore/razor-pages/security/authorization/resource-based.md b/aspnetcore/razor-pages/security/authorization/resource-based.md index 27d9dfb9abab..7e35f212f944 100644 --- a/aspnetcore/razor-pages/security/authorization/resource-based.md +++ b/aspnetcore/razor-pages/security/authorization/resource-based.md @@ -83,7 +83,7 @@ public async Task OnGetAsync(Guid documentId) ## Create a resource-based handler -Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#security-authorization-policies-based-authorization-handler). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see the [Policy-based authorization: Requirements](xref:security/authorization/policies#requirements). +Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#authorization-handlers). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see the [Policy-based authorization: Requirements](xref:security/authorization/policies#requirements). The handler class specifies the requirement and resource type. The following example demonstrates a handler utilizing a `SameAuthorRequirement` requirement and a `Document` resource: diff --git a/aspnetcore/release-notes/aspnetcore-5.0.md b/aspnetcore/release-notes/aspnetcore-5.0.md index f37f2e8ec496..c56408c76aca 100644 --- a/aspnetcore/release-notes/aspnetcore-5.0.md +++ b/aspnetcore/release-notes/aspnetcore-5.0.md @@ -344,7 +344,7 @@ Prior to .NET 5, building and publishing a *Dockerfile* for an ASP.NET Core app ### Microsoft Entra ID authentication with Microsoft.Identity.Web -The ASP.NET Core project templates now integrate with to handle authentication with [Microsoft Entra ID](/azure/active-directory/fundamentals/active-directory-whatis). The [Microsoft.Identity.Web package](https://www.nuget.org/packages/Microsoft.Identity.Web/) provides: +The ASP.NET Core project templates now integrate with to handle authentication with [Microsoft Entra ID](/entra/fundamentals/what-is-entra). The [Microsoft.Identity.Web package](https://www.nuget.org/packages/Microsoft.Identity.Web/) provides: * A better experience for authentication through Microsoft Entra ID. * An easier way to access Azure resources on behalf of your users, including [Microsoft Graph](/graph/overview). See the [Microsoft.Identity.Web sample](https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2), which starts with a basic login and advances through multi-tenancy, using Azure APIs, using Microsoft Graph, and protecting your own APIs. `Microsoft.Identity.Web` is available alongside .NET 5. diff --git a/aspnetcore/security/authorization/custom-authorization-policies-with-iauthorizationrequirementdata.md b/aspnetcore/security/authorization/custom-authorization-policies-with-iauthorizationrequirementdata.md index b04e2b3fce54..31bd43a8037c 100644 --- a/aspnetcore/security/authorization/custom-authorization-policies-with-iauthorizationrequirementdata.md +++ b/aspnetcore/security/authorization/custom-authorization-policies-with-iauthorizationrequirementdata.md @@ -114,8 +114,8 @@ You can decode the token in an online JWT decoder, such as [`jwt.ms`](https://jw "alg": "HS256", "typ": "JWT" }.{ - "unique_name": "guard", - "sub": "guard", + "unique_name": "{USER}", + "sub": "{USER}", "jti": "6cd613ed", "birthdate": "1989-01-01", "aud": [ diff --git a/aspnetcore/security/authorization/dependencyinjection.md b/aspnetcore/security/authorization/dependencyinjection.md index 8f6ab0513514..d38956ee7af7 100644 --- a/aspnetcore/security/authorization/dependencyinjection.md +++ b/aspnetcore/security/authorization/dependencyinjection.md @@ -11,7 +11,7 @@ uid: security/authorization/dependencyinjection :::moniker range=">= aspnetcore-6.0" -[Authorization handlers must be registered](xref:security/authorization/policies#security-authorization-policies-based-handler-registration) in the service collection during configuration using [dependency injection](xref:fundamentals/dependency-injection). +[Authorization handlers must be registered](xref:security/authorization/policies#handler-registration) in the service collection during configuration using [dependency injection](xref:fundamentals/dependency-injection). Suppose you had a repository of rules you wanted to evaluate inside an authorization handler and that repository was registered in the service collection. Authorization resolves and injects that into the constructor. @@ -52,7 +52,7 @@ An instance of the handler is created when the app starts, and DI injects the re :::moniker range="< aspnetcore-6.0" -[Authorization handlers must be registered](xref:security/authorization/policies#security-authorization-policies-based-handler-registration) in the service collection during configuration using [dependency injection](xref:fundamentals/dependency-injection). +[Authorization handlers must be registered](xref:security/authorization/policies#handler-registration) in the service collection during configuration using [dependency injection](xref:fundamentals/dependency-injection). Suppose you had a repository of rules you wanted to evaluate inside an authorization handler and that repository was registered in the service collection. Authorization resolves and injects that into the constructor. diff --git a/aspnetcore/security/authorization/iauthorizationpolicyprovider.md b/aspnetcore/security/authorization/iauthorizationpolicyprovider.md index e4151e1f12b4..6b9fc06a8cf8 100644 --- a/aspnetcore/security/authorization/iauthorizationpolicyprovider.md +++ b/aspnetcore/security/authorization/iauthorizationpolicyprovider.md @@ -180,7 +180,7 @@ public Task GetFallbackPolicyAsync() => To use custom policies from an `IAuthorizationPolicyProvider`, you ***must***: -* Register the appropriate `AuthorizationHandler` types with dependency injection (described in [policy-based authorization](xref:security/authorization/policies#security-authorization-policies-based-authorization-handler)), as with all policy-based authorization scenarios. +* Register the appropriate `AuthorizationHandler` types with dependency injection (described in [policy-based authorization](xref:security/authorization/policies#authorization-handlers)), as with all policy-based authorization scenarios. * Register the custom `IAuthorizationPolicyProvider` type in the application dependency injection service collection in `Startup.ConfigureServices` and replace the default policy provider. diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index 29008a4951a7..f7265d43f789 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -6,18 +6,38 @@ description: Learn how to create and use authorization policy handlers for enfor monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 02/17/2026 +ms.date: 06/05/2026 uid: security/authorization/policies --- # Policy-based authorization in ASP.NET Core -:::moniker range=">= aspnetcore-6.0" +An ASP.NET Core authorization policy is a named set of one or more authorization requirements that the framework evaluates to decide whether a user is allowed to access a resource. + +This article explains: + +* Registering and applying policies. +* Authorization handlers for single and multiple requirement evaluation. +* How multiple requirements in a single policy are evaluated. + +In practice, you apply a policy with [Authorize(Policy = "...")] or RequireAuthorization(...), and ASP.NET Core uses handlers to evaluate the requirements behind that policy. If you need policies generated dynamically instead of registered up front, that’s where IAuthorizationPolicyProvider comes in. Underneath the covers, [role-based authorization](xref:security/authorization/roles) and [claim-based authorization](xref:security/authorization/claims) use a requirement, a requirement handler, and a preconfigured policy. These building blocks support the expression of authorization evaluations in code. The result is a richer, reusable, testable authorization structure. +:::moniker range=">= aspnetcore-6.0" + An authorization policy consists of one or more requirements. Register it as part of the authorization service configuration, in the app's `Program.cs` file: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Program.cs" range="20-23,29"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Program.cs" range="20-23,29"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +An authorization policy consists of one or more requirements. It's registered as part of the authorization service configuration, in the `Startup.ConfigureServices` method: + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/3.0PoliciesAuthApp1/Startup.cs" range="31-32,39-40,42-45, 53, 58"::: + +:::moniker-end In the preceding example, an "AtLeast21" policy is created. It has a single requirement—that of a minimum age, which is supplied as a parameter to the requirement. @@ -25,7 +45,74 @@ In the preceding example, an "AtLeast21" policy is created. It has a single requ The primary service that determines if authorization is successful is : -:::code language="csharp" source="~/security/authorization/policies/samples/stubs/copy_of_IAuthorizationService.cs" id="snippet" highlight="24-25,48-49"::: +STUBS (the highlights were for 24-25 and 48-49)... + +```csharp +// THIS IS A COPY OF https://github.com/aspnet/AspNetCore/blob/v2.2.4/src/Security/Authorization/Core/src/IAuthorizationService.cs +// USED FOR DOCUMENTAION +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Authorization +{ + // + /// + /// Checks policy based permissions for a user + /// + public interface IAuthorizationService + { + /// + /// Checks if a user meets a specific set of requirements for the specified resource + /// + /// The user to evaluate the requirements against. + /// + /// An optional resource the policy should be checked with. + /// If a resource is not required for policy evaluation you may pass null as the value + /// + /// The requirements to evaluate. + /// + /// A flag indicating whether authorization has succeeded. + /// This value is true when the user fulfills the policy; + /// otherwise false. + /// + /// + /// Resource is an optional parameter and may be null. Please ensure that you check + /// it is not null before acting upon it. + /// + Task AuthorizeAsync(ClaimsPrincipal user, object resource, + IEnumerable requirements); + + /// + /// Checks if a user meets a specific authorization policy + /// + /// The user to check the policy against. + /// + /// An optional resource the policy should be checked with. + /// If a resource is not required for policy evaluation you may pass null as the value + /// + /// The name of the policy to check against a specific + /// context. + /// + /// A flag indicating whether authorization has succeeded. + /// Returns a flag indicating whether the user, and optional resource has fulfilled + /// the policy. + /// true when the policy has been fulfilled; + /// otherwise false. + /// + /// + /// Resource is an optional parameter and may be null. Please ensure that you check + /// it is not null before acting upon it. + /// + Task AuthorizeAsync( + ClaimsPrincipal user, object resource, string policyName); + } + // +} +``` The preceding code highlights the two methods of the [IAuthorizationService](https://github.com/dotnet/AspNetCore/blob/v2.2.4/src/Security/Authorization/Core/src/IAuthorizationService.cs). @@ -81,6 +168,8 @@ public async Task AuthorizeAsync(ClaimsPrincipal user, The following code shows a typical authorization service configuration: +:::moniker range=">= aspnetcore-6.0" + ```csharp // Add all of your handlers to DI. builder.Services.AddSingleton(); @@ -94,27 +183,71 @@ builder.Services.AddAuthorization(options => policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything"))); ``` -Use , `[Authorize(Policy = "Something")]`, or `RequireAuthorization("Something")` for authorization. +:::moniker-end - +:::moniker range="< aspnetcore-6.0" -## Apply policies to MVC controllers +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // Add all of your handlers to DI. + services.AddSingleton(); + // MyHandler2, ... + + services.AddSingleton(); -For apps that use Razor Pages, see the [Apply policies to Razor Pages](#apply-policies-to-razor-pages) section. + // Configure your policies + services.AddAuthorization(options => + options.AddPolicy("Something", + policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything"))); + + + services.AddControllersWithViews(); + services.AddRazorPages(); +} +``` + +:::moniker-end + +Use or `[Authorize(Policy = "Something")]` for authorization. + +## Apply policies to MVC controllers Apply policies to controllers by using the `[Authorize]` attribute with the policy name: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Controllers/AtLeast21Controller.cs" id="snippet" highlight="1"::: +:::moniker range=">= aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Controllers/AtLeast21Controller.cs" id="snippet" highlight="1"::: If multiple policies are applied at the controller and action levels, ***all*** policies must pass before access is granted: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Controllers/AtLeast21Controller2.cs" id="snippet" highlight="1,4"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Controllers/AtLeast21Controller2.cs" id="snippet" highlight="1,4"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +Policies are applied to controllers by using the `[Authorize]` attribute with the policy name. For example: + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Controllers/AlcoholPurchaseController.cs" id="snippet_AlcoholPurchaseControllerClass" highlight="4"::: + +:::moniker-end ## Apply policies to Razor Pages Apply policies to Razor Pages by using the `[Authorize]` attribute with the policy name. For example: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Pages/AtLeast21.cshtml.cs" highlight="6"::: +:::moniker range=">= aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Pages/AtLeast21.cshtml.cs" highlight="6"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp2/Pages/AlcoholPurchase.cshtml.cs" id="snippet_AlcoholPurchaseModelClass" highlight="4"::: + +:::moniker-end Policies can ***not*** be applied at the Razor Page handler level, they must be applied to the Page. @@ -124,36 +257,50 @@ Policies can also be applied to Razor Pages by using an [authorization conventio Apply policies to endpoints by using with the policy name. For example: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Program.cs" id="snippet_requireAuthorization"::: - - +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Program.cs" id="snippet_requireAuthorization"::: ## Requirements An authorization requirement is a collection of data parameters that a policy can use to evaluate the current user principal. In our "AtLeast21" policy, the requirement is a single parameter—the minimum age. A requirement implements , which is an empty marker interface. A parameterized minimum age requirement could be implemented as follows: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Policies/Requirements/MinimumAgeRequirement.cs"::: +:::moniker range=">= aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Requirements/MinimumAgeRequirement.cs"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Requirements/MinimumAgeRequirement.cs" id="snippet_MinimumAgeRequirementClass"::: + +:::moniker-end If an authorization policy contains multiple authorization requirements, all requirements must pass in order for the policy evaluation to succeed. In other words, multiple authorization requirements added to a single authorization policy are treated on an **AND** basis. > [!NOTE] > A requirement doesn't need to have data or properties. - - ## Authorization handlers An authorization handler is responsible for the evaluation of a requirement's properties. The authorization handler evaluates the requirements against a provided to determine if access is allowed. -A requirement can have [multiple handlers](#security-authorization-policies-based-multiple-handlers). A handler may inherit , where `TRequirement` is the requirement to be handled. Alternatively, a handler may implement directly to handle more than one type of requirement. +A requirement can have [multiple handlers](#why-would-i-want-multiple-handlers-for-a-requirement?). A handler may inherit , where `TRequirement` is the requirement to be handled. Alternatively, a handler may implement directly to handle more than one type of requirement. ### Use a handler for one requirement - - The following example shows a one-to-one relationship in which a minimum age handler handles a single requirement: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Policies/Handlers/MinimumAgeHandler.cs"::: +:::moniker range=">= aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/MinimumAgeHandler.cs"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/MinimumAgeHandler.cs" id="snippet_MinimumAgeHandlerClass"::: + +:::moniker-end The preceding code determines if the current user principal has a date of birth claim that has been issued by a known and trusted Issuer. Authorization can't occur when the claim is missing, in which case a completed task is returned. When a claim is present, the user's age is calculated. If the user meets the minimum age defined by the requirement, authorization is considered successful. When authorization is successful, `context.Succeed` is invoked with the satisfied requirement as its sole parameter. @@ -161,17 +308,35 @@ The preceding code determines if the current user principal has a date of birth The following example shows a one-to-many relationship in which a permission handler can handle three different types of requirements: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Policies/Handlers/PermissionHandler.cs"::: +:::moniker range=">= aspnetcore-6.0" -The preceding code traverses —a property containing requirements not marked as successful. For a `ReadPermission` requirement, the user must be either an owner or a sponsor to access the requested resource. For an `EditPermission` or `DeletePermission` requirement, they must be an owner to access the requested resource. +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/PermissionHandler.cs"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" - +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/PermissionHandler.cs" id="snippet_PermissionHandlerClass"::: + +:::moniker-end + +The preceding code traverses —a property containing requirements not marked as successful. For a `ReadPermission` requirement, the user must be either an owner or a sponsor to access the requested resource. For an `EditPermission` or `DeletePermission` requirement, they must be an owner to access the requested resource. ### Handler registration Register handlers in the services collection during configuration. For example: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Program.cs" id="snippet_minimumAgeHandlerRegistration"::: +:::moniker range=">= aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Program.cs" id="snippet_minimumAgeHandlerRegistration"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/3.0PoliciesAuthApp1/Startup.cs" range="31-32,39-40,42-45,53-55,58"::: + +:::moniker-end The preceding code registers `MinimumAgeHandler` as a singleton. Handlers can be registered using any of the built-in [service lifetimes](xref:fundamentals/dependency-injection#service-lifetimes). @@ -181,7 +346,7 @@ See the implementation of the [!NOTE] > Authorization handlers are called even if authentication fails. Also handlers can execute in any order, so do ***not*** depend on them being called in any particular order. - - ## Why would I want multiple handlers for a requirement? In cases where you want evaluation to be on an **OR** basis, implement multiple handlers for a single requirement. For example, Microsoft has doors that only open with key cards. If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. In this scenario, you'd have a single requirement, *BuildingEntry*, but multiple handlers, each one examining a single requirement. +:::moniker range=">= aspnetcore-6.0" + +`BuildingEntryRequirement.cs` + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Requirements/BuildingEntryRequirement.cs"::: + +`BadgeEntryHandler.cs` + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/BadgeEntryHandler.cs"::: + +`TemporaryStickerHandler.cs` + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/TemporaryStickerHandler.cs"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + `BuildingEntryRequirement.cs` -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Policies/Requirements/BuildingEntryRequirement.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Requirements/BuildingEntryRequirement.cs" id="snippet_BuildingEntryRequirementClass"::: `BadgeEntryHandler.cs` -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Policies/Handlers/BadgeEntryHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/BadgeEntryHandler.cs" id="snippet_BadgeEntryHandlerClass"::: `TemporaryStickerHandler.cs` -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Policies/Handlers/TemporaryStickerHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/TemporaryStickerHandler.cs" id="snippet_TemporaryStickerHandlerClass"::: -Ensure that both handlers are [registered](xref:security/authorization/policies#security-authorization-policies-based-handler-registration). If either handler succeeds when a policy evaluates the `BuildingEntryRequirement`, the policy evaluation succeeds. +:::moniker-end - +Ensure that both handlers are [registered](xref:security/authorization/policies#handler-registration). If either handler succeeds when a policy evaluates the `BuildingEntryRequirement`, the policy evaluation succeeds. ## Use a func to fulfill a policy @@ -222,9 +403,17 @@ There may be situations in which fulfilling a policy is simple to express in cod For example, the previous `BadgeEntryHandler` could be rewritten as follows: -:::code language="csharp" source="~/security/authorization/policies/samples/6.0/AuthorizationPoliciesSample/Program.cs" range="20-21,25-29"::: +:::moniker range=">= aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Program.cs" range="20-21,25-29"::: - +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/3.0PoliciesAuthApp1/Startup.cs" range="42-43,47-53"::: + +:::moniker-end ## Access MVC request context in handlers @@ -258,41 +447,141 @@ if (context.Resource is AuthorizationFilterContext mvcContext) For information on how to require authentication for all app users, see . - +:::moniker range=">= aspnetcore-6.0" -## Authorization with external service sample +## Authorization via an external service sample -The sample code on [AspNetCore.Docs.Samples](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/samples/aspnetcore-authz-with-ext-authz-service) shows how to implement additional authorization requirements with an external authorization service. The sample `Contoso.API` project is secured with [Azure AD](/azure/active-directory/fundamentals/active-directory-whatis). An additional authorization check from the `Contoso.Security.API` project returns a payload describing whether the `Contoso.API` client app can invoke the `GetWeather` API. +The [Authorization via an external service sample (`dotnet/AspNetCore.Docs.Samples` GitHub repository)](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/security/authorization/AuthorizationExternalService) shows how to implement additional authorization requirements with an external authorization service. The solution's `Contoso.API` project is secured with [Microsoft Entra ID](/entra/fundamentals/what-is-entra). An additional authorization check from the `Contoso.Security.API` project returns a payload describing whether the `Contoso.API` client app can invoke the `GetWeather` API. ### Configure the sample -* Create an [application registration](/azure/active-directory/develop/quickstart-register-app) in your [Microsoft Entra ID tenant](/azure/active-directory/develop/quickstart-create-new-tenant): +The following demonstration relies on using the Swagger UI or [cURL](https://curl.se/) in a command shell. + +In the `Contoso.Security.API` project, configure the `AllowedClients` placeholder of `{CLIENT ID (FOR THE CLIENT CALLING CONTOSO.API)}` of the with any test GUID value (for example, `00001111-aaaa-2222-bbbb-3333cccc4444`): + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthorizationExternalService/Contoso.Security.API/appsettings.json"::: + +Use [`dotnet user-jwts`](xref:security/authentication/jwt) to generate an access token with an `appid` claim for the client app's ID, which was created in the preceding step (for example, `00001111-aaaa-2222-bbbb-3333cccc4444`). + +```dotnetcli +dotnet user-jwts create --claim appid={GUID} +``` + +Example: + +```dotnetcli +dotnet user-jwts create --claim appid=00001111-aaaa-2222-bbbb-3333cccc4444 +``` + +The output produces a token after "`Token:`" in the command shell: + +```dotnetcli +New JWT saved with ID '{JWT ID}'. +Name: {USER} +Custom Claims: [appid=00001111-aaaa-2222-bbbb-3333cccc4444] + +Token: {TOKEN} +``` + +Set the value of the token (where the `{TOKEN}` placeholder appears in the preceding output) aside for use later. + +You can decode the token in an online JWT decoder, such as [`jwt.ms`](https://jwt.ms/) to see its contents, revealing that it contains an `appid` claim with the client app's ID: + +```json +{ + "alg": "HS256", + "typ": "JWT" +}.{ + "unique_name": "{USER}", + "sub": "{USER}", + "jti": "14ed7729", + "appid": "9e7b23cf-2f98-48b5-a681-42cb4fb0df68", + "aud": [ + "https://localhost:7250", + "http://localhost:7251" + ], + "nbf": 1780660887, + "exp": 1788609687, + "iat": 1780660888, + "iss": "dotnet-user-jwts" +}.[Signature] +``` + +Execute the command again with an incorrect client ID (`appid`) value: + +```dotnetcli +dotnet user-jwts create --claim appid=aaaabbbb-0000-cccc-1111-dddd2222eeee +``` + +Set the value of second token aside. + +Start both the `Contoso.API` and `Contoso.Security.API` projects in Visual Studio or with the `dotnet watch` command in a command shell: + +```dotnetcli +dotnet watch +``` - * Assign it an AppRole. - * Under API permissions, add the AppRole as a permission and grant Admin consent. Note that in this setup, this app registration represents both the API and the client invoking the API. If you like, you can create two app registrations. If you are using this setup, be sure to only perform the API permissions, add AppRole as a permission step for only the client. Only the client app registration requires a client secret to be generated. +# [Swagger UI](#tab/swagger-ui) -* Configure the `Contoso.API` project with the following settings: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/samples/aspnetcore-authz-with-ext-authz-service/Contoso.API/appsettings.json"::: -* Configure `Contoso.Security.API` with the following settings: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/samples/aspnetcore-authz-with-ext-authz-service/Contoso.Security.API/appsettings.json"::: -* Open the [ContosoAPI.collection.json](https://github.com/dotnet/AspNetCore.Docs.Samples/blob/main/samples/aspnetcore-authz-with-ext-authz-service/ContosoAPI.collection.json) file and configure an environment with the following: - * `ClientId`: Client Id from app registration representing the client calling the API. - * `clientSecret`: Client Secret from app registration representing the client calling the API. - * `TenantId`: Tenant Id from AAD properties +# [cURL in a command shell](#tab/curl-command-shell) -* Extract the commands from the `ContosoAPI.collection.json` file and use them to construct cURL commands to test the app. -* Run the solution and use [cURL](https://curl.se/) to invoke the API. You can add breakpoints in the `Contoso.Security.API.SecurityPolicyController` and observe the client Id is being passed in that is used to assert whether it is allowed to Get Weather. +In a command shell, use the .NET CLI to execute the following `curl.exe` command to request the `WeatherForecast` endpoint. Replace the `{TOKEN}` placeholder with the first JWT bearer token that you saved earlier: + +```dotnetcli +curl.exe -i -H "Authorization: Bearer {TOKEN}" https://localhost:7250/WeatherForecast +``` + +The output indicates success because the user's birth date claim indicates that they're at least 21 years old: + +```dotnetcli +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 +Date: Fri, 05 Jun 2026 12:48:58 GMT +Server: Kestrel +Transfer-Encoding: chunked + +[{ ... WEATHER DATA ... }] +``` + +Execute the command again using the second token with the invalid client ID (`appid`). The result indicates a policy failure: + +```dotnetcli +HTTP/1.1 403 Forbidden +Content-Length: 0 +Date: Fri, 05 Jun 2026 13:09:56 GMT +Server: Kestrel +``` + +You can add breakpoints in the `Contoso.Security.API.SecurityPolicyController` and observe the passed client ID (`appid`) is used to assert whether it is allowed to obtain weather data. + +You can also send the client ID directly to the `Contoso.Security.API` either via the Swagger UI or cURL in a command shell (for example: ``) to see it return either `true` or `false` for `canGetWeather` + +```dotnetcli +curl.exe -i -H "Authorization: Bearer {TOKEN}" https://localhost:7123/SecurityPolicy/9e7b23cf-2f98-48b5-a681-42cb4fb0df68 +``` + +With the correct client ID (`appid`), `canGetWeather` is `true`: + +```dotnetcli +HTTP/1.1 200 OK +Content-Type: application/json; charset=utf-8 +Date: Fri, 05 Jun 2026 13:19:49 GMT +Server: Kestrel +Transfer-Encoding: chunked + +{"canGetWeather":true} +``` + +--- ## Additional resources -* [Quickstart: Configure an application to expose a web API](/azure/active-directory/develop/quickstart-configure-app-expose-web-apis) -* [AspNetCore.Docs.Samples code](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/samples/aspnetcore-authz-with-ext-authz-service) +* [Quickstart: Configure an application to expose a web API](/entra/identity-platform/quickstart-configure-app-expose-web-apis) +* [Authorization via an external service sample (`dotnet/AspNetCore.Docs.Samples` GitHub repository)](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/security/authorization/AuthorizationExternalService) :::moniker-end - -[!INCLUDE[](~/security/authorization/policies/includes/policies5.md)] diff --git a/aspnetcore/security/authorization/policies/includes/policies5.md b/aspnetcore/security/authorization/policies/includes/policies5.md deleted file mode 100644 index 7f25640e8cf2..000000000000 --- a/aspnetcore/security/authorization/policies/includes/policies5.md +++ /dev/null @@ -1,245 +0,0 @@ -:::moniker range="< aspnetcore-6.0" - -Underneath the covers, [role-based authorization](xref:security/authorization/roles) and [claim-based authorization](xref:security/authorization/claims) use a requirement, a requirement handler, and a pre-configured policy. These building blocks support the expression of authorization evaluations in code. The result is a richer, reusable, testable authorization structure. - -An authorization policy consists of one or more requirements. It's registered as part of the authorization service configuration, in the `Startup.ConfigureServices` method: - -:::code language="csharp" source="~/security/authorization/policies/samples/3.0PoliciesAuthApp1/Startup.cs" range="31-32,39-40,42-45, 53, 58"::: - -In the preceding example, an "AtLeast21" policy is created. It has a single requirement—that of a minimum age, which is supplied as a parameter to the requirement. - -## IAuthorizationService - -The primary service that determines if authorization is successful is : - -:::code language="csharp" source="~/security/authorization/policies/samples/stubs/copy_of_IAuthorizationService.cs" id="snippet" highlight="24-25,48-49"::: - -The preceding code highlights the two methods of the [IAuthorizationService](https://github.com/dotnet/AspNetCore/blob/v2.2.4/src/Security/Authorization/Core/src/IAuthorizationService.cs). - - is a marker interface with no methods, and the mechanism for tracking whether authorization is successful. - -Each is responsible for checking if requirements are met: - - -```csharp -/// -/// Classes implementing this interface are able to make a decision if authorization -/// is allowed. -/// -public interface IAuthorizationHandler -{ - /// - /// Makes a decision if authorization is allowed. - /// - /// The authorization information. - Task HandleAsync(AuthorizationHandlerContext context); -} -``` - -The class is what the handler uses to mark whether requirements have been met: - -```csharp - context.Succeed(requirement) -``` - -The following code shows the simplified (and annotated with comments) default implementation of the authorization service: - -```csharp -public async Task AuthorizeAsync(ClaimsPrincipal user, - object resource, IEnumerable requirements) -{ - // Create a tracking context from the authorization inputs. - var authContext = _contextFactory.CreateContext(requirements, user, resource); - - // By default this returns an IEnumerable from DI. - var handlers = await _handlers.GetHandlersAsync(authContext); - - // Invoke all handlers. - foreach (var handler in handlers) - { - await handler.HandleAsync(authContext); - } - - // Check the context, by default success is when all requirements have been met. - return _evaluator.Evaluate(authContext); -} -``` - -The following code shows a typical `ConfigureServices`: - -```csharp -public void ConfigureServices(IServiceCollection services) -{ - // Add all of your handlers to DI. - services.AddSingleton(); - // MyHandler2, ... - - services.AddSingleton(); - - // Configure your policies - services.AddAuthorization(options => - options.AddPolicy("Something", - policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything"))); - - - services.AddControllersWithViews(); - services.AddRazorPages(); -} -``` - -Use or `[Authorize(Policy = "Something")]` for authorization. - - - -## Apply policies to MVC controller - -If you're using Razor Pages, see [Apply policies to Razor Pages](#apply-policies-to-razor-pages) in this document. - -Policies are applied to controllers by using the `[Authorize]` attribute with the policy name. For example: - -:::code language="csharp" source="~/security/authorization/policies/samples/PoliciesAuthApp1/Controllers/AlcoholPurchaseController.cs" id="snippet_AlcoholPurchaseControllerClass" highlight="4"::: - -## Apply policies to Razor Pages - -Policies are applied to Razor Pages by using the `[Authorize]` attribute with the policy name. For example: - -:::code language="csharp" source="~/security/authorization/policies/samples/PoliciesAuthApp2/Pages/AlcoholPurchase.cshtml.cs" id="snippet_AlcoholPurchaseModelClass" highlight="4"::: - -Policies can ***not*** be applied at the Razor Page handler level, they must be applied to the Page. - -Policies can be applied to Razor Pages by using an [authorization convention](xref:razor-pages/security/authorization/conventions). - - - -## Requirements - -An authorization requirement is a collection of data parameters that a policy can use to evaluate the current user principal. In our "AtLeast21" policy, the requirement is a single parameter—the minimum age. A requirement implements , which is an empty marker interface. A parameterized minimum age requirement could be implemented as follows: - -:::code language="csharp" source="~/security/authorization/policies/samples/PoliciesAuthApp1/Services/Requirements/MinimumAgeRequirement.cs" id="snippet_MinimumAgeRequirementClass"::: - -If an authorization policy contains multiple authorization requirements, all requirements must pass in order for the policy evaluation to succeed. In other words, multiple authorization requirements added to a single authorization policy are treated on an **AND** basis. - -> [!NOTE] -> A requirement doesn't need to have data or properties. - - - -## Authorization handlers - -An authorization handler is responsible for the evaluation of a requirement's properties. The authorization handler evaluates the requirements against a provided to determine if access is allowed. - -A requirement can have [multiple handlers](#security-authorization-policies-based-multiple-handlers). A handler may inherit , where `TRequirement` is the requirement to be handled. Alternatively, a handler may implement to handle more than one type of requirement. - -### Use a handler for one requirement - - - -The following example shows a one-to-one relationship in which a minimum age handler utilizes a single requirement: - -:::code language="csharp" source="~/security/authorization/policies/samples/PoliciesAuthApp1/Services/Handlers/MinimumAgeHandler.cs" id="snippet_MinimumAgeHandlerClass"::: - -The preceding code determines if the current user principal has a date of birth claim that has been issued by a known and trusted Issuer. Authorization can't occur when the claim is missing, in which case a completed task is returned. When a claim is present, the user's age is calculated. If the user meets the minimum age defined by the requirement, authorization is considered successful. When authorization is successful, `context.Succeed` is invoked with the satisfied requirement as its sole parameter. - -### Use a handler for multiple requirements - -The following example shows a one-to-many relationship in which a permission handler can handle three different types of requirements: - -:::code language="csharp" source="~/security/authorization/policies/samples/PoliciesAuthApp1/Services/Handlers/PermissionHandler.cs" id="snippet_PermissionHandlerClass"::: - -The preceding code traverses —a property containing requirements not marked as successful. For a `ReadPermission` requirement, the user must be either an owner or a sponsor to access the requested resource. For an `EditPermission` or `DeletePermission` requirement, the user must be an owner to access the requested resource. - - - -### Handler registration - -Handlers are registered in the services collection during configuration. For example: - -:::code language="csharp" source="~/security/authorization/policies/samples/3.0PoliciesAuthApp1/Startup.cs" range="31-32,39-40,42-45,53-55,58"::: - -The preceding code registers `MinimumAgeHandler` as a singleton by invoking `services.AddSingleton();`. Handlers can be registered using any of the built-in [service lifetimes](xref:fundamentals/dependency-injection#service-lifetimes). - -It's possible to bundle both a requirement and a handler in a single class implementing both and . This bundling creates a tight coupling between the handler and requirement and is only recommended for simple requirements and handlers. Creating a class that implements both interfaces removes the need to register the handler in DI because of the built-in that allows requirements to handle themselves. - -See the class for a good example where the `AssertionRequirement` is both a requirement and the handler in a fully self-contained class. - -## What should a handler return? - -Note that the `Handle` method in the [handler example](#security-authorization-handler-example) returns no value. How is a status of either success or failure indicated? - -* A handler indicates success by calling `context.Succeed(IAuthorizationRequirement requirement)`, passing the requirement that has been successfully validated. - -* A handler doesn't need to handle failures generally, as other handlers for the same requirement may succeed. - -* To guarantee failure, even if other requirement handlers succeed, call `context.Fail`. - -If a handler calls `context.Succeed` or `context.Fail`, all other handlers are still called. This allows requirements to produce side effects, such as logging, which takes place even if another handler has successfully validated or failed a requirement. When set to `false`, the property short-circuits the execution of handlers when `context.Fail` is called. `InvokeHandlersAfterFailure` defaults to `true`, in which case all handlers are called. - -> [!NOTE] -> Authorization handlers are called even if authentication fails. - - - -## Why would I want multiple handlers for a requirement? - -In cases where you want evaluation to be on an **OR** basis, implement multiple handlers for a single requirement. For example, Microsoft has doors that only open with key cards. If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. In this scenario, you'd have a single requirement, *BuildingEntry*, but multiple handlers, each one examining a single requirement. - -`BuildingEntryRequirement.cs` - -:::code language="csharp" source="~/security/authorization/policies/samples/PoliciesAuthApp1/Services/Requirements/BuildingEntryRequirement.cs" id="snippet_BuildingEntryRequirementClass"::: - -`BadgeEntryHandler.cs` - -:::code language="csharp" source="~/security/authorization/policies/samples/PoliciesAuthApp1/Services/Handlers/BadgeEntryHandler.cs" id="snippet_BadgeEntryHandlerClass"::: - -`TemporaryStickerHandler.cs` - -:::code language="csharp" source="~/security/authorization/policies/samples/PoliciesAuthApp1/Services/Handlers/TemporaryStickerHandler.cs" id="snippet_TemporaryStickerHandlerClass"::: - -Ensure that both handlers are [registered](xref:security/authorization/policies#security-authorization-policies-based-handler-registration). If either handler succeeds when a policy evaluates the `BuildingEntryRequirement`, the policy evaluation succeeds. - - - -## Use a func to fulfill a policy - -There may be situations in which fulfilling a policy is simple to express in code. It's possible to supply a `Func` when configuring your policy with the `RequireAssertion` policy builder. - -For example, the previous `BadgeEntryHandler` could be rewritten as follows: - -:::code language="csharp" source="~/security/authorization/policies/samples/3.0PoliciesAuthApp1/Startup.cs" range="42-43,47-53"::: - - - -## Access MVC request context in handlers - -The `HandleRequirementAsync` method you implement in an authorization handler has two parameters: an `AuthorizationHandlerContext` and the `TRequirement` you are handling. Frameworks such as MVC or SignalR are free to add any object to the `Resource` property on the `AuthorizationHandlerContext` to pass extra information. - -When using endpoint routing, authorization is typically handled by the Authorization Middleware. In this case, the `Resource` property is an instance of . The context can be used to access the current endpoint, which can be used to probe the underlying resource to which you're routing. For example: - -```csharp -if (context.Resource is HttpContext httpContext) -{ - var endpoint = httpContext.GetEndpoint(); - var actionDescriptor = endpoint.Metadata.GetMetadata(); - ... -} -``` - -With traditional routing, or when authorization happens as part of MVC's authorization filter, the value of `Resource` is an instance. This property provides access to `HttpContext`, `RouteData`, and everything else provided by MVC and Razor Pages. - -The use of the `Resource` property is framework-specific. Using information in the `Resource` property limits your authorization policies to particular frameworks. Cast the `Resource` property using the `is` keyword, and then confirm the cast has succeeded to ensure your code doesn't crash with an `InvalidCastException` when run on other frameworks: - -```csharp -// Requires the following import: -// using Microsoft.AspNetCore.Mvc.Filters; -if (context.Resource is AuthorizationFilterContext mvcContext) -{ - // Examine MVC-specific things like routing data. -} -``` - -## Globally require all users to be authenticated - -For information on how to require authentication for all app users, see . - -:::moniker-end diff --git a/aspnetcore/security/authorization/policies/samples/stubs/copy_of_IAuthorizationService.cs b/aspnetcore/security/authorization/policies/samples/stubs/copy_of_IAuthorizationService.cs deleted file mode 100644 index f7e0c54041e8..000000000000 --- a/aspnetcore/security/authorization/policies/samples/stubs/copy_of_IAuthorizationService.cs +++ /dev/null @@ -1,64 +0,0 @@ -// THIS IS A COPY OF https://github.com/aspnet/AspNetCore/blob/v2.2.4/src/Security/Authorization/Core/src/IAuthorizationService.cs -// USED FOR DOCUMENTAION -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Security.Claims; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Authorization -{ - // - /// - /// Checks policy based permissions for a user - /// - public interface IAuthorizationService - { - /// - /// Checks if a user meets a specific set of requirements for the specified resource - /// - /// The user to evaluate the requirements against. - /// - /// An optional resource the policy should be checked with. - /// If a resource is not required for policy evaluation you may pass null as the value - /// - /// The requirements to evaluate. - /// - /// A flag indicating whether authorization has succeeded. - /// This value is true when the user fulfills the policy; - /// otherwise false. - /// - /// - /// Resource is an optional parameter and may be null. Please ensure that you check - /// it is not null before acting upon it. - /// - Task AuthorizeAsync(ClaimsPrincipal user, object resource, - IEnumerable requirements); - - /// - /// Checks if a user meets a specific authorization policy - /// - /// The user to check the policy against. - /// - /// An optional resource the policy should be checked with. - /// If a resource is not required for policy evaluation you may pass null as the value - /// - /// The name of the policy to check against a specific - /// context. - /// - /// A flag indicating whether authorization has succeeded. - /// Returns a flag indicating whether the user, and optional resource has fulfilled - /// the policy. - /// true when the policy has been fulfilled; - /// otherwise false. - /// - /// - /// Resource is an optional parameter and may be null. Please ensure that you check - /// it is not null before acting upon it. - /// - Task AuthorizeAsync( - ClaimsPrincipal user, object resource, string policyName); - } - // -} \ No newline at end of file diff --git a/aspnetcore/security/authorization/resource-based.md b/aspnetcore/security/authorization/resource-based.md index 0a80de53a715..454169d3219f 100644 --- a/aspnetcore/security/authorization/resource-based.md +++ b/aspnetcore/security/authorization/resource-based.md @@ -80,7 +80,7 @@ protected override async Task OnParametersSetAsync() ## Create a resource-based handler -Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#security-authorization-policies-based-authorization-handler). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see [Policy-based authorization: Requirements](xref:security/authorization/policies#requirements). +Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#authorization-handlers). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see [Policy-based authorization: Requirements](xref:security/authorization/policies#requirements). The following demonstration `Document` class is used: diff --git a/aspnetcore/security/how-to-choose-identity-solution.md b/aspnetcore/security/how-to-choose-identity-solution.md index 23eac8ff6ca7..1e0bc51913e6 100644 --- a/aspnetcore/security/how-to-choose-identity-solution.md +++ b/aspnetcore/security/how-to-choose-identity-solution.md @@ -62,7 +62,7 @@ Some solutions are free and open source, while others are commercially licensed. ## Disconnected scenarios -Many solutions, such as [Microsoft Entra ID](/azure/active-directory/fundamentals/active-directory-whatis), are cloud-based and require an Internet connection to work. If your environment doesn't allow Internet connectivity, you won't be able to use the service. +Many solutions, such as [Microsoft Entra ID](/entra/fundamentals/what-is-entra), are cloud-based and require an Internet connection to work. If your environment doesn't allow Internet connectivity, you won't be able to use the service. ASP.NET Core Identity works perfectly well in disconnected scenarios, such as: From 97084b93154852fc7c32f1223f2c6a5b341fadac Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 5 Jun 2026 09:37:33 -0400 Subject: [PATCH 02/11] Updates --- aspnetcore/security/authorization/policies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index f7265d43f789..748e9e802cb2 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -284,7 +284,7 @@ If an authorization policy contains multiple authorization requirements, all req An authorization handler is responsible for the evaluation of a requirement's properties. The authorization handler evaluates the requirements against a provided to determine if access is allowed. -A requirement can have [multiple handlers](#why-would-i-want-multiple-handlers-for-a-requirement?). A handler may inherit , where `TRequirement` is the requirement to be handled. Alternatively, a handler may implement directly to handle more than one type of requirement. +A requirement can have [multiple handlers](#why-would-i-want-multiple-handlers-for-a-requirement). A handler may inherit , where `TRequirement` is the requirement to be handled. Alternatively, a handler may implement directly to handle more than one type of requirement. ### Use a handler for one requirement From d5bb9945bf0690d14e92be624008d6cc63145201 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:13:17 -0400 Subject: [PATCH 03/11] Updates --- aspnetcore/security/authorization/policies.md | 162 +++++------------- 1 file changed, 43 insertions(+), 119 deletions(-) diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index 748e9e802cb2..07fe8d2898f4 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -6,7 +6,7 @@ description: Learn how to create and use authorization policy handlers for enfor monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 06/05/2026 +ms.date: 06/11/2026 uid: security/authorization/policies --- # Policy-based authorization in ASP.NET Core @@ -19,104 +19,52 @@ This article explains: * Authorization handlers for single and multiple requirement evaluation. * How multiple requirements in a single policy are evaluated. -In practice, you apply a policy with [Authorize(Policy = "...")] or RequireAuthorization(...), and ASP.NET Core uses handlers to evaluate the requirements behind that policy. If you need policies generated dynamically instead of registered up front, that’s where IAuthorizationPolicyProvider comes in. +In practice, a policy is applied with `[Authorize(Policy = "...")]` or `RequireAuthorization(...)`, and the framework uses handlers to evaluate the requirements behind a policy. `IAuthorizationPolicyProvider` generates policies dynamically instead of registering them at app startup. -Underneath the covers, [role-based authorization](xref:security/authorization/roles) and [claim-based authorization](xref:security/authorization/claims) use a requirement, a requirement handler, and a preconfigured policy. These building blocks support the expression of authorization evaluations in code. The result is a richer, reusable, testable authorization structure. +[Role-based authorization](xref:security/authorization/roles) and [claim-based authorization](xref:security/authorization/claims) use a requirement, a requirement handler, and a preconfigured authorization policy. These building blocks support the expression of authorization evaluations in code. + +## Policy registration :::moniker range=">= aspnetcore-6.0" -An authorization policy consists of one or more requirements. Register it as part of the authorization service configuration, in the app's `Program.cs` file: +An authorization policy consists of one or more requirements. Register the policy as part of the authorization service configuration with in the app's `Program` file. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Program.cs" range="20-23,29"::: +```csharp +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("AtLeast21", policy => + policy.Requirements.Add(new MinimumAgeRequirement(21))); +}); +``` :::moniker-end :::moniker range="< aspnetcore-6.0" -An authorization policy consists of one or more requirements. It's registered as part of the authorization service configuration, in the `Startup.ConfigureServices` method: +An authorization policy consists of one or more requirements. Register the policy as part of the authorization service configuration with in the `Startup.ConfigureServices` method. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/3.0PoliciesAuthApp1/Startup.cs" range="31-32,39-40,42-45, 53, 58"::: +```csharp +services.AddAuthorization(options => +{ + options.AddPolicy("AtLeast21", policy => + policy.Requirements.Add(new MinimumAgeRequirement(21))); +}); +``` :::moniker-end -In the preceding example, an "AtLeast21" policy is created. It has a single requirement—that of a minimum age, which is supplied as a parameter to the requirement. - -## IAuthorizationService +## `IAuthorizationService` The primary service that determines if authorization is successful is : -STUBS (the highlights were for 24-25 and 48-49)... +* `AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable requirements)`: Checks if a user meets a specific set of authorization requirements for a specified resource. +* `AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)`: Checks if a user meets a specific authorization policy for a specified resource. -```csharp -// THIS IS A COPY OF https://github.com/aspnet/AspNetCore/blob/v2.2.4/src/Security/Authorization/Core/src/IAuthorizationService.cs -// USED FOR DOCUMENTAION -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +If a resource isn't required for policy evaluation, pass `null` for the resource. Confirm that the resource is assigned when acting on a resource-focused policy. -using System.Collections.Generic; -using System.Security.Claims; -using System.Threading.Tasks; +These methods return `true` when authorization succeeds and `false` when it fails. -namespace Microsoft.AspNetCore.Authorization -{ - // - /// - /// Checks policy based permissions for a user - /// - public interface IAuthorizationService - { - /// - /// Checks if a user meets a specific set of requirements for the specified resource - /// - /// The user to evaluate the requirements against. - /// - /// An optional resource the policy should be checked with. - /// If a resource is not required for policy evaluation you may pass null as the value - /// - /// The requirements to evaluate. - /// - /// A flag indicating whether authorization has succeeded. - /// This value is true when the user fulfills the policy; - /// otherwise false. - /// - /// - /// Resource is an optional parameter and may be null. Please ensure that you check - /// it is not null before acting upon it. - /// - Task AuthorizeAsync(ClaimsPrincipal user, object resource, - IEnumerable requirements); - - /// - /// Checks if a user meets a specific authorization policy - /// - /// The user to check the policy against. - /// - /// An optional resource the policy should be checked with. - /// If a resource is not required for policy evaluation you may pass null as the value - /// - /// The name of the policy to check against a specific - /// context. - /// - /// A flag indicating whether authorization has succeeded. - /// Returns a flag indicating whether the user, and optional resource has fulfilled - /// the policy. - /// true when the policy has been fulfilled; - /// otherwise false. - /// - /// - /// Resource is an optional parameter and may be null. Please ensure that you check - /// it is not null before acting upon it. - /// - Task AuthorizeAsync( - ClaimsPrincipal user, object resource, string policyName); - } - // -} -``` - -The preceding code highlights the two methods of the [IAuthorizationService](https://github.com/dotnet/AspNetCore/blob/v2.2.4/src/Security/Authorization/Core/src/IAuthorizationService.cs). - - is a marker interface with no methods, and the mechanism for tracking whether authorization is successful. + is a marker interface with no methods that serves as the mechanism for tracking whether authorization is successful. Each is responsible for checking if requirements are met: +Each is responsible for checking if requirements are met via . The class contains the authorization information used by . When is called with the , the policy is met: ```csharp -/// -/// Classes implementing this interface are able to make a decision if authorization -/// is allowed. -/// -public interface IAuthorizationHandler -{ - /// - /// Makes a decision if authorization is allowed. - /// - /// The authorization information. - Task HandleAsync(AuthorizationHandlerContext context); -} +context.Succeed(requirement); ``` -The class is what the handler uses to mark whether requirements have been met: - -```csharp -context.Succeed(requirement) -``` +The following code shows a typical authorization service configuration: -The following code shows a typical authorization service configuration: - :::moniker range=">= aspnetcore-6.0" ```csharp @@ -106,8 +92,9 @@ builder.Services.AddSingleton(); // Configure policies builder.Services.AddAuthorization(options => - options.AddPolicy("Something", - policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything"))); + options.AddPolicy("Something", + policy => + policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything"))); ``` :::moniker-end @@ -124,13 +111,23 @@ services.AddSingleton(); // Configure your policies services.AddAuthorization(options => options.AddPolicy("Something", - policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything"))); + policy => + policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything"))); ``` :::moniker-end Use or `[Authorize(Policy = "Something")]` for authorization. +## Apply policies in Razor components + + + + + + + + ## Apply policies to MVC controllers Apply policies to controllers by using the `[Authorize]` attribute with the policy name: From 8ea678b7908727f8501feffb7e0df3355267aab2 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:57:16 -0400 Subject: [PATCH 05/11] Updates --- aspnetcore/security/authorization/policies.md | 128 +++++++++--------- 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index d3062fd5962a..49de029faabc 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -6,7 +6,7 @@ description: Learn how to create and use authorization policy handlers for enfor monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 06/11/2026 +ms.date: 06/15/2026 uid: security/authorization/policies --- # Policy-based authorization in ASP.NET Core @@ -55,19 +55,14 @@ services.AddAuthorization(options => ## Authorization service interface (`IAuthorizationService`) -The primary service that determines if authorization is successful is . - - - - - when one of the overloads is called: + is primarily responsible for determining if authorization is successful when one of the overloads is called: * `AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable requirements)`: Checks if a user meets a specific set of authorization requirements for a specified resource. * `AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)`: Checks if a user meets a specific authorization policy for a specified resource. -If a resource isn't required for policy evaluation, pass `null` for the resource. Confirm that the resource is assigned when acting on a resource-focused policy. +If a resource isn't required for policy evaluation, pass `null` for the resource. When acting on a resource-focused policy, confirm that the resource is assigned. -These methods return `true` when authorization succeeds and `false` when it fails. +The preceding methods return `true` when authorization succeeds and `false` when it fails. is a marker interface with no methods that serves as the mechanism for tracking whether authorization is successful. @@ -77,24 +72,23 @@ Each is responsi context.Succeed(requirement); ``` -The following code shows a typical authorization service configuration: - +The following code demonstrates a typical authorization service configuration, which are further explained later in this article: +* Authorization handler registrations (`IAuthorizationHandler`) + * A custom handler represented by the `CustomHandler1` class is registered. + * A second custom handler represented by the `CustomHandler2` class is registered. +* Authorization policy (name = `Admin`) registration: The user must have `Administrator` and `SecurityPortal` claims to satisfy the policy. :::moniker range=">= aspnetcore-6.0" ```csharp -// Add all of your handlers to DI. -builder.Services.AddSingleton(); -// MyHandler2, ... - -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); -// Configure policies builder.Services.AddAuthorization(options => - options.AddPolicy("Something", + options.AddPolicy("Admin", policy => - policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything"))); + policy.RequireClaim("Administrator", "SecurityPortal"))); ``` :::moniker-end @@ -103,21 +97,19 @@ builder.Services.AddAuthorization(options => ```csharp // Add all of your handlers to DI. -services.AddSingleton(); -// MyHandler2, ... - -services.AddSingleton(); +services.AddSingleton(); +services.AddSingleton(); // Configure your policies services.AddAuthorization(options => - options.AddPolicy("Something", + options.AddPolicy("Admin", policy => - policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything"))); + policy.RequireClaim("Administrator", "SecurityPortal"))); ``` :::moniker-end -Use or `[Authorize(Policy = "Something")]` for authorization. +Use or `[Authorize(Policy = "{POLICY NAME}")]` for authorization, both of which are demonstrated later in this article. ## Apply policies in Razor components @@ -178,7 +170,7 @@ Apply policies to endpoints by using , which is an empty marker interface. A parameterized minimum age requirement could be implemented as follows: +An authorization requirement is a collection of data parameters that a policy can use to evaluate the current user principal. In our "AtLeast21" policy, the requirement is a single parameter, the minimum age. A requirement implements , which is an empty marker interface. A parameterized minimum age requirement could be implemented as follows: :::moniker range=">= aspnetcore-6.0" @@ -192,7 +184,7 @@ An authorization requirement is a collection of data parameters that a policy ca :::moniker-end -If an authorization policy contains multiple authorization requirements, all requirements must pass in order for the policy evaluation to succeed. In other words, multiple authorization requirements added to a single authorization policy are treated on an **AND** basis. +If an authorization policy contains multiple authorization requirements, all of the requirements must pass in order for the policy evaluation to succeed. In other words, multiple authorization requirements added to a single authorization policy are treated on an **AND** basis. > [!NOTE] > A requirement doesn't need to have data or properties. @@ -219,7 +211,7 @@ The following example shows a one-to-one relationship in which a minimum age han :::moniker-end -The preceding code determines if the current user principal has a date of birth claim that has been issued by a known and trusted Issuer. Authorization can't occur when the claim is missing, in which case a completed task is returned. When a claim is present, the user's age is calculated. If the user meets the minimum age defined by the requirement, authorization is considered successful. When authorization is successful, `context.Succeed` is invoked with the satisfied requirement as its sole parameter. +The preceding code determines if the current user principal has a date of birth claim that has been issued by a known and trusted issuer. Authorization can't occur when the claim is missing, in which case a completed task is returned. When a claim is present, the user's age is calculated. If the user meets the minimum age defined by the requirement, authorization is considered successful. When authorization is successful, [`context.Succeed`](xref:Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext.Succeed%2A) is invoked with the satisfied requirement as its sole parameter. ### Use a handler for multiple requirements @@ -241,7 +233,7 @@ The preceding code traverses (); :::moniker-end -The preceding code registers `MinimumAgeHandler` as a singleton. Handlers can be registered using any of the built-in [service lifetimes](xref:fundamentals/dependency-injection#service-lifetimes). +It's possible to bundle both a requirement and a handler into a single class implementing both and . This bundling creates a tight coupling between the handler and requirement and is only recommended for simple requirements and handlers. Creating a class that implements both interfaces removes the need to register the handler in the service container due to the built-in that allows requirements to handle themselves. -It's possible to bundle both a requirement and a handler into a single class implementing both and . This bundling creates a tight coupling between the handler and requirement and is only recommended for simple requirements and handlers. Creating a class that implements both interfaces removes the need to register the handler in DI because of the built-in that allows requirements to handle themselves. +See the [implementation of the ASP.NET Core `AssertionRequirement` class](https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authorization/Core/src/AssertionRequirement.cs) for a good example where the is both a requirement and the handler in a fully self-contained class. The framework's API allows you to validate access using inline lambda expressions instead of writing separate, boilerplate requirement and handler classes. -See the implementation of the class for a good example where the is both a requirement and the handler in a fully self-contained class. +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] ## What should a handler return? -Note that the `Handle` method in the [handler example](#use-a-handler-for-one-requirement) returns no value. How is a status of either success or failure indicated? +The `Handle` method in the [handler example](#use-a-handler-for-one-requirement) returns no value. How is a status of either success or failure indicated? -* A handler indicates success by calling `context.Succeed(IAuthorizationRequirement requirement)`, passing the requirement that has been successfully validated. +* A handler indicates success by calling [`context.Succeed`](xref:Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext.Succeed%2A), passing the successfully validated requirement (). -* A handler doesn't need to handle failures generally, as other handlers for the same requirement may succeed. +* A handler isn't required to handle failures generally, as other handlers for the same requirement may succeed. -* To guarantee failure, even if other requirement handlers succeed, call `context.Fail`. +* To guarantee failure, even if other requirement handlers succeed, call [`context.Fail`](xref:Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext.Fail%2A). -If a handler calls `context.Succeed` or `context.Fail`, all other handlers are still called. This allows requirements to produce side effects, such as logging, which takes place even if another handler has successfully validated or failed a requirement. When set to `false`, the property short-circuits the execution of handlers when `context.Fail` is called. `InvokeHandlersAfterFailure` defaults to `true`, in which case all handlers are called. +If a handler calls [`context.Succeed`](xref:Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext.Succeed%2A) or [`context.Fail`](xref:Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext.Fail%2A), all other handlers are still called. This allows requirements to produce side effects, such as logging, which takes place even if another handler successfully validates or fails on a requirement. When set to `false`, the property short-circuits the execution of handlers when [`context.Fail`](xref:Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext.Fail%2A) is called. defaults to `true`, in which case all handlers are called. > [!NOTE] -> Authorization handlers are called even if authentication fails. Also handlers can execute in any order, so do ***not*** depend on them being called in any particular order. +> Authorization handlers are called even if authentication fails. Also handlers can execute in any order, so do ***not*** depend on the order of calling handlers. ## Why would I want multiple handlers for a requirement? -In cases where you want evaluation to be on an **OR** basis, implement multiple handlers for a single requirement. For example, Microsoft has doors that only open with key cards. If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. In this scenario, you'd have a single requirement, *BuildingEntry*, but multiple handlers, each one examining a single requirement. +In cases where you want evaluation to be on an **OR** basis, implement multiple handlers for a single requirement. For example, Microsoft has doors that only open with key cards. If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. In this scenario, the app has a single requirement but multiple handlers, each one examining a single requirement. + +In the following example implementations: + +* `BuildingEntryRequirement` is the building entry requirement. +* `BadgeEntryHandler` (the individual has a badge) and `TemporaryStickerHandler` (the individual has a temporary sticker) are separate handlers, each examining a single requirement. :::moniker range=">= aspnetcore-6.0" -`BuildingEntryRequirement.cs` +`BuildingEntryRequirement.cs`: :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Requirements/BuildingEntryRequirement.cs"::: -`BadgeEntryHandler.cs` +`BadgeEntryHandler.cs`: :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/BadgeEntryHandler.cs"::: -`TemporaryStickerHandler.cs` +`TemporaryStickerHandler.cs`: :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/TemporaryStickerHandler.cs"::: @@ -302,27 +299,25 @@ In cases where you want evaluation to be on an **OR** basis, implement multiple :::moniker range="< aspnetcore-6.0" -`BuildingEntryRequirement.cs` +`BuildingEntryRequirement.cs`: :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Requirements/BuildingEntryRequirement.cs" id="snippet_BuildingEntryRequirementClass"::: -`BadgeEntryHandler.cs` +`BadgeEntryHandler.cs`: :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/BadgeEntryHandler.cs" id="snippet_BadgeEntryHandlerClass"::: -`TemporaryStickerHandler.cs` +`TemporaryStickerHandler.cs`: :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/TemporaryStickerHandler.cs" id="snippet_TemporaryStickerHandlerClass"::: :::moniker-end -Ensure that both handlers are [registered](xref:security/authorization/policies#handler-registration). If either handler succeeds when a policy evaluates the `BuildingEntryRequirement`, the policy evaluation succeeds. - -## Use a func to fulfill a policy +Ensure that both handlers are [registered](xref:security/authorization/policies#handler-registration). If either of the handlers succeed when a policy evaluates the `BuildingEntryRequirement`, the policy evaluation succeeds. -There may be situations in which fulfilling a policy is simple to express in code. It's possible to supply a `Func` when configuring a policy with the `RequireAssertion` policy builder. +## Use a `Func` to fulfill a policy -For example, the previous `BadgeEntryHandler` could be rewritten as follows: +There are situations where fulfilling a policy is simple to express in code with a `Func` delegate when configuring a policy with the `RequireAssertion` policy builder. For example, the preceding `BadgeEntryHandler` can be rewritten as follows: :::moniker range=">= aspnetcore-6.0" @@ -338,33 +333,37 @@ For example, the previous `BadgeEntryHandler` could be rewritten as follows: ## Access MVC request context in handlers -The `HandleRequirementAsync` method has two parameters: an `AuthorizationHandlerContext` and the `TRequirement` being handled. Frameworks such as MVC or SignalR are free to add any object to the `Resource` property on the `AuthorizationHandlerContext` to pass extra information. +The method has two parameters: an and the `TRequirement` being handled. Frameworks such as MVC or SignalR are free to add any object to the property to pass extra information. -When using endpoint routing, authorization is typically handled by the Authorization Middleware. In this case, the `Resource` property is an instance of . The context can be used to access the current endpoint, which can be used to probe the underlying resource to which you're routing. For example: +When using endpoint routing, authorization is typically handled by the Authorization Middleware, and the property is an instance of . The context is used to access the current endpoint, which can be used to probe the underlying resource to which you're routing: ```csharp if (context.Resource is HttpContext httpContext) { var endpoint = httpContext.GetEndpoint(); - var actionDescriptor = endpoint.Metadata.GetMetadata(); + var actionDescriptor = + endpoint.Metadata.GetMetadata(); ... } ``` -With traditional routing, or when authorization happens as part of MVC's authorization filter, the value of `Resource` is an instance. This property provides access to `HttpContext`, `RouteData`, and everything else provided by MVC and Razor Pages. +With traditional routing, or when authorization happens as part of MVC's authorization filter, the value of is an instance. This property provides access to , , and everything else provided by MVC and Razor Pages. -The use of the `Resource` property is framework-specific. Using information in the `Resource` property limits your authorization policies to particular frameworks. Cast the `Resource` property using the `is` keyword, and then confirm the cast has succeeded to ensure your code doesn't crash with an `InvalidCastException` when run on other frameworks: +The use of the property is framework-specific. Using information in the property limits your authorization policies to particular frameworks. Cast the property using the `is` keyword, and then confirm the cast has succeeded to ensure your code doesn't crash with an when run on other frameworks. When the cast succeeds, examine MVC-specific data, such as routing data: ```csharp -// Requires the following import: -// using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Filters; + +... + if (context.Resource is AuthorizationFilterContext mvcContext) { - // Examine MVC-specific things like routing data. + var routeValues = mvcContext.RouteData.Values; + ... } ``` -## Globally require all users to be authenticated +## Require global user authentication For information on how to require authentication for all app users, see . @@ -376,13 +375,13 @@ The [Authorization via an external service sample (`dotnet/AspNetCore.Docs.Sampl ### Configure the sample -The following demonstration relies on using the Swagger UI or [cURL](https://curl.se/) in a command shell. +The following demonstration relies on using the [Nswag (Swagger/OpenAPI)](https://github.com/RicoSuter/NSwag) or [cURL](https://curl.se/) in a command shell. In the `Contoso.Security.API` project, configure the `AllowedClients` placeholder of `{CLIENT ID (FOR THE CLIENT CALLING CONTOSO.API)}` of the with any test GUID value (for example, `00001111-aaaa-2222-bbbb-3333cccc4444`): :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthorizationExternalService/Contoso.Security.API/appsettings.json"::: -Use [`dotnet user-jwts`](xref:security/authentication/jwt) to generate an access token with an `appid` claim for the client app's ID, which was created in the preceding step (for example, `00001111-aaaa-2222-bbbb-3333cccc4444`). +In a command shell opened to the `Contoso.API` project, use [`dotnet user-jwts`](xref:security/authentication/jwt) to generate an access token with an `appid` claim for the client app's ID, which was created in the preceding step (for example, `00001111-aaaa-2222-bbbb-3333cccc4444`). ```dotnetcli dotnet user-jwts create --claim appid={GUID} @@ -444,10 +443,15 @@ dotnet watch # [Swagger UI](#tab/swagger-ui) +In the Swagger UI of the `Contoso.API` project (`https://localhost:7250/swagger/index.html`), select the **Authorize** button. +In the **Available authorizations: Bearer** window, enter the access token. Select the **Authorize** button. Close the **Available authorizations** window. +Under **default**, select the **Get** button for the `/WeatherForecast` endpoint. Select the **Try it out** button. Select the **Execute** button. +The output under **Responses** > **Server response** > **Response body** shows the weather forecast JSON returned by the `Contoso.API` project. +Perform the same steps with the access token that was generated with an invalid client app ID. The response is *403 - Forbidden*. # [cURL in a command shell](#tab/curl-command-shell) From 9f9b37aaf32c1140a6bd3acc2714f56aa8060886 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:41:24 -0400 Subject: [PATCH 06/11] Updates --- aspnetcore/migration/22-to-30.md | 4 +- aspnetcore/migration/70-to-80.md | 2 +- .../mvc/security/authorization/policies.md | 69 +++++ .../security/authorization/policies.md | 34 +++ aspnetcore/release-notes/aspnetcore-5.0.md | 2 +- ...tomizingauthorizationmiddlewareresponse.md | 2 +- .../authorization/dependencyinjection.md | 2 +- .../iauthorizationpolicyprovider.md | 2 +- aspnetcore/security/authorization/policies.md | 241 ++++++------------ .../security/authorization/resource-based.md | 10 +- .../how-to-choose-identity-solution.md | 2 +- aspnetcore/toc.yml | 4 + 12 files changed, 205 insertions(+), 169 deletions(-) create mode 100644 aspnetcore/mvc/security/authorization/policies.md create mode 100644 aspnetcore/razor-pages/security/authorization/policies.md diff --git a/aspnetcore/migration/22-to-30.md b/aspnetcore/migration/22-to-30.md index 61d0c201acd5..16020118ed8b 100644 --- a/aspnetcore/migration/22-to-30.md +++ b/aspnetcore/migration/22-to-30.md @@ -4,7 +4,7 @@ author: wadepickett description: Learn how to migrate an ASP.NET Core 2.2 project to ASP.NET Core 3.0. ms.author: wpickett ms.custom: mvc -ms.date: 06/02/2023 +ms.date: 07/08/2026 uid: migration/22-to-30 --- # Migrate from ASP.NET Core 2.2 to 3.0 @@ -841,7 +841,7 @@ Protection is implemented for some scenarios. Endpoints Middleware throws an exc #### Custom authorization handlers -If the app uses custom [authorization handlers](xref:security/authorization/policies#authorization-handlers), endpoint routing passes a different resource type to handlers than MVC. Handlers that expect the authorization handler context resource to be of type (the resource type [provided by MVC filters](xref:security/authorization/policies#access-mvc-request-context-in-handlers)) will need to be updated to handle resources of type (the resource type given to authorization handlers by endpoint routing). +If the app uses custom [authorization handlers](xref:security/authorization/policies#authorization-handlers), endpoint routing passes a different resource type to handlers than MVC. Handlers that expect the authorization handler context resource to be of type (the resource type [provided by MVC filters](xref:mvc/security/authorization/policies#access-mvc-request-context-in-handlers)) will need to be updated to handle resources of type (the resource type given to authorization handlers by endpoint routing). MVC still uses `AuthorizationFilterContext` resources, so if the app uses MVC authorization filters along with endpoint routing authorization, it may be necessary to handle both types of resources. diff --git a/aspnetcore/migration/70-to-80.md b/aspnetcore/migration/70-to-80.md index 53a484fbffd3..8b47e9562678 100644 --- a/aspnetcore/migration/70-to-80.md +++ b/aspnetcore/migration/70-to-80.md @@ -614,7 +614,7 @@ This problem is under evaluation for a new framework feature in [MapRazorCompone }); ``` -* Register a custom `AuthorizationHandler` that [checks the `HttpContext`](xref:security/authorization/policies#access-mvc-request-context-in-handlers) to allow the `/_framework/blazor.server.js` file through. +* Register a custom `AuthorizationHandler` that [checks the `HttpContext`](xref:mvc/security/authorization/policies#access-mvc-request-context-in-handlers) to allow the `/_framework/blazor.server.js` file through. ## Docker diff --git a/aspnetcore/mvc/security/authorization/policies.md b/aspnetcore/mvc/security/authorization/policies.md new file mode 100644 index 000000000000..41957fff45ca --- /dev/null +++ b/aspnetcore/mvc/security/authorization/policies.md @@ -0,0 +1,69 @@ +--- +title: Policy-based authorization in ASP.NET Core MVC +ai-usage: ai-assisted +author: wadepickett +description: Learn how to create and use authorization policy handlers for enforcing authorization requirements in an ASP.NET Core MVC app. +monikerRange: '>= aspnetcore-3.1' +ms.author: wpickett +ms.custom: mvc +ms.date: 07/08/2026 +uid: mvc/security/authorization/policies +--- +# Policy-based authorization in ASP.NET Core MVC + +This article provides additional MVC policy-based authorization scenarios following , which should be read before this article when learning about policy-based authorization. + +## Apply policies to MVC controllers + +Apply policies to controllers using the `[Authorize]` attribute with the policy name: + +:::moniker range=">= aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Controllers/AtLeast21Controller.cs" id="snippet" highlight="1"::: + +If multiple policies are applied at the controller and action levels, ***all*** policies must pass before access is granted: + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Controllers/AtLeast21Controller2.cs" id="snippet" highlight="1,4"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Controllers/AlcoholPurchaseController.cs" id="snippet_AlcoholPurchaseControllerClass" highlight="4"::: + +:::moniker-end + +## Access MVC request context in handlers + +The method has two parameters: an and the `TRequirement` being handled. Frameworks such as MVC or SignalR are free to add any object to the property to pass extra information. + +When using endpoint routing, authorization is typically handled by the Authorization Middleware, and the property is an instance of . The context is used to access the current endpoint, which can be used to probe the underlying resource to which you're routing: + +```csharp +if (context.Resource is HttpContext httpContext) +{ + var endpoint = httpContext.GetEndpoint(); + var actionDescriptor = + endpoint?.Metadata.GetMetadata(); + ... +} +``` + +With traditional routing, or when authorization happens as part of MVC's authorization filter, the value of is an instance. This property provides access to , , and everything else provided by MVC and Razor Pages. + +The use of the property is framework-specific. Using information in the property limits your authorization policies to particular frameworks. Cast the property using the `is` keyword, and then confirm the cast has succeeded to ensure your code doesn't crash with an when run on other frameworks. When the cast succeeds, examine MVC-specific data, such as routing data: + +```csharp +using Microsoft.AspNetCore.Mvc.Filters; + +... + +if (context.Resource is AuthorizationFilterContext mvcContext) +{ + var routeValues = mvcContext.RouteData.Values; + ... +} +``` + +> [!NOTE] +> Endpoint routing passes to authorization handlers, unlike traditional routing in MVC apps, which use an authorization handler context resource of type . If the app uses MVC authorization filters along with endpoint routing authorization, it may be necessary to handle both types of resources. diff --git a/aspnetcore/razor-pages/security/authorization/policies.md b/aspnetcore/razor-pages/security/authorization/policies.md new file mode 100644 index 000000000000..737293da76e3 --- /dev/null +++ b/aspnetcore/razor-pages/security/authorization/policies.md @@ -0,0 +1,34 @@ +--- +title: Policy-based authorization in ASP.NET Core Razor Pages +ai-usage: ai-assisted +author: wadepickett +description: Learn how to create and use authorization policy handlers for enforcing authorization requirements in an ASP.NET Core Razor Pages app. +monikerRange: '>= aspnetcore-3.1' +ms.author: wpickett +ms.custom: mvc +ms.date: 07/08/2026 +uid: razor-pages/security/authorization/policies +--- +# Policy-based authorization in ASP.NET Core Razor Pages + +This article provides additional Razor Pages policy-based authorization scenarios following , which should be read before this article when learning about policy-based authorization. + +## Apply policies to Razor Pages + +Apply policies to Razor Pages using the `[Authorize]` attribute with the policy name: + +:::moniker range=">= aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Pages/AtLeast21.cshtml.cs" highlight="6"::: + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp2/Pages/AlcoholPurchase.cshtml.cs" id="snippet_AlcoholPurchaseModelClass" highlight="4"::: + +:::moniker-end + +Policies can't be applied at the page handler level, they must be applied to the class. + +Policies can also be applied to pages using an [authorization convention](xref:razor-pages/security/authorization/conventions). diff --git a/aspnetcore/release-notes/aspnetcore-5.0.md b/aspnetcore/release-notes/aspnetcore-5.0.md index c56408c76aca..a584f2d51083 100644 --- a/aspnetcore/release-notes/aspnetcore-5.0.md +++ b/aspnetcore/release-notes/aspnetcore-5.0.md @@ -4,7 +4,7 @@ author: wadepickett description: Learn about the new features in ASP.NET Core in .NET 5. ms.author: wpickett ms.custom: mvc -ms.date: 07/29/2025 +ms.date: 07/08/2026 uid: aspnetcore-5.0 --- # What's new in ASP.NET Core in .NET 5 diff --git a/aspnetcore/security/authorization/customizingauthorizationmiddlewareresponse.md b/aspnetcore/security/authorization/customizingauthorizationmiddlewareresponse.md index da43f591c5ad..a1480968a8a2 100644 --- a/aspnetcore/security/authorization/customizingauthorizationmiddlewareresponse.md +++ b/aspnetcore/security/authorization/customizingauthorizationmiddlewareresponse.md @@ -4,7 +4,7 @@ author: tdykstra description: This article explains how to customize the result handling of AuthorizationMiddleware. ms.author: tdykstra monikerRange: '>= aspnetcore-5.0' -ms.date: 03/24/2022 +ms.date: 07/08/2026 uid: security/authorization/authorizationmiddlewareresulthandler --- # Customize the behavior of `AuthorizationMiddleware` diff --git a/aspnetcore/security/authorization/dependencyinjection.md b/aspnetcore/security/authorization/dependencyinjection.md index d38956ee7af7..eefc4c45346b 100644 --- a/aspnetcore/security/authorization/dependencyinjection.md +++ b/aspnetcore/security/authorization/dependencyinjection.md @@ -4,7 +4,7 @@ author: tdykstra description: Learn how to inject authorization requirement handlers into an ASP.NET Core app using dependency injection. monikerRange: ">= aspnetcore-2.1" ms.author: tdykstra -ms.date: 03/25/2022 +ms.date: 07/08/2026 uid: security/authorization/dependencyinjection --- # Dependency injection in requirement handlers in ASP.NET Core diff --git a/aspnetcore/security/authorization/iauthorizationpolicyprovider.md b/aspnetcore/security/authorization/iauthorizationpolicyprovider.md index 6b9fc06a8cf8..906aa8c37d0c 100644 --- a/aspnetcore/security/authorization/iauthorizationpolicyprovider.md +++ b/aspnetcore/security/authorization/iauthorizationpolicyprovider.md @@ -4,7 +4,7 @@ author: mjrousos description: Learn how to use a custom IAuthorizationPolicyProvider in an ASP.NET Core app to dynamically generate authorization policies. ms.author: wpickett ms.custom: mvc -ms.date: 05/15/2026 +ms.date: 07/08/2026 uid: security/authorization/iauthorizationpolicyprovider # customer intent: As an ASP.NET developer, I want to use a custom IAuthorizationPolicyProvider in my ASP.NET Core app, so I can dynamically generate authorization policies. diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index 49de029faabc..c75f3c476c23 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -6,7 +6,7 @@ description: Learn how to create and use authorization policy handlers for enfor monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 06/15/2026 +ms.date: 07/08/2026 uid: security/authorization/policies --- # Policy-based authorization in ASP.NET Core @@ -15,6 +15,7 @@ An ASP.NET Core authorization policy is a named set of one or more authorization This article explains: +* Creating requirements. * Registering and applying policies. * Authorization handlers for single and multiple requirement evaluation. * How multiple requirements in a single policy are evaluated. @@ -23,144 +24,100 @@ In practice, a policy is applied with `[Authorize(Policy = "...")]` or `RequireA [Role-based authorization](xref:security/authorization/roles) and [claim-based authorization](xref:security/authorization/claims) use a requirement, a requirement handler, and a preconfigured authorization policy. These building blocks support the expression of authorization evaluations in code. -## Policy registration +This article uses Razor component examples and focuses on [Blazor](xref:blazor/index) authorization scenarios for ASP.NET Core 3.1 or later. For Razor Pages and MVC guidance, which apply to all releases of ASP.NET Core, see the following resources after reading this article: + +* +* + +Examples in this article use *primary constructors*, available in C# 12 (.NET 8) or later. For more information, see [Declare primary constructors for classes and structs (C# documentation tutorial)](/dotnet/csharp/whats-new/tutorials/primary-constructors) and [Primary constructors (C# Guide)](/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors). + +## Requirements and policy registration :::moniker range=">= aspnetcore-6.0" -An authorization policy consists of one or more requirements. Register the policy as part of the authorization service configuration with in the app's `Program` file. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): +An authorization policy consists of one or more *requirements*. An authorization requirement is a collection of data parameters that a policy can use to evaluate authorization for the current user principal. -```csharp -builder.Services.AddAuthorization(options => -{ - options.AddPolicy("AtLeast21", policy => - policy.Requirements.Add(new MinimumAgeRequirement(21))); -}); -``` +A requirement implements , which is an empty marker interface. Consider the following `MinimumAgeRequirement` requirement, which describes a single parameter, a minimum age, to evaluate for user authorization: -:::moniker-end +:::moniker range=">= aspnetcore-8.0" -:::moniker range="< aspnetcore-6.0" +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Policies/Requirements/MinimumAgeRequirement.cs"::: -An authorization policy consists of one or more requirements. Register the policy as part of the authorization service configuration with in the `Startup.ConfigureServices` method. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): +:::moniker-end -```csharp -services.AddAuthorization(options => -{ - options.AddPolicy("AtLeast21", policy => - policy.Requirements.Add(new MinimumAgeRequirement(21))); -}); -``` +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Requirements/MinimumAgeRequirement.cs"::: :::moniker-end -## Authorization service interface (`IAuthorizationService`) +:::moniker range="< aspnetcore-6.0" - is primarily responsible for determining if authorization is successful when one of the overloads is called: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Requirements/MinimumAgeRequirement.cs"::: -* `AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable requirements)`: Checks if a user meets a specific set of authorization requirements for a specified resource. -* `AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)`: Checks if a user meets a specific authorization policy for a specified resource. +:::moniker-end -If a resource isn't required for policy evaluation, pass `null` for the resource. When acting on a resource-focused policy, confirm that the resource is assigned. +If an authorization policy contains multiple authorization requirements, all of the requirements must pass in order for the policy evaluation to succeed. In other words, multiple authorization requirements added to a single authorization policy are treated on an **AND** basis. -The preceding methods return `true` when authorization succeeds and `false` when it fails. +> [!NOTE] +> A requirement doesn't require data or properties. - is a marker interface with no methods that serves as the mechanism for tracking whether authorization is successful. +:::moniker range=">= aspnetcore-7.0" -Each is responsible for checking if requirements are met via . The class contains the authorization information used by . When is called with the , the policy is met: +A policy is registered as part of the authorization service configuration with either in the app's `Program` file. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): ```csharp -context.Succeed(requirement); +builder.Services.AddAuthorizationBuilder() + .AddPolicy("AtLeast21", policy => + policy.Requirements.Add(new MinimumAgeRequirement(21))); ``` -The following code demonstrates a typical authorization service configuration, which are further explained later in this article: +:::moniker-end -* Authorization handler registrations (`IAuthorizationHandler`) - * A custom handler represented by the `CustomHandler1` class is registered. - * A second custom handler represented by the `CustomHandler2` class is registered. -* Authorization policy (name = `Admin`) registration: The user must have `Administrator` and `SecurityPortal` claims to satisfy the policy. +:::moniker range=">= aspnetcore=6.0 < aspnetcore-7.0" -:::moniker range=">= aspnetcore-6.0" +A policy is registered as part of the authorization service configuration with in the app's `Program` file. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): ```csharp -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); - builder.Services.AddAuthorization(options => - options.AddPolicy("Admin", - policy => - policy.RequireClaim("Administrator", "SecurityPortal"))); +{ + options.AddPolicy("AtLeast21", policy => + policy.Requirements.Add(new MinimumAgeRequirement(21))); +}); ``` :::moniker-end :::moniker range="< aspnetcore-6.0" -```csharp -// Add all of your handlers to DI. -services.AddSingleton(); -services.AddSingleton(); +A policy is registered as part of the authorization service configuration with in `Startup.ConfigureServices` (`Startup.cs`). In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): -// Configure your policies +```csharp services.AddAuthorization(options => - options.AddPolicy("Admin", - policy => - policy.RequireClaim("Administrator", "SecurityPortal"))); +{ + options.AddPolicy("AtLeast21", policy => + policy.Requirements.Add(new MinimumAgeRequirement(21))); +}); ``` :::moniker-end -Use or `[Authorize(Policy = "{POLICY NAME}")]` for authorization, both of which are demonstrated later in this article. - -## Apply policies in Razor components - - - - - - - - -## Apply policies to MVC controllers - -Apply policies to controllers by using the `[Authorize]` attribute with the policy name: - -:::moniker range=">= aspnetcore-6.0" - -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Controllers/AtLeast21Controller.cs" id="snippet" highlight="1"::: - -If multiple policies are applied at the controller and action levels, ***all*** policies must pass before access is granted: - -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Controllers/AtLeast21Controller2.cs" id="snippet" highlight="1,4"::: - -:::moniker-end - -:::moniker range="< aspnetcore-6.0" - -Policies are applied to controllers by using the `[Authorize]` attribute with the policy name. For example: - -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Controllers/AlcoholPurchaseController.cs" id="snippet_AlcoholPurchaseControllerClass" highlight="4"::: - -:::moniker-end - -## Apply policies to Razor Pages - -Apply policies to Razor Pages by using the `[Authorize]` attribute with the policy name. For example: - -:::moniker range=">= aspnetcore-6.0" - -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Pages/AtLeast21.cshtml.cs" highlight="6"::: - -:::moniker-end - -:::moniker range="< aspnetcore-6.0" +## Apply policies to Razor components -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp2/Pages/AlcoholPurchase.cshtml.cs" id="snippet_AlcoholPurchaseModelClass" highlight="4"::: +Apply policies to Razor components using the `[Authorize]` attribute with the policy name: -:::moniker-end +```razor +@using Microsoft.AspNetCore.Authorization +@attribute [Authorize(Policy = "CustomerServiceMember")] +``` -Policies can ***not*** be applied at the Razor Page handler level, they must be applied to the Page. +If multiple policies are applied, ***all*** policies must pass before access is granted: -Policies can also be applied to Razor Pages by using an [authorization convention](xref:razor-pages/security/authorization/conventions). +```razor +@using Microsoft.AspNetCore.Authorization +@attribute [Authorize(Policy = "CustomerServiceMember")] +@attribute [Authorize(Policy = "HumanResourcesMember")] +``` ## Apply policies to endpoints @@ -168,38 +125,42 @@ Apply policies to endpoints by using , which is an empty marker interface. A parameterized minimum age requirement could be implemented as follows: - -:::moniker range=">= aspnetcore-6.0" +## Authorization service interface (`IAuthorizationService`) -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Requirements/MinimumAgeRequirement.cs"::: + is primarily responsible for determining if authorization is successful when an overload is called: -:::moniker-end +* `AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable requirements)`: Checks if a user meets a specific set of authorization requirements for a specified resource. +* `AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)`: Checks if a user meets a specific authorization policy for a specified resource. -:::moniker range="< aspnetcore-6.0" +If a resource isn't required for policy evaluation, pass `null` for the resource. When acting on a resource-focused policy, confirm that the resource is assigned. -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Requirements/MinimumAgeRequirement.cs" id="snippet_MinimumAgeRequirementClass"::: +The preceding methods return `true` when authorization succeeds and `false` when it fails. -:::moniker-end +Each is responsible for checking if requirements are met via . The class contains the authorization information used by the implementation. is a marker interface with no methods that serves as the mechanism for tracking whether authorization is successful. When is called with the , the policy is met: -If an authorization policy contains multiple authorization requirements, all of the requirements must pass in order for the policy evaluation to succeed. In other words, multiple authorization requirements added to a single authorization policy are treated on an **AND** basis. +```csharp +context.Succeed(requirement); +``` -> [!NOTE] -> A requirement doesn't need to have data or properties. +Use with the policy name parameter overload, `[Authorize(Policy = "{POLICY NAME}")]`, or `RequireAuthorization("{POLICY NAME}")` for authorization, where the `{POLICY NAME}` placeholder is the policy name. These approaches are demonstrated later in this article. ## Authorization handlers An authorization handler is responsible for the evaluation of a requirement's properties. The authorization handler evaluates the requirements against a provided to determine if access is allowed. -A requirement can have [multiple handlers](#why-would-i-want-multiple-handlers-for-a-requirement). A handler may inherit , where `TRequirement` is the requirement to be handled. Alternatively, a handler may implement directly to handle more than one type of requirement. +A requirement can have [multiple handlers](#why-would-i-want-multiple-handlers-for-a-requirement). A handler may inherit , where `TRequirement` is the requirement to handle. Alternatively, a handler may implement directly to handle more than one type of requirement. ### Use a handler for one requirement The following example shows a one-to-one relationship in which a minimum age handler handles a single requirement: -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-8.0" + +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Policies/Handlers/MinimumAgeHandler.cs"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/MinimumAgeHandler.cs"::: @@ -207,11 +168,11 @@ The following example shows a one-to-one relationship in which a minimum age han :::moniker range="< aspnetcore-6.0" -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/MinimumAgeHandler.cs" id="snippet_MinimumAgeHandlerClass"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/MinimumAgeHandler.cs"::: :::moniker-end -The preceding code determines if the current user principal has a date of birth claim that has been issued by a known and trusted issuer. Authorization can't occur when the claim is missing, in which case a completed task is returned. When a claim is present, the user's age is calculated. If the user meets the minimum age defined by the requirement, authorization is considered successful. When authorization is successful, [`context.Succeed`](xref:Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext.Succeed%2A) is invoked with the satisfied requirement as its sole parameter. +The preceding code determines if the current user principal has a date of birth claim. Authorization can't occur when the claim is missing, in which case a completed task is returned. When a claim is present, the user's age is calculated. If the user meets the minimum age defined by the requirement, authorization is considered successful. When authorization is successful, [`context.Succeed`](xref:Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext.Succeed%2A) is invoked with the satisfied requirement as its sole parameter. ### Use a handler for multiple requirements @@ -219,13 +180,13 @@ The following example shows a one-to-many relationship in which a permission han :::moniker range=">= aspnetcore-6.0" -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/PermissionHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Handlers/PermissionHandler.cs"::: :::moniker-end :::moniker range="< aspnetcore-6.0" -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/PermissionHandler.cs" id="snippet_PermissionHandlerClass"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/PermissionHandler.cs"::: :::moniker-end @@ -253,7 +214,7 @@ services.AddSingleton(); It's possible to bundle both a requirement and a handler into a single class implementing both and . This bundling creates a tight coupling between the handler and requirement and is only recommended for simple requirements and handlers. Creating a class that implements both interfaces removes the need to register the handler in the service container due to the built-in that allows requirements to handle themselves. -See the [implementation of the ASP.NET Core `AssertionRequirement` class](https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authorization/Core/src/AssertionRequirement.cs) for a good example where the is both a requirement and the handler in a fully self-contained class. The framework's API allows you to validate access using inline lambda expressions instead of writing separate, boilerplate requirement and handler classes. +See the [implementation of the ASP.NET Core `AssertionRequirement` class](https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authorization/Core/src/AssertionRequirement.cs) for an example where the is both a requirement and the handler in a fully self-contained class. The framework's API allows you to validate access using inline lambda expressions instead of writing separate, boilerplate requirement and handler classes. [!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] @@ -274,7 +235,7 @@ If a handler calls [`context.Succeed`](xref:Microsoft.AspNetCore.Authorization.A ## Why would I want multiple handlers for a requirement? -In cases where you want evaluation to be on an **OR** basis, implement multiple handlers for a single requirement. For example, Microsoft has doors that only open with key cards. If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. In this scenario, the app has a single requirement but multiple handlers, each one examining a single requirement. +In cases where you want evaluation to be on an **OR** basis, implement multiple handlers for a single requirement. For example, assume that the Contoso Corporation has doors that only open with key cards. If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. In this scenario, the app has a single requirement but multiple handlers, each one examining a single requirement. In the following example implementations: @@ -285,15 +246,15 @@ In the following example implementations: `BuildingEntryRequirement.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Requirements/BuildingEntryRequirement.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Requirements/BuildingEntryRequirement.cs"::: `BadgeEntryHandler.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/BadgeEntryHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Handlers/BadgeEntryHandler.cs"::: `TemporaryStickerHandler.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/6.0/AuthorizationPoliciesSample/Policies/Handlers/TemporaryStickerHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Handlers/TemporaryStickerHandler.cs"::: :::moniker-end @@ -301,15 +262,15 @@ In the following example implementations: `BuildingEntryRequirement.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Requirements/BuildingEntryRequirement.cs" id="snippet_BuildingEntryRequirementClass"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Requirements/BuildingEntryRequirement.cs"::: `BadgeEntryHandler.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/BadgeEntryHandler.cs" id="snippet_BadgeEntryHandlerClass"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/BadgeEntryHandler.cs"::: `TemporaryStickerHandler.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/TemporaryStickerHandler.cs" id="snippet_TemporaryStickerHandlerClass"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/PoliciesAuthApp1/Services/Handlers/TemporaryStickerHandler.cs"::: :::moniker-end @@ -331,38 +292,6 @@ There are situations where fulfilling a policy is simple to express in code with :::moniker-end -## Access MVC request context in handlers - -The method has two parameters: an and the `TRequirement` being handled. Frameworks such as MVC or SignalR are free to add any object to the property to pass extra information. - -When using endpoint routing, authorization is typically handled by the Authorization Middleware, and the property is an instance of . The context is used to access the current endpoint, which can be used to probe the underlying resource to which you're routing: - -```csharp -if (context.Resource is HttpContext httpContext) -{ - var endpoint = httpContext.GetEndpoint(); - var actionDescriptor = - endpoint.Metadata.GetMetadata(); - ... -} -``` - -With traditional routing, or when authorization happens as part of MVC's authorization filter, the value of is an instance. This property provides access to , , and everything else provided by MVC and Razor Pages. - -The use of the property is framework-specific. Using information in the property limits your authorization policies to particular frameworks. Cast the property using the `is` keyword, and then confirm the cast has succeeded to ensure your code doesn't crash with an when run on other frameworks. When the cast succeeds, examine MVC-specific data, such as routing data: - -```csharp -using Microsoft.AspNetCore.Mvc.Filters; - -... - -if (context.Resource is AuthorizationFilterContext mvcContext) -{ - var routeValues = mvcContext.RouteData.Values; - ... -} -``` - ## Require global user authentication For information on how to require authentication for all app users, see . diff --git a/aspnetcore/security/authorization/resource-based.md b/aspnetcore/security/authorization/resource-based.md index 454169d3219f..f4c50065e2cf 100644 --- a/aspnetcore/security/authorization/resource-based.md +++ b/aspnetcore/security/authorization/resource-based.md @@ -5,7 +5,7 @@ author: wadepickett description: Learn how to implement resource-based authorization in an ASP.NET Core app when an [Authorize] attribute doesn't suffice. ms.author: wpickett ms.custom: mvc -ms.date: 05/20/2026 +ms.date: 07/08/2026 uid: security/authorization/resource-based --- # Resource-based authorization in ASP.NET Core @@ -101,13 +101,13 @@ public class Document The handler class specifies the requirement and resource type. The following example demonstrates a handler utilizing a `SameAuthorRequirement` requirement and a `Document` resource. -`Services/DocumentAuthorizationHandler.cs`: +`Policies/Handlers/DocumentAuthorizationHandler.cs`: ```csharp using Microsoft.AspNetCore.Authorization; using BlazorWebAppAuthorization.Models; -namespace BlazorWebAppAuthorization.Services; +namespace BlazorWebAppAuthorization.Policies.Handlers; public class DocumentAuthorizationHandler : AuthorizationHandler @@ -244,14 +244,14 @@ The following `DocumentAuthorizationCrudHandler` authorization handler validates * Only users in the `Admin` role can create and update documents. * Only users in the `SuperUser` role can delete documents. -`Services/DocumentAuthorizationCrudHandler.cs`: +`Policies/Handlers/DocumentAuthorizationCrudHandler.cs`: ```csharp using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Infrastructure; using BlazorWebAppAuthorization.Models; -namespace BlazorWebAppAuthorization.Services; +namespace BlazorWebAppAuthorization.Policies.Handlers; public class DocumentAuthorizationCrudHandler : AuthorizationHandler diff --git a/aspnetcore/security/how-to-choose-identity-solution.md b/aspnetcore/security/how-to-choose-identity-solution.md index 1e0bc51913e6..28867076d8d8 100644 --- a/aspnetcore/security/how-to-choose-identity-solution.md +++ b/aspnetcore/security/how-to-choose-identity-solution.md @@ -5,7 +5,7 @@ author: JeremyLikness ms.author: wpickett ms.reviewer: jeliknes ms.topic: how-to -ms.date: 10/27/2025 +ms.date: 07/08/2026 uid: security/how-to-choose-identity --- diff --git a/aspnetcore/toc.yml b/aspnetcore/toc.yml index 246244e001f5..5bf725fa68e1 100644 --- a/aspnetcore/toc.yml +++ b/aspnetcore/toc.yml @@ -574,6 +574,8 @@ items: uid: razor-pages/security/authorization/roles - name: Claim-based authorization uid: razor-pages/security/authorization/claims + - name: Policy-based authorization + uid: razor-pages/security/authorization/policies - name: Resource-based authorization uid: razor-pages/security/authorization/resource-based - name: MVC @@ -628,6 +630,8 @@ items: uid: mvc/security/authorization/roles - name: Claim-based authorization uid: mvc/security/authorization/claims + - name: Policy-based authorization + uid: mvc/security/authorization/policies - name: Resource-based authorization uid: mvc/security/authorization/resource-based - name: Blazor From 02cbf96b26a2065e3eb66474c267aa1a2ddf02af Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:58:18 -0400 Subject: [PATCH 07/11] Updates --- aspnetcore/security/authorization/policies.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index c75f3c476c23..4a13abc6ab21 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -33,15 +33,13 @@ Examples in this article use *primary constructors*, available in C# 12 (.NET 8) ## Requirements and policy registration -:::moniker range=">= aspnetcore-6.0" - An authorization policy consists of one or more *requirements*. An authorization requirement is a collection of data parameters that a policy can use to evaluate authorization for the current user principal. A requirement implements , which is an empty marker interface. Consider the following `MinimumAgeRequirement` requirement, which describes a single parameter, a minimum age, to evaluate for user authorization: :::moniker range=">= aspnetcore-8.0" -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Policies/Requirements/MinimumAgeRequirement.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Policies/Requirements/MinimumAgeRequirement.cs"::: :::moniker-end @@ -74,7 +72,7 @@ builder.Services.AddAuthorizationBuilder() :::moniker-end -:::moniker range=">= aspnetcore=6.0 < aspnetcore-7.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" A policy is registered as part of the authorization service configuration with in the app's `Program` file. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): @@ -156,7 +154,7 @@ The following example shows a one-to-one relationship in which a minimum age han :::moniker range=">= aspnetcore-8.0" -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Policies/Handlers/MinimumAgeHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Policies/Handlers/MinimumAgeHandler.cs"::: :::moniker-end @@ -180,7 +178,7 @@ The following example shows a one-to-many relationship in which a permission han :::moniker range=">= aspnetcore-6.0" -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Handlers/PermissionHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Handlers/PermissionHandler.cs"::: :::moniker-end @@ -246,15 +244,15 @@ In the following example implementations: `BuildingEntryRequirement.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Requirements/BuildingEntryRequirement.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Requirements/BuildingEntryRequirement.cs"::: `BadgeEntryHandler.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Handlers/BadgeEntryHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Handlers/BadgeEntryHandler.cs"::: `TemporaryStickerHandler.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/policies/BlazorWebAppAuthorization/Handlers/TemporaryStickerHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Handlers/TemporaryStickerHandler.cs"::: :::moniker-end From 3b206e26c9201a9dad82e611e372305fc1f16c05 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 9 Jul 2026 17:02:21 -0400 Subject: [PATCH 08/11] Updates --- aspnetcore/security/authorization/policies.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index 4a13abc6ab21..f7ca49d66fe2 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -178,7 +178,7 @@ The following example shows a one-to-many relationship in which a permission han :::moniker range=">= aspnetcore-6.0" -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Handlers/PermissionHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Policies/Handlers/PermissionHandler.cs"::: :::moniker-end @@ -244,15 +244,15 @@ In the following example implementations: `BuildingEntryRequirement.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Requirements/BuildingEntryRequirement.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Policies/Requirements/BuildingEntryRequirement.cs"::: `BadgeEntryHandler.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Handlers/BadgeEntryHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Policies/Handlers/BadgeEntryHandler.cs"::: `TemporaryStickerHandler.cs`: -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Handlers/TemporaryStickerHandler.cs"::: +:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/BlazorWebAppAuthorization/Policies/Handlers/TemporaryStickerHandler.cs"::: :::moniker-end From 89b717110bb3919bda78a09072ffa05f827657bb Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:26:20 -0400 Subject: [PATCH 09/11] Updates --- aspnetcore/migration/22-to-30.md | 2 +- .../mvc/security/authorization/policies.md | 2 +- .../security/authorization/policies.md | 2 +- aspnetcore/release-notes/aspnetcore-5.0.md | 2 +- ...tomizingauthorizationmiddlewareresponse.md | 2 +- .../authorization/dependencyinjection.md | 2 +- .../iauthorizationpolicyprovider.md | 2 +- aspnetcore/security/authorization/policies.md | 25 +++++++++++-------- .../security/authorization/resource-based.md | 2 +- .../how-to-choose-identity-solution.md | 3 +-- 10 files changed, 24 insertions(+), 20 deletions(-) diff --git a/aspnetcore/migration/22-to-30.md b/aspnetcore/migration/22-to-30.md index 16020118ed8b..b0e846bd54c1 100644 --- a/aspnetcore/migration/22-to-30.md +++ b/aspnetcore/migration/22-to-30.md @@ -4,7 +4,7 @@ author: wadepickett description: Learn how to migrate an ASP.NET Core 2.2 project to ASP.NET Core 3.0. ms.author: wpickett ms.custom: mvc -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: migration/22-to-30 --- # Migrate from ASP.NET Core 2.2 to 3.0 diff --git a/aspnetcore/mvc/security/authorization/policies.md b/aspnetcore/mvc/security/authorization/policies.md index 41957fff45ca..d657707ed881 100644 --- a/aspnetcore/mvc/security/authorization/policies.md +++ b/aspnetcore/mvc/security/authorization/policies.md @@ -6,7 +6,7 @@ description: Learn how to create and use authorization policy handlers for enfor monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: mvc/security/authorization/policies --- # Policy-based authorization in ASP.NET Core MVC diff --git a/aspnetcore/razor-pages/security/authorization/policies.md b/aspnetcore/razor-pages/security/authorization/policies.md index 737293da76e3..27d58cf173f4 100644 --- a/aspnetcore/razor-pages/security/authorization/policies.md +++ b/aspnetcore/razor-pages/security/authorization/policies.md @@ -6,7 +6,7 @@ description: Learn how to create and use authorization policy handlers for enfor monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: razor-pages/security/authorization/policies --- # Policy-based authorization in ASP.NET Core Razor Pages diff --git a/aspnetcore/release-notes/aspnetcore-5.0.md b/aspnetcore/release-notes/aspnetcore-5.0.md index a584f2d51083..89b5fd604719 100644 --- a/aspnetcore/release-notes/aspnetcore-5.0.md +++ b/aspnetcore/release-notes/aspnetcore-5.0.md @@ -4,7 +4,7 @@ author: wadepickett description: Learn about the new features in ASP.NET Core in .NET 5. ms.author: wpickett ms.custom: mvc -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: aspnetcore-5.0 --- # What's new in ASP.NET Core in .NET 5 diff --git a/aspnetcore/security/authorization/customizingauthorizationmiddlewareresponse.md b/aspnetcore/security/authorization/customizingauthorizationmiddlewareresponse.md index a1480968a8a2..8465313cab55 100644 --- a/aspnetcore/security/authorization/customizingauthorizationmiddlewareresponse.md +++ b/aspnetcore/security/authorization/customizingauthorizationmiddlewareresponse.md @@ -4,7 +4,7 @@ author: tdykstra description: This article explains how to customize the result handling of AuthorizationMiddleware. ms.author: tdykstra monikerRange: '>= aspnetcore-5.0' -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: security/authorization/authorizationmiddlewareresulthandler --- # Customize the behavior of `AuthorizationMiddleware` diff --git a/aspnetcore/security/authorization/dependencyinjection.md b/aspnetcore/security/authorization/dependencyinjection.md index eefc4c45346b..60545f836eaa 100644 --- a/aspnetcore/security/authorization/dependencyinjection.md +++ b/aspnetcore/security/authorization/dependencyinjection.md @@ -4,7 +4,7 @@ author: tdykstra description: Learn how to inject authorization requirement handlers into an ASP.NET Core app using dependency injection. monikerRange: ">= aspnetcore-2.1" ms.author: tdykstra -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: security/authorization/dependencyinjection --- # Dependency injection in requirement handlers in ASP.NET Core diff --git a/aspnetcore/security/authorization/iauthorizationpolicyprovider.md b/aspnetcore/security/authorization/iauthorizationpolicyprovider.md index 906aa8c37d0c..3d5a23cd1415 100644 --- a/aspnetcore/security/authorization/iauthorizationpolicyprovider.md +++ b/aspnetcore/security/authorization/iauthorizationpolicyprovider.md @@ -4,7 +4,7 @@ author: mjrousos description: Learn how to use a custom IAuthorizationPolicyProvider in an ASP.NET Core app to dynamically generate authorization policies. ms.author: wpickett ms.custom: mvc -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: security/authorization/iauthorizationpolicyprovider # customer intent: As an ASP.NET developer, I want to use a custom IAuthorizationPolicyProvider in my ASP.NET Core app, so I can dynamically generate authorization policies. diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index f7ca49d66fe2..c0928cee3979 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -6,7 +6,7 @@ description: Learn how to create and use authorization policy handlers for enfor monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: security/authorization/policies --- # Policy-based authorization in ASP.NET Core @@ -20,7 +20,7 @@ This article explains: * Authorization handlers for single and multiple requirement evaluation. * How multiple requirements in a single policy are evaluated. -In practice, a policy is applied with `[Authorize(Policy = "...")]` or `RequireAuthorization(...)`, and the framework uses handlers to evaluate the requirements behind a policy. `IAuthorizationPolicyProvider` generates policies dynamically instead of registering them at app startup. +In practice, a policy is applied with `[Authorize(Policy = "...")]` (Razor components, pages, and controllers) or `RequireAuthorization(...)` (endpoints), and the framework uses handlers to evaluate the requirements behind a policy. ( documentation) generates policies dynamically instead of registering them at app startup. [Role-based authorization](xref:security/authorization/roles) and [claim-based authorization](xref:security/authorization/claims) use a requirement, a requirement handler, and a preconfigured authorization policy. These building blocks support the expression of authorization evaluations in code. @@ -62,7 +62,7 @@ If an authorization policy contains multiple authorization requirements, all of :::moniker range=">= aspnetcore-7.0" -A policy is registered as part of the authorization service configuration with either in the app's `Program` file. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): +A policy is registered as part of the authorization service configuration with in the app's `Program` file. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`) and setting the minimum age to 21 years old: ```csharp builder.Services.AddAuthorizationBuilder() @@ -74,7 +74,7 @@ builder.Services.AddAuthorizationBuilder() :::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" -A policy is registered as part of the authorization service configuration with in the app's `Program` file. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): +A policy is registered as part of the authorization service configuration with in the app's `Program` file. In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`) and setting the minimum age to 21 years old: ```csharp builder.Services.AddAuthorization(options => @@ -88,7 +88,7 @@ builder.Services.AddAuthorization(options => :::moniker range="< aspnetcore-6.0" -A policy is registered as part of the authorization service configuration with in `Startup.ConfigureServices` (`Startup.cs`). In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`), which is supplied as a parameter to the requirement (`21`): +A policy is registered as part of the authorization service configuration with in `Startup.ConfigureServices` (`Startup.cs`). In the following example, the `AtLeast21` policy is created with a single requirement of a minimum age (`MinimumAgeRequirement`) and setting the minimum age to 21 years old: ```csharp services.AddAuthorization(options => @@ -123,6 +123,13 @@ Apply policies to endpoints by using +* + ## Authorization service interface (`IAuthorizationService`) is primarily responsible for determining if authorization is successful when an overload is called: @@ -130,7 +137,7 @@ Apply policies to endpoints by using requirements)`: Checks if a user meets a specific set of authorization requirements for a specified resource. * `AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)`: Checks if a user meets a specific authorization policy for a specified resource. -If a resource isn't required for policy evaluation, pass `null` for the resource. When acting on a resource-focused policy, confirm that the resource is assigned. +If a resource isn't required for policy evaluation, `null` is passed for the resource. The preceding methods return `true` when authorization succeeds and `false` when it fails. @@ -140,8 +147,6 @@ Each is responsi context.Succeed(requirement); ``` -Use with the policy name parameter overload, `[Authorize(Policy = "{POLICY NAME}")]`, or `RequireAuthorization("{POLICY NAME}")` for authorization, where the `{POLICY NAME}` placeholder is the policy name. These approaches are demonstrated later in this article. - ## Authorization handlers An authorization handler is responsible for the evaluation of a requirement's properties. The authorization handler evaluates the requirements against a provided to determine if access is allowed. @@ -216,7 +221,7 @@ See the [implementation of the ASP.NET Core `AssertionRequirement` class](https: [!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] -## What should a handler return? +### What should a handler return? The `Handle` method in the [handler example](#use-a-handler-for-one-requirement) returns no value. How is a status of either success or failure indicated? @@ -231,7 +236,7 @@ If a handler calls [`context.Succeed`](xref:Microsoft.AspNetCore.Authorization.A > [!NOTE] > Authorization handlers are called even if authentication fails. Also handlers can execute in any order, so do ***not*** depend on the order of calling handlers. -## Why would I want multiple handlers for a requirement? +### Why would I want multiple handlers for a requirement? In cases where you want evaluation to be on an **OR** basis, implement multiple handlers for a single requirement. For example, assume that the Contoso Corporation has doors that only open with key cards. If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. In this scenario, the app has a single requirement but multiple handlers, each one examining a single requirement. diff --git a/aspnetcore/security/authorization/resource-based.md b/aspnetcore/security/authorization/resource-based.md index f4c50065e2cf..dc8365349d24 100644 --- a/aspnetcore/security/authorization/resource-based.md +++ b/aspnetcore/security/authorization/resource-based.md @@ -5,7 +5,7 @@ author: wadepickett description: Learn how to implement resource-based authorization in an ASP.NET Core app when an [Authorize] attribute doesn't suffice. ms.author: wpickett ms.custom: mvc -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: security/authorization/resource-based --- # Resource-based authorization in ASP.NET Core diff --git a/aspnetcore/security/how-to-choose-identity-solution.md b/aspnetcore/security/how-to-choose-identity-solution.md index 28867076d8d8..bb078105f989 100644 --- a/aspnetcore/security/how-to-choose-identity-solution.md +++ b/aspnetcore/security/how-to-choose-identity-solution.md @@ -5,10 +5,9 @@ author: JeremyLikness ms.author: wpickett ms.reviewer: jeliknes ms.topic: how-to -ms.date: 07/08/2026 +ms.date: 07/10/2026 uid: security/how-to-choose-identity --- - # Choose an identity management solution Most web apps support authentication to ensure that users are who they claim to be. A *user* might be a person or another app. Management of access ensures users are only able to see and modify the information they're authorized to see and modify. For example, an end user shouldn't have access to the administrative section of a website. Identity management solutions are built to handle the requirements of authentication and authorization-related tasks. To learn more about identity management, see [What is identity and access management?](/azure/active-directory/fundamentals/introduction-identity-access-management). Many [identity management solutions for .NET web apps](xref:security/identity-management-solutions) are available, each with different capabilities and requirements to use or install. This article provides guidance on how to choose the right solution. From e8082c561943de7036394a6d8fb451dfd5287c81 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:33:10 -0400 Subject: [PATCH 10/11] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- aspnetcore/security/authorization/policies.md | 8 ++++---- aspnetcore/security/how-to-choose-identity-solution.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index c0928cee3979..e00d8b407e2a 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -29,7 +29,7 @@ This article uses Razor component examples and focuses on [Blazor](xref:blazor/i * * -Examples in this article use *primary constructors*, available in C# 12 (.NET 8) or later. For more information, see [Declare primary constructors for classes and structs (C# documentation tutorial)](/dotnet/csharp/whats-new/tutorials/primary-constructors) and [Primary constructors (C# Guide)](/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors). +Some examples in this article (ASP.NET Core 8.0 or later) use *primary constructors*, available in C# 12 (.NET 8) or later. For more information, see [Declare primary constructors for classes and structs (C# documentation tutorial)](/dotnet/csharp/whats-new/tutorials/primary-constructors) and [Primary constructors (C# Guide)](/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors). ## Requirements and policy registration @@ -347,7 +347,7 @@ You can decode the token in an online JWT decoder, such as [`jwt.ms`](https://jw "unique_name": "{USER}", "sub": "{USER}", "jti": "14ed7729", - "appid": "9e7b23cf-2f98-48b5-a681-42cb4fb0df68", + "appid": "{CLIENT ID}", "aud": [ "https://localhost:7250", "http://localhost:7251" @@ -416,10 +416,10 @@ Server: Kestrel You can add breakpoints in the `Contoso.Security.API.SecurityPolicyController` and observe the passed client ID (`appid`) is used to assert whether it is allowed to obtain weather data. -You can also send the client ID directly to the `Contoso.Security.API` either via the Swagger UI or cURL in a command shell (for example: ``) to see it return either `true` or `false` for `canGetWeather` +You can also send the client ID directly to the `Contoso.Security.API` either via the Swagger UI or cURL in a command shell (for example: `https://localhost:7123/SecurityPolicy/{CLIENT ID}`) to see it return either `true` or `false` for `canGetWeather`. ```dotnetcli -curl.exe -i -H "Authorization: Bearer {TOKEN}" https://localhost:7123/SecurityPolicy/9e7b23cf-2f98-48b5-a681-42cb4fb0df68 +curl.exe -i -H "Authorization: Bearer {TOKEN}" https://localhost:7123/SecurityPolicy/{CLIENT ID} ``` With the correct client ID (`appid`), `canGetWeather` is `true`: diff --git a/aspnetcore/security/how-to-choose-identity-solution.md b/aspnetcore/security/how-to-choose-identity-solution.md index bb078105f989..27eb390d3cd1 100644 --- a/aspnetcore/security/how-to-choose-identity-solution.md +++ b/aspnetcore/security/how-to-choose-identity-solution.md @@ -4,7 +4,7 @@ description: A guide to help choose the right identity management solution for a author: JeremyLikness ms.author: wpickett ms.reviewer: jeliknes -ms.topic: how-to +ms.topic: how-to ms.date: 07/10/2026 uid: security/how-to-choose-identity --- From 2efe55e4d4cf9cc8e619b4f662705f3000c509b0 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:39:19 -0400 Subject: [PATCH 11/11] Updates --- aspnetcore/mvc/security/authorization/resource-based.md | 4 ++-- .../razor-pages/security/authorization/resource-based.md | 4 ++-- aspnetcore/security/authorization/policies.md | 6 +++--- aspnetcore/security/authorization/resource-based.md | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/aspnetcore/mvc/security/authorization/resource-based.md b/aspnetcore/mvc/security/authorization/resource-based.md index e4efce7e33b7..fd8e3d7dd765 100644 --- a/aspnetcore/mvc/security/authorization/resource-based.md +++ b/aspnetcore/mvc/security/authorization/resource-based.md @@ -5,7 +5,7 @@ author: wadepickett description: Learn how to implement resource-based authorization in an ASP.NET Core MVC app when an [Authorize] attribute doesn't suffice. ms.author: wpickett ms.custom: mvc -ms.date: 05/20/2026 +ms.date: 07/10/2026 uid: mvc/security/authorization/resource-based --- # Resource-based authorization in ASP.NET Core MVC @@ -84,7 +84,7 @@ public async Task Edit(Guid documentId) ## Create a resource-based handler -Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#authorization-handlers). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see the [Policy-based authorization: Requirements](xref:security/authorization/policies#requirements). +Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#authorization-handlers). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see . The handler class specifies the requirement and resource type. The following example demonstrates a handler utilizing a `SameAuthorRequirement` requirement and a `Document` resource: diff --git a/aspnetcore/razor-pages/security/authorization/resource-based.md b/aspnetcore/razor-pages/security/authorization/resource-based.md index 7e35f212f944..e4c38a234ffe 100644 --- a/aspnetcore/razor-pages/security/authorization/resource-based.md +++ b/aspnetcore/razor-pages/security/authorization/resource-based.md @@ -5,7 +5,7 @@ author: wadepickett description: Learn how to implement resource-based authorization in an ASP.NET Core Razor Pages app when an [Authorize] attribute doesn't suffice. ms.author: wpickett ms.custom: mvc -ms.date: 05/20/2026 +ms.date: 07/10/2026 uid: razor-pages/security/authorization/resource-based --- # Resource-based authorization in ASP.NET Core Razor Pages @@ -83,7 +83,7 @@ public async Task OnGetAsync(Guid documentId) ## Create a resource-based handler -Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#authorization-handlers). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see the [Policy-based authorization: Requirements](xref:security/authorization/policies#requirements). +Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#authorization-handlers). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see . The handler class specifies the requirement and resource type. The following example demonstrates a handler utilizing a `SameAuthorRequirement` requirement and a `Document` resource: diff --git a/aspnetcore/security/authorization/policies.md b/aspnetcore/security/authorization/policies.md index c0928cee3979..86410cd1ecd3 100644 --- a/aspnetcore/security/authorization/policies.md +++ b/aspnetcore/security/authorization/policies.md @@ -139,7 +139,7 @@ For guidance on applying policies in Razor Pages and MVC apps, see the following If a resource isn't required for policy evaluation, `null` is passed for the resource. -The preceding methods return `true` when authorization succeeds and `false` when it fails. +The preceding methods return an wrapped in a . Each is responsible for checking if requirements are met via . The class contains the authorization information used by the implementation. is a marker interface with no methods that serves as the mechanism for tracking whether authorization is successful. When is called with the , the policy is met: @@ -307,9 +307,9 @@ The [Authorization via an external service sample (`dotnet/AspNetCore.Docs.Sampl ### Configure the sample -The following demonstration relies on using the [Nswag (Swagger/OpenAPI)](https://github.com/RicoSuter/NSwag) or [cURL](https://curl.se/) in a command shell. +The following demonstration relies on using the [NSwag (Swagger/OpenAPI)](https://github.com/RicoSuter/NSwag) or [cURL](https://curl.se/) in a command shell. -In the `Contoso.Security.API` project, configure the `AllowedClients` placeholder of `{CLIENT ID (FOR THE CLIENT CALLING CONTOSO.API)}` of the with any test GUID value (for example, `00001111-aaaa-2222-bbbb-3333cccc4444`): +In the `Contoso.Security.API` project, set the `AllowedClients` placeholder (`{CLIENT ID}`) to any test GUID value (for example, `00001111-aaaa-2222-bbbb-3333cccc4444`): :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthorizationExternalService/Contoso.Security.API/appsettings.json"::: diff --git a/aspnetcore/security/authorization/resource-based.md b/aspnetcore/security/authorization/resource-based.md index dc8365349d24..2a447e3ec48a 100644 --- a/aspnetcore/security/authorization/resource-based.md +++ b/aspnetcore/security/authorization/resource-based.md @@ -80,7 +80,7 @@ protected override async Task OnParametersSetAsync() ## Create a resource-based handler -Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#authorization-handlers). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see [Policy-based authorization: Requirements](xref:security/authorization/policies#requirements). +Creating a resource-based authorization handler is similar to [creating a plain requirements handler](xref:security/authorization/policies#authorization-handlers). Create a custom requirement class and implement a requirement handler class. For more information on creating a requirement class, see . The following demonstration `Document` class is used: