Skip to content

Commit 5dcd3ff

Browse files
authored
Claim-based authz article overhaul (#36937)
1 parent db3c949 commit 5dcd3ff

File tree

119 files changed

+721
-149438
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+721
-149438
lines changed

aspnetcore/blazor/security/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ To handle the case where the user must satisfy several policies simultaneously,
12751275
</AuthorizeView>
12761276
```
12771277

1278-
Claims-based authorization is a special case of policy-based authorization. For example, you can define a policy that requires users to have a certain claim. For more information, see <xref:security/authorization/policies>.
1278+
Claim-based authorization is a special case of policy-based authorization. For example, you can define a policy that requires users to have a certain claim. For more information, see <xref:security/authorization/policies>.
12791279

12801280
If both <xref:Microsoft.AspNetCore.Components.Authorization.AuthorizeView.Roles> and <xref:Microsoft.AspNetCore.Components.Authorization.AuthorizeView.Policy> are set, authorization succeeds only when both conditions are satisfied. That is, the user must belong to at least one of the specified roles *and* meet the requirements defined by the policy.
12811281

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
---
2+
title: Claim-based authorization in ASP.NET Core MVC
3+
ai-usage: ai-assisted
4+
author: wadepickett
5+
description: Learn how to add claims checks for authorization in an ASP.NET Core MVC app.
6+
monikerRange: '>= aspnetcore-3.1'
7+
ms.author: wpickett
8+
ms.date: 04/07/2026
9+
uid: mvc/security/authorization/claims
10+
---
11+
# Claim-based authorization in ASP.NET Core MVC
12+
13+
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.
14+
15+
This article uses MVC examples and focuses on MVC scenarios. For Blazor and Razor Pages guidance, see the following resources:
16+
17+
* <xref:security/authorization/claims>
18+
* <xref:razor-pages/security/authorization/claims>
19+
20+
## Sample app
21+
22+
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`).
23+
24+
## Add claim checks
25+
26+
Claim-based authorization checks are declarative and applied to controllers or actions within a controller.
27+
28+
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.
29+
30+
The simplest type of claim policy looks for the presence of a claim and doesn't check the value.
31+
32+
:::moniker range=">= aspnetcore-7.0"
33+
34+
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:
35+
36+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Program.cs" id="snippet" highlight="6-7,21":::
37+
38+
:::moniker-end
39+
40+
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
41+
42+
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:
43+
44+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/6.x/WebAll/Program.cs" id="snippet" highlight="6-9,23":::
45+
46+
:::moniker-end
47+
48+
:::moniker range="< aspnetcore-6.0"
49+
50+
Build and register the policy in `Startup.ConfigureServices` (`Startup.cs`) in the Authorization service's configuration:
51+
52+
```csharp
53+
services.AddAuthorization(options =>
54+
{
55+
options.AddPolicy("EmployeeOnly",
56+
policy => policy.RequireClaim("EmployeeNumber"));
57+
});
58+
```
59+
60+
Call <xref:Microsoft.AspNetCore.Builder.AuthorizationAppBuilderExtensions.UseAuthorization%2A> in `Startup.Configure` (`Startup.cs`) immediately after <xref:Microsoft.AspNetCore.Builder.AuthAppBuilderExtensions.UseAuthentication%2A> is called:
61+
62+
```csharp
63+
app.UseAuthorization();
64+
```
65+
66+
:::moniker-end
67+
68+
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:
69+
70+
<!-- DOC AUTHOR NOTE: The following code snippet from the 7.x sample app covers all ASP.NET Core releases. -->
71+
72+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Controllers/Home2Controller.cs" id="snippet" highlight="1":::
73+
74+
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:
75+
76+
<!-- DOC AUTHOR NOTE: The following code snippet from the 7.x sample app covers all ASP.NET Core releases. -->
77+
78+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Controllers/VacationController.cs" id="snippet" highlight="1":::
79+
80+
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):
81+
82+
<!-- DOC AUTHOR NOTE: The following code snippet from the 7.x sample app covers all ASP.NET Core releases. -->
83+
84+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Controllers/VacationController.cs" id="snippet" highlight="14":::
85+
86+
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:
87+
88+
:::moniker range=">= aspnetcore-7.0"
89+
90+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Program.cs" id="snippet2" highlight="6-8":::
91+
92+
:::moniker-end
93+
94+
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
95+
96+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/6.x/WebAll/Program.cs" id="snippet2" highlight="6-10":::
97+
98+
:::moniker-end
99+
100+
:::moniker range="< aspnetcore-6.0"
101+
102+
```csharp
103+
services.AddAuthorization(options =>
104+
{
105+
options.AddPolicy("Founder", policy =>
106+
policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
107+
});
108+
```
109+
110+
:::moniker-end
111+
112+
### Add a generic claim check
113+
114+
If the claim value isn't a single value or you need more flexible claim evaluation logic, such as pattern matching, checking the claim issuer, or parsing complex claim values, use <xref:Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder.RequireAssertion%2A> with <xref:System.Security.Claims.ClaimsPrincipal.HasClaim%2A>. For example, the following policy requires that the user's `email` claim ends with a specific domain:
115+
116+
:::moniker range=">= aspnetcore-7.0"
117+
118+
```csharp
119+
builder.Services.AddAuthorizationBuilder()
120+
.AddPolicy("ContosoOnly", policy =>
121+
policy.RequireAssertion(context =>
122+
context.User.HasClaim(c =>
123+
c.Type == "email" &&
124+
c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
125+
```
126+
127+
:::moniker-end
128+
129+
:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0"
130+
131+
```csharp
132+
builder.Services.AddAuthorization(options =>
133+
{
134+
options.AddPolicy("ContosoOnly", policy =>
135+
policy.RequireAssertion(context =>
136+
context.User.HasClaim(c =>
137+
c.Type == "email" &&
138+
c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
139+
});
140+
```
141+
142+
:::moniker-end
143+
144+
:::moniker range="< aspnetcore-6.0"
145+
146+
```csharp
147+
services.AddAuthorization(options =>
148+
{
149+
options.AddPolicy("ContosoOnly", policy =>
150+
policy.RequireAssertion(context =>
151+
context.User.HasClaim(c =>
152+
c.Type == "email" &&
153+
c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
154+
});
155+
```
156+
157+
:::moniker-end
158+
159+
For more information, see <xref:security/authorization/policies#use-a-func-to-fulfill-a-policy>.
160+
161+
## Evaluate multiple policies
162+
163+
If multiple policies are applied at the controller and action levels, ***all*** policies must pass before access is granted:
164+
165+
<!-- DOC AUTHOR NOTE: The following code snippet from the 7.x sample app covers all ASP.NET Core releases. -->
166+
167+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/claims/7.x/WebAll/Controllers/SalaryController.cs" id="snippet" highlight="1,14":::
168+
169+
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.
170+
171+
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).
172+
173+
## Claim case sensitivity
174+
175+
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 claim values, regardless of which authentication handler created the identity.
176+
177+
Separately, the claim *type* comparison (used to locate claims by their type, such as `email`) 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.
178+
179+
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.
180+
181+
In practice, this distinction rarely matters when the claim type is configured once during identity creation and matched consistently. This also applies to roles when they're represented as claims. Always use consistent casing for claim values and claim types to avoid subtle issues.

0 commit comments

Comments
 (0)