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..748e9e802cb2 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: