-
Notifications
You must be signed in to change notification settings - Fork 24.9k
Claim-based authz article overhaul #36937
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
guardrex
wants to merge
7
commits into
main
Choose a base branch
from
guardrex/claims-based-auth-overhaul
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
bdd38fb
Claim-based authz article overhaul
guardrex b927ac8
Updates
guardrex 6c006c2
React to Copilot feedback
guardrex aa28a12
Apply suggestions from code review
guardrex 7ac1f41
Update content on BWA/Blazor Server UseAuthorization call
guardrex 85f6f90
Update Roles article for UseAuthentication changes and add remarks onβ¦
guardrex 485a099
Updates
guardrex File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| --- | ||
| title: Claim-based authorization in ASP.NET Core MVC | ||
| ai-usage: ai-assisted | ||
| author: wadepickett | ||
| description: Learn how to add claims checks for authorization in an ASP.NET Core MVC app. | ||
| ms.author: wpickett | ||
| monikerRange: '>= aspnetcore-3.1' | ||
| ms.date: 04/06/2026 | ||
| uid: mvc/security/authorization/claims | ||
| --- | ||
| # Claim-based authorization in ASP.NET Core MVC | ||
|
|
||
| When an identity is created for an app user upon signing into an app, the identity provider may assign one or more [claims](xref:System.Security.Claims.Claim#remarks) to the user's identity. A claim is a name value pair that represents what the subject (a user, an app or service, or a device/computer) is, not what the subject can do. A claim can be evaluated by the app to determine access rights to data and other secured resources during the process of authorization and can also be used to make or express authentication decisions about a subject. An identity can contain multiple claims with multiple values and can contain multiple claims of the same type. This article explains how to add claims checks for authorization in an ASP.NET Core app. | ||
|
|
||
| This article uses MVC examples and focuses on MVC scenarios. For Blazor and Razor Pages guidance, see the following resources: | ||
|
|
||
| * <xref:security/authorization/claims> | ||
| * <xref:razor-pages/security/authorization/claims> | ||
|
|
||
| ## Sample app | ||
|
|
||
| The sample app for this article is the [`WebAll` sample app (`dotnet/AspNetCore.Docs.Samples` GitHub repository)](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/security/authorization/claims) ([how to download](xref:index#how-to-download-a-sample)). For more information, see the sample's README file (`README.md`). | ||
|
|
||
| ## Add claim checks | ||
|
|
||
| Claim-based authorization checks are declarative and applied to controllers or actions within a controller. | ||
|
|
||
| Claims in code specify claims which the current user must possess, and optionally the value the claim must hold to access the requested resource. Claims requirements are policy based. The developer must build and register a policy expressing the claims requirements. | ||
|
|
||
| The simplest type of claim policy looks for the presence of a claim and doesn't check the value. | ||
|
|
||
| :::moniker range=">= aspnetcore-7.0" | ||
|
|
||
| Build and register the policy and call <xref:Microsoft.AspNetCore.Builder.AuthorizationAppBuilderExtensions.UseAuthorization%2A> (place the call after the line that calls <xref:Microsoft.AspNetCore.Builder.AuthAppBuilderExtensions.UseAuthentication%2A>). Registering the policy takes place as part of the Authorization service configuration, typically in the `Program` file: | ||
|
|
||
| :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Program.cs" id="snippet" highlight="6-7,21"::: | ||
|
|
||
| :::moniker-end | ||
|
|
||
| :::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" | ||
|
|
||
| Build and register the policy and call <xref:Microsoft.AspNetCore.Builder.AuthorizationAppBuilderExtensions.UseAuthorization%2A> (place the call after the line that calls <xref:Microsoft.AspNetCore.Builder.AuthAppBuilderExtensions.UseAuthentication%2A>). Registering the policy takes place as part of the Authorization service configuration, typically in the `Program` file: | ||
|
|
||
| :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/6.x/WebAll/Program.cs" id="snippet" highlight="6-9,23"::: | ||
|
|
||
| :::moniker-end | ||
|
|
||
| :::moniker range="< aspnetcore-6.0" | ||
|
|
||
| Build and register the policy in `Startup.ConfigureServices` (`Startup.cs`) in the Authorization service's configuration: | ||
|
|
||
| ```csharp | ||
| services.AddAuthorization(options => | ||
| { | ||
| options.AddPolicy("EmployeeOnly", | ||
| policy => policy.RequireClaim("EmployeeNumber")); | ||
| }); | ||
| ``` | ||
|
|
||
| Call <xref:Microsoft.AspNetCore.Builder.AuthorizationAppBuilderExtensions.UseAuthorization%2A> in `Startup.Configure` (`Startup.cs`) immediately after <xref:Microsoft.AspNetCore.Builder.AuthAppBuilderExtensions.UseAuthentication%2A> is called: | ||
|
|
||
| ```csharp | ||
| app.UseAuthorization(); | ||
| ``` | ||
|
|
||
| :::moniker-end | ||
|
|
||
| Apply the policy using the `Policy` property on the [`[Authorize]`](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) attribute to specify the policy name. In the following example, the `EmployeeOnly` policy checks for the presence of an `EmployeeNumber` claim on the current identity: | ||
|
|
||
| <!-- DOC AUTHOR NOTE: The following code snippet from the 7.x sample app covers all ASP.NET Core releases. --> | ||
|
|
||
| :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Controllers/Home2Controller.cs" id="snippet" highlight="1"::: | ||
|
|
||
| The `[Authorize]` attribute can be applied to an entire controller, in which case only identities matching the policy are allowed access to any action on the controller: | ||
|
|
||
| <!-- DOC AUTHOR NOTE: The following code snippet from the 7.x sample app covers all ASP.NET Core releases. --> | ||
|
|
||
| :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Controllers/VacationController.cs" id="snippet" highlight="1"::: | ||
|
|
||
| If you have a controller that's protected by the `[Authorize]` attribute but want to allow anonymous access to a particular action, apply the [`[AllowAnonymous]` attribute](xref:Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute): | ||
|
|
||
| <!-- DOC AUTHOR NOTE: The following code snippet from the 7.x sample app covers all ASP.NET Core releases. --> | ||
|
|
||
| :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Controllers/VacationController.cs" id="snippet" highlight="14"::: | ||
|
|
||
| You can specify a list of allowed values when creating a policy. The following policy only passes for employees whose employee number is 1, 2, 3, 4, or 5: | ||
|
|
||
| :::moniker range=">= aspnetcore-7.0" | ||
|
|
||
| :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Program.cs" id="snippet2" highlight="6-8"::: | ||
|
|
||
| :::moniker-end | ||
|
|
||
| :::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" | ||
|
|
||
| :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/6.x/WebAll/Program.cs" id="snippet2" highlight="6-10"::: | ||
|
|
||
| :::moniker-end | ||
|
|
||
| :::moniker range="< aspnetcore-6.0" | ||
|
|
||
| ```csharp | ||
| services.AddAuthorization(options => | ||
| { | ||
| options.AddPolicy("Founder", policy => | ||
| policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5")); | ||
| }); | ||
| ``` | ||
|
|
||
| :::moniker-end | ||
|
|
||
| ### Add a generic claim check | ||
|
|
||
| If the claim value isn't a single value or a transformation is required, use <xref:Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder.RequireAssertion%2A>. For more information, see <xref:security/authorization/policies#use-a-func-to-fulfill-a-policy>. | ||
|
|
||
| ## Evaluate multiple policies | ||
|
|
||
| If multiple policies are applied at the controller and action levels, ***all*** policies must pass before access is granted: | ||
|
|
||
| <!-- DOC AUTHOR NOTE: The following code snippet from the 7.x sample app covers all ASP.NET Core releases. --> | ||
|
|
||
| :::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Controllers/SalaryController.cs" id="snippet" highlight="1,14"::: | ||
|
|
||
| In the preceding example, any identity that fulfills the `EmployeeOnly` policy can access the `Payslip` action, as that policy is enforced on the controller. However, in order to call the `UpdateSalary` action, the identity must fulfill *both* the `EmployeeOnly` policy and the `HumanResources` policy. | ||
|
|
||
| If you want more complicated policies, such as taking a date of birth claim, calculating an age from it then checking the age is 21 or older then you need to write [custom policy handlers](xref:security/authorization/policies). | ||
|
|
||
| ## Claim case sensitivity | ||
|
|
||
| Claim *values* are compared using [`StringComparison.Ordinal`](xref:System.StringComparison?displayProperty=nameWithType). This means `Admin` (uppercase `A`) and `admin` (lowercase `a`) are always treated as different roles, regardless of which authentication handler created the identity. | ||
|
|
||
| Separately, the claim *type* comparison (used to locate role claims by their claim type, such as `http://schemas.microsoft.com/ws/2008/06/identity/claims/role`) may be case-sensitive or case-insensitive depending on the <xref:System.Security.Claims.ClaimsIdentity> implementation. With `Microsoft.IdentityModel` in ASP.NET Core 8.0 or later (used by <xref:Microsoft.Extensions.DependencyInjection.JwtBearerExtensions.AddJwtBearer%2A>, <xref:Microsoft.Extensions.DependencyInjection.OpenIdConnectExtensions.AddOpenIdConnect%2A>, <xref:Microsoft.Extensions.DependencyInjection.WsFederationExtensions.AddWsFederation%2A>, and <xref:Microsoft.Identity.Web.AppBuilderExtension.AddMicrosoftIdentityWebApp%2A>/<xref:Microsoft.Identity.Web.AppBuilderExtension.AddMicrosoftIdentityWebApi%2A>), <xref:Microsoft.IdentityModel.Tokens.CaseSensitiveClaimsIdentity> is produced during token validation, which uses case-sensitive claim type matching. | ||
|
|
||
| The default <xref:System.Security.Claims.ClaimsIdentity> provided by the .NET runtime (used in most cases, including all cookie-based flows) still uses case-insensitive claim type matching. | ||
|
|
||
| In practice, this distinction rarely matters for role authorization because the role claim type is set once during identity creation and matched consistently. Always use consistent casing for role names and claim types to avoid subtle issues. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.