Skip to content

Commit 368c3fa

Browse files
Copilotwadepickett
andauthored
Update claims-based authorization samples to use AddAuthorizationBuilder() (#36675)
* Initial plan * Create 7.x sample with AddAuthorizationBuilder() and update documentation Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com> * Revise README for ASP.NET Core 7.0 sample Updated README to reflect ASP.NET Core 7.0 features and structure. * Apply suggestion from @wadepickett * Apply suggestion from @wadepickett forcing a rebuild with a minor change to the ms.date to test policheck. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: wadepickett <10985336+wadepickett@users.noreply.github.com> Co-authored-by: Wade Pickett <wpickett@microsoft.com>
1 parent 31df823 commit 368c3fa

56 files changed

Lines changed: 74636 additions & 2 deletions

Some content is hidden

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

aspnetcore/security/authorization/claims.md

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,85 @@ author: wadepickett
44
description: Learn how to add claims checks for authorization in an ASP.NET Core app.
55
ms.author: wpickett
66
monikerRange: '>= aspnetcore-3.1'
7-
ms.date: 11/26/2021
7+
ms.date: 01/22/2026
88
uid: security/authorization/claims
9+
ai-usage: ai-assisted
910
---
1011
# Claims-based authorization in ASP.NET Core
1112

1213
<a name="security-authorization-claims-based"></a>
1314

14-
:::moniker range=">= aspnetcore-6.0"
15+
:::moniker range=">= aspnetcore-7.0"
16+
17+
When an identity is created it may be assigned one or more claims issued by a trusted party. A claim is a name value pair that represents what the subject is, not what the subject can do. For example, you may have a driver's license, issued by a local driving license authority. Your driver's license has your date of birth on it. In this case the claim name would be `DateOfBirth`, the claim value would be your date of birth, for example `8th June 1970` and the issuer would be the driving license authority. Claims-based authorization, at its simplest, checks the value of a claim and allows access to a resource based upon that value. For example if you want access to a night club the authorization process might be:
18+
19+
The door security officer would evaluate the value of your date of birth claim and whether they trust the issuer (the driving license authority) before granting you access.
20+
21+
An identity can contain multiple claims with multiple values and can contain multiple claims of the same type.
22+
23+
## Adding claims checks
24+
25+
Claim-based authorization checks:
26+
27+
* Are declarative.
28+
* Are applied to Razor Pages, controllers, or actions within a controller.
29+
* Can ***not*** be applied at the Razor Page handler level, they must be applied to the Page.
30+
31+
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.
32+
33+
The simplest type of claim policy looks for the presence of a claim and doesn't check the value.
34+
35+
Build and register the policy and call <xref:Microsoft.AspNetCore.Builder.AuthorizationAppBuilderExtensions.UseAuthorization%2A>. Registering the policy takes place as part of the Authorization service configuration, typically in the `Program.cs` file:
36+
37+
[!code-csharp[](~/security/authorization/claims/samples/7.x/WebAll/Program.cs?name=snippet&highlight=6-7,21)]
38+
39+
In this case the `EmployeeOnly` policy checks for the presence of an `EmployeeNumber` claim on the current identity.
40+
41+
Apply the policy using the `Policy` property on the [`[Authorize]`](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) attribute to specify the policy name.
42+
43+
[!code-csharp[](~/security/authorization/claims/samples/7.x/WebAll/Controllers/Home2Controller.cs?name=snippet&highlight=1)]
44+
45+
The `[Authorize]` attribute can be applied to an entire controller or Razor Page, in which case only identities matching the policy are allowed access to any Action on the controller.
46+
47+
[!code-csharp[](~/security/authorization/claims/samples/7.x/WebAll/Controllers/VacationController.cs?name=snippet&highlight=1)]
48+
49+
The following code applies the `[Authorize]` attribute to a Razor Page:
50+
51+
[!code-csharp[](~/security/authorization/claims/samples/7.x/WebAll/Pages/Index.cshtml.cs?name=snippet&highlight=1)]
52+
53+
Policies can ***not*** be applied at the Razor Page handler level, they must be applied to the Page.
54+
55+
If you have a controller that's protected by the `[Authorize]` attribute but want to allow anonymous access to particular actions, you apply the `AllowAnonymousAttribute` attribute.
56+
57+
[!code-csharp[](~/security/authorization/claims/samples/7.x/WebAll/Controllers/VacationController.cs?name=snippet&highlight=14)]
58+
59+
Because policies can ***not*** be applied at the Razor Page handler level, we recommend using a controller when policies must be applied at the page handler level. The rest of the app that doesn't require policies at the Razor Page handler level can use Razor Pages.
60+
61+
Most claims come with a value. You can specify a list of allowed values when creating the policy. The following example would only succeed for employees whose employee number was 1, 2, 3, 4, or 5.
62+
63+
[!code-csharp[](~/security/authorization/claims/samples/7.x/WebAll/Program.cs?name=snippet2&highlight=6-8)]
64+
65+
### Add a generic claim check
66+
67+
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 [Use a func to fulfill a policy](xref:security/authorization/policies#use-a-func-to-fulfill-a-policy).
68+
69+
## Multiple Policy Evaluation
70+
71+
If multiple policies are applied at the controller and action levels, ***all*** policies must pass before access is granted:
72+
73+
[!code-csharp[](~/security/authorization/claims/samples/7.x/WebAll/Controllers/SalaryController.cs?name=snippet&highlight=1,14)]
74+
75+
In the preceding example any identity which 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.
76+
77+
If you want more complicated policies, such as taking a date of birth claim, calculating an age from it, and then checking that the age is 21 or older, then you need to write [custom policy handlers](xref:security/authorization/policies).
78+
79+
In the following sample, both page handler methods must fulfill *both* the `EmployeeOnly` policy and the `HumanResources` policy:
80+
81+
[!code-csharp[](~/security/authorization/claims/samples/7.x/WebAll/Pages/X/Salary.cshtml.cs?name=snippet&highlight=1,2)]
82+
83+
:::moniker-end
84+
85+
:::moniker range="= aspnetcore-6.0"
1586

1687
When an identity is created it may be assigned one or more claims issued by a trusted party. A claim is a name value pair that represents what the subject is, not what the subject can do. For example, you may have a driver's license, issued by a local driving license authority. Your driver's license has your date of birth on it. In this case the claim name would be `DateOfBirth`, the claim value would be your date of birth, for example `8th June 1970` and the issuer would be the driving license authority. Claims-based authorization, at its simplest, checks the value of a claim and allows access to a resource based upon that value. For example if you want access to a night club the authorization process might be:
1788

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Microsoft.AspNetCore.Mvc;
3+
using System.Diagnostics;
4+
using WebAll.Models;
5+
6+
namespace WebAll.Controllers
7+
{
8+
public class Home2Controller : Controller
9+
{
10+
private readonly ILogger<Home2Controller> _logger;
11+
12+
public Home2Controller(ILogger<Home2Controller> logger)
13+
{
14+
_logger = logger;
15+
}
16+
17+
public IActionResult Index()
18+
{
19+
return View();
20+
}
21+
22+
public IActionResult Privacy()
23+
{
24+
return View();
25+
}
26+
27+
#region snippet
28+
[Authorize(Policy = "EmployeeOnly")]
29+
public IActionResult VacationBalance()
30+
{
31+
return View();
32+
}
33+
#endregion
34+
35+
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
36+
public IActionResult Error()
37+
{
38+
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
39+
}
40+
}
41+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
namespace WebAll.Controllers
5+
{
6+
#region snippet
7+
[Authorize(Policy = "EmployeeOnly")]
8+
public class SalaryController : Controller
9+
{
10+
public IActionResult Index()
11+
{
12+
return View();
13+
}
14+
15+
public IActionResult Payslip()
16+
{
17+
return View();
18+
}
19+
20+
[Authorize(Policy = "HumanResources")]
21+
public IActionResult UpdateSalary()
22+
{
23+
return View();
24+
}
25+
}
26+
#endregion
27+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Microsoft.AspNetCore.Mvc;
3+
4+
namespace WebAll.Controllers
5+
{
6+
#region snippet
7+
#region snippet2
8+
[Authorize(Policy = "EmployeeOnly")]
9+
public class VacationController : Controller
10+
{
11+
public IActionResult Index()
12+
{
13+
return View();
14+
}
15+
16+
public ActionResult VacationBalance()
17+
{
18+
return View();
19+
}
20+
#endregion
21+
22+
[AllowAnonymous]
23+
public ActionResult VacationPolicy()
24+
{
25+
return View();
26+
}
27+
}
28+
#endregion
29+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace WebAll.Models
2+
{
3+
public class ErrorViewModel
4+
{
5+
public string? RequestId { get; set; }
6+
7+
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
8+
}
9+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
@page
2+
@model ErrorModel
3+
@{
4+
ViewData["Title"] = "Error";
5+
}
6+
7+
<h1 class="text-danger">Error.</h1>
8+
<h2 class="text-danger">An error occurred while processing your request.</h2>
9+
10+
@if (Model.ShowRequestId)
11+
{
12+
<p>
13+
<strong>Request ID:</strong> <code>@Model.RequestId</code>
14+
</p>
15+
}
16+
17+
<h3>Development Mode</h3>
18+
<p>
19+
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
20+
</p>
21+
<p>
22+
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
23+
It can result in displaying sensitive information from exceptions to end users.
24+
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
25+
and restarting the app.
26+
</p>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.AspNetCore.Mvc.RazorPages;
3+
using System.Diagnostics;
4+
5+
namespace WebAll.Pages
6+
{
7+
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
8+
[IgnoreAntiforgeryToken]
9+
public class ErrorModel : PageModel
10+
{
11+
public string? RequestId { get; set; }
12+
13+
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
14+
15+
private readonly ILogger<ErrorModel> _logger;
16+
17+
public ErrorModel(ILogger<ErrorModel> logger)
18+
{
19+
_logger = logger;
20+
}
21+
22+
public void OnGet()
23+
{
24+
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
25+
}
26+
}
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@page
2+
@model IndexModel
3+
@{
4+
ViewData["Title"] = "RP Home page";
5+
}
6+
7+
<div class="text-center">
8+
<h1 class="display-4">RP pages Welcome</h1>
9+
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
10+
</div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.AspNetCore.Authorization;
2+
using Microsoft.AspNetCore.Mvc.RazorPages;
3+
4+
namespace WebAll.Pages
5+
{
6+
#region snippet
7+
[Authorize(Policy = "EmployeeOnly")]
8+
public class IndexModel : PageModel
9+
{
10+
public void OnGet()
11+
{
12+
13+
}
14+
}
15+
#endregion
16+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@page
2+
@model PrivacyModel
3+
@{
4+
ViewData["Title"] = "RP Privacy Policy";
5+
}
6+
<h1>@ViewData["Title"]</h1>
7+
8+
<p>RP: Use this page to detail your site's privacy policy.</p>

0 commit comments

Comments
 (0)