Skip to content

Commit e61d106

Browse files
authored
Dynamic authorization policy coverage updates (#35476)
1 parent 498c6d9 commit e61d106

2 files changed

Lines changed: 164 additions & 60 deletions

File tree

aspnetcore/release-notes/aspnetcore-8.0.md

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -777,50 +777,50 @@ Apps that use asynchronous I/O and that can have more than one write outstanding
777777

778778
## Authentication and authorization
779779

780-
ASP.NET Core 8 adds new features to authentication and authorization.
780+
.NET 8 adds new features to authentication and authorization.
781781

782782
### Identity API endpoints
783783

784784
[`MapIdentityApi<TUser>`](xref:Microsoft.AspNetCore.Routing.IdentityApiEndpointRouteBuilderExtensions.MapIdentityApi%2A) is a new extension method that adds two API endpoints (`/register` and `/login`). The main goal of the `MapIdentityApi` is to make it easy for developers to use ASP.NET Core Identity for authentication in JavaScript-based single page apps (SPA) or Blazor apps. Instead of using the default UI provided by ASP.NET Core Identity, which is based on Razor Pages, MapIdentityApi adds JSON API endpoints that are more suitable for SPA apps and nonbrowser apps. For more information, see [Identity API endpoints](https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-4/#identity-api-endpoints).
785785
786-
### IAuthorizationRequirementData
786+
### `IAuthorizationRequirementData`
787787

788-
Prior to ASP.NET Core 8, adding a parameterized authorization policy to an endpoint required implementing an:
788+
Prior to the release of .NET 8, adding a parameterized authorization policy to an endpoint required implementing:
789789

790790
* `AuthorizeAttribute` for each policy.
791791
* `AuthorizationPolicyProvider` to process a custom policy from a string-based contract.
792792
* `AuthorizationRequirement` for the policy.
793793
* `AuthorizationHandler` for each requirement.
794794

795-
For example, consider the following sample written for ASP.NET Core 7.0:
795+
For example, consider the following code written for .NET 7:
796796

797797
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/OldStyleAuthRequirements/Program.cs" highlight="9":::
798798

799799
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/OldStyleAuthRequirements/Controllers/GreetingsController.cs" :::
800800

801801
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/OldStyleAuthRequirements/Authorization/MinimumAgeAuthorizationHandler.cs" highlight="7,19":::
802802

803-
The complete sample is [here](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/security/authorization/OldStyleAuthRequirements) in the [AspNetCore.Docs.Samples](https://github.com/dotnet/AspNetCore.Docs.Samples) repository.
803+
The complete sample for .NET 7 or earlier is the [OldStyleAuthRequirements sample app (`dotnet/AspNetCore.Docs.Samples` GitHub repository)](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/security/authorization/OldStyleAuthRequirements) ([how to download](xref:blazor/fundamentals/index#sample-apps)).
804804
805-
ASP.NET Core 8 introduces the <xref:Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData> interface. The `IAuthorizationRequirementData` interface allows the attribute definition to specify the requirements associated with the authorization policy. Using `IAuthorizationRequirementData`, the preceding custom authorization policy code can be written with fewer lines of code. The updated `Program.cs` file:
805+
.NET 8 introduces the <xref:Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData> interface. The `IAuthorizationRequirementData` interface allows the attribute definition to specify the requirements associated with the authorization policy. Using `IAuthorizationRequirementData`, the preceding custom authorization policy code can be written with fewer lines of code. The updated `Program.cs` file:
806806

807807
```diff
808-
using AuthRequirementsData.Authorization;
809-
using Microsoft.AspNetCore.Authorization;
808+
using AuthRequirementsData.Authorization;
809+
using Microsoft.AspNetCore.Authorization;
810810

811-
var builder = WebApplication.CreateBuilder();
811+
var builder = WebApplication.CreateBuilder();
812812

813-
builder.Services.AddAuthentication().AddJwtBearer();
814-
builder.Services.AddAuthorization();
815-
builder.Services.AddControllers();
816-
- builder.Services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
817-
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>();
813+
builder.Services.AddAuthentication().AddJwtBearer();
814+
builder.Services.AddAuthorization();
815+
builder.Services.AddControllers();
816+
-builder.Services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
817+
builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>();
818818

819-
var app = builder.Build();
819+
var app = builder.Build();
820820

821-
app.MapControllers();
821+
app.MapControllers();
822822

823-
app.Run();
823+
app.Run();
824824
```
825825

826826
The updated `MinimumAgeAuthorizationHandler`:
@@ -832,28 +832,23 @@ using System.Security.Claims;
832832

833833
namespace AuthRequirementsData.Authorization;
834834

835-
- class MinimumAgeAuthorizationHandler : AuthorizationHandler<MinimumAgeRequirement>
836-
+ class MinimumAgeAuthorizationHandler : AuthorizationHandler<MinimumAgeAuthorizeAttribute>
835+
class MinimumAgeAuthorizationHandler(ILogger<MinimumAgeAuthorizationHandler> logger)
836+
- : AuthorizationHandler<MinimumAgeRequirement>
837+
+ : AuthorizationHandler<MinimumAgeAuthorizeAttribute>
837838
{
838-
private readonly ILogger<MinimumAgeAuthorizationHandler> _logger;
839-
840-
public MinimumAgeAuthorizationHandler(ILogger<MinimumAgeAuthorizationHandler> logger)
839+
protected override Task HandleRequirementAsync(
840+
AuthorizationHandlerContext context,
841+
- MinimumAgeRequirement requirement)
842+
+ MinimumAgeAuthorizeAttribute requirement)
841843
{
842-
_logger = logger;
844+
...
843845
}
844-
845-
// Check whether a given MinimumAgeRequirement is satisfied or not for a particular
846-
// context
847-
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
848-
- MinimumAgeRequirement requirement)
849-
+ MinimumAgeAuthorizeAttribute requirement)
850-
{
851-
// Remaining code omitted for brevity.
846+
}
852847
```
853848

854-
The complete updated sample can be found [here](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/security/authorization/AuthRequirementsData).
849+
The updated sample is the [AuthRequirementsData sample app (`dotnet/AspNetCore.Docs.Samples` GitHub repository)](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/security/authorization/AuthRequirementsData) ([how to download](xref:blazor/fundamentals/index#sample-apps)).
855850
856-
See <xref:security/authorization/iard> for a detailed examination of the new sample.
851+
For more information, see <xref:security/authorization/iard>.
857852

858853
### Securing Swagger UI endpoints
859854

Lines changed: 136 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,161 @@
11
---
2-
title: Custom authorization policies with IAuthorizationRequirementData
2+
title: Custom authorization policies with `IAuthorizationRequirementData`
33
author: rick-anderson
4-
description: Learn how to add custom authorization policies with IAuthorizationRequirementData.
4+
description: Learn how to specify requirements associated with the authorization policy in attribute definitions with the IAuthorizationRequirementData interface.
55
ms.author: riande
66
monikerRange: '>= aspnetcore-8.0'
7-
ms.date: 6/4/2023
7+
ms.date: 5/16/2025
88
uid: security/authorization/iard
99
---
10-
# Custom authorization policies with IAuthorizationRequirementData
10+
# Custom authorization policies with `IAuthorizationRequirementData`
1111

12-
Consider the following sample that implements a custom `MinimumAgeAuthorizationHandler`:
12+
Use the <xref:Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData> interface to specify requirements associated with the authorization policy in attribute definitions.
1313

14-
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Program.cs" highlight="9":::
14+
## Sample app
1515

16-
The `MinimumAgeAuthorizationHandler` class:
16+
The complete sample described in this article is the [AuthRequirementsData sample app (`dotnet/AspNetCore.Docs.Samples` GitHub repository)](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/security/authorization/AuthRequirementsData) ([how to download](xref:blazor/fundamentals/index#sample-apps)). The sample app implements a minimum age handler for users, requiring a user to present a birth date claim indicating that they're at least 21 years old.
1717

18-
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Authorization/MinimumAgeAuthorizationHandler.cs" highlight="7,19":::
18+
## Minimum age authorize attribute
1919

20-
The custom `MinimumAgePolicyProvider`:
20+
The `MinimumAgeAuthorizeAttribute` implementation of <xref:Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData> sets an authorization age:
2121

22-
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Authorization/MinimumAgePolicyProvider.cs" id="snippet_all":::
22+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Authorization/MinimumAgeAuthorizeAttribute.cs":::
2323

24-
ASP.NET Core only uses one authorization policy provider. If the custom implementation
25-
doesn't handle all policies, including default policies, etc., it should fall back to an
26-
alternate provider. In the preceding sample, a default authorization policy provider is:
24+
## Minimum age authorization handler
2725

28-
* Constructed with options from the [dependency injection container](xref:fundamentals/dependency-injection).
29-
* Used if this custom provider isn't able to handle a given policy name.
26+
The `MinimumAgeAuthorizationHandler` class handles the single <xref:Microsoft.AspNetCore.Authorization.IAuthorizationRequirement> provided by `MinimumAgeAuthorizeAttribute`, as specified by the generic parameter `MinimumAgeAuthorizeAttribute`.
3027

31-
If a custom policy provider is able to handle all expected policy names, setting the fallback policy with <xref:Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider.GetFallbackPolicyAsync> isn't required.
28+
The `HandleRequirementAsync` method:
3229

33-
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Authorization/MinimumAgePolicyProvider.cs" id="snippet_1":::
30+
* Gets the user's birth date claim.
31+
* Obtains the user's age from the claim.
32+
* Adjusts age if the user hasn't had a birthday this year.
33+
* Marks the authorization requirement succeeded if the user meets the age requirement.
34+
* Implements logging for demonstration purposes.
3435

35-
Policies are looked up by string name, therefore parameters, for example, `age`, are embedded in the policy names. This is abstracted away from developers by the more strongly-typed attributes derived from <xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute>. For example, the `[MinimumAgeAuthorize()]` attribute in this sample looks up policies by string name.
36+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Authorization/MinimumAgeAuthorizationHandler.cs":::
3637

37-
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Authorization/MinimumAgePolicyProvider.cs" id="snippet_2":::
38+
The `MinimumAgeAuthorizationHandler` is registered as a scoped <xref:Microsoft.AspNetCore.Authorization.IAuthorizationHandler> service in the app's `Program` file:
3839

39-
The `MinimumAgeAuthorizeAttribute` uses the <xref:Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData> interface that allows the attribute definition to specify the requirements associated with the authorization policy:
40+
```csharp
41+
builder.Services.AddSingleton<IAuthorizationHandler,
42+
MinimumAgeAuthorizationHandler>();
43+
```
4044

41-
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Authorization/MinimumAgeAuthorizeAttribute.cs" highlight="6":::
45+
The `GreetingsController` displays the user's name when they satisfy the minimum age policy, using an age of 21 years old with the `[MinimumAgeAuthorize({AGE})]` attribute, where the `{AGE}` placeholder is the age:
4246

43-
The `GreetingsController` displays the user's name when they satisfy the minimum age policy:
47+
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Controllers/GreetingsController.cs":::
4448

45-
:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Controllers/GreetingsController.cs" highlight="10":::
49+
If the user's birth date claim indicates that they're at least 21 years old, the controller displays the greeting string, issuing a 200 (OK) status code. If the user is missing the birth date claim or the claim indicates that they aren't at least 21 years old, the greeting isn't displayed and a 403 (Forbidden) status code is issued.
4650

47-
The complete sample can be found in the [AuthRequirementsData](https://github.com/dotnet/AspNetCore.Docs.Samples/tree/main/security/authorization/AuthRequirementsData) folder of the [AspNetCore.Docs.Samples](https://github.com/dotnet/AspNetCore.Docs.Samples) repository.
51+
JWT bearer authentication services are added in the app's `Program` file:
4852

49-
The sample can be tested with [`dotnet user-jwts`](xref:security/authentication/jwt) and curl:
53+
```csharp
54+
builder.Services.AddAuthentication().AddJwtBearer();
55+
```
5056

51-
* `dotnet user-jwts create --claim http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth=1989-01-01`
52-
* `curl -i -H "Authorization: Bearer <token from dotnet user-jwts>" http://localhost:<port>/api/greetings/hello`
57+
The app settings file (`appsettings.json`) configures the audience and issuer for JWT bearer authentication:
58+
59+
```json
60+
"Authentication": {
61+
"Schemes": {
62+
"Bearer": {
63+
"ValidAudiences": [
64+
"https://localhost:51100"
65+
],
66+
"ValidIssuer": "dotnet-user-jwts"
67+
}
68+
}
69+
}
70+
```
71+
72+
In the preceding example, the localhost audience matches the localhost address specified by `applicationUrl` in the app's launch profile (`Properties/launchSettings.json`).
73+
74+
## Demonstration
75+
76+
Test the sample with [`dotnet user-jwts`](xref:security/authentication/jwt) and curl.
77+
78+
From the project's folder in a command shell, execute the following command to create a JWT bearer token with a birth date claim that makes the user over 21 years old:
79+
80+
```dotnetcli
81+
dotnet user-jwts create --claim http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth=1989-01-01
82+
```
83+
84+
The output produces a token after "`Token:`" in the command shell:
85+
86+
```dotnetcli
87+
New JWT saved with ID '{JWT ID}'.
88+
Name: {USER}
89+
Custom Claims: [http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth=1989-01-01]
90+
91+
Token: {TOKEN}
92+
```
93+
94+
Set the value of the token (where the `{TOKEN}` placeholder appears in the preceding output) aside for use later.
95+
96+
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 a `birthdate` claim with the user's birth date:
97+
98+
```json
99+
{
100+
"alg": "HS256",
101+
"typ": "JWT"
102+
}.{
103+
"unique_name": "{USER}",
104+
"sub": "{USER}",
105+
"jti": "{JWT ID}",
106+
"birthdate": "1989-01-01",
107+
"aud": [
108+
"https://localhost:51100",
109+
"http://localhost:51101"
110+
],
111+
"nbf": 1747315312,
112+
"exp": 1755264112,
113+
"iat": 1747315313,
114+
"iss": "dotnet-user-jwts"
115+
}.[Signature]
116+
```
117+
118+
Execute the command again with a `dateofbirth` value that makes the user under the age of 21:
119+
120+
```dotnetcli
121+
dotnet user-jwts create --claim http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth=2020-01-01
122+
```
123+
124+
Set the value of second token aside.
125+
126+
Start the app in Visual Studio or with the `dotnet watch` command in the .NET CLI.
127+
128+
In the .NET CLI, execute the following `curl.exe` command to request the `api/greetings/hello` endpoint. Replace the `{TOKEN}` placeholder with the first JWT bearer token that you saved earlier:
129+
130+
```dotnetcli
131+
curl.exe -i -H "Authorization: Bearer {TOKEN}" https://localhost:51100/api/greetings/hello
132+
```
133+
134+
The output indicates success because the user's birth date claim indicates that they're at least 21 years old:
135+
136+
```dotnetcli
137+
HTTP/1.1 200 OK
138+
Content-Type: text/plain; charset=utf-8
139+
Date: Thu, 15 May 2025 22:58:10 GMT
140+
Server: Kestrel
141+
Transfer-Encoding: chunked
142+
143+
Hello {USER}!
144+
```
145+
146+
Logging indicates that the age requirement was met:
147+
148+
> :::no-loc text="MinimumAgeAuthorizationHandler: Information: Evaluating authorization requirement for age >= 21":::
149+
> :::no-loc text="MinimumAgeAuthorizationHandler: Information: Minimum age authorization requirement 21 satisfied":::
150+
151+
Re-execute the `curl.exe` command with the second token, which indicates the user is under 21 years old. The output indicates that the requirement isn't met. Access to the endpoint is forbidden (status code 403):
152+
153+
```dotnetcli
154+
HTTP/1.1 403 Forbidden
155+
Content-Length: 0
156+
Date: Thu, 15 May 2025 22:58:36 GMT
157+
Server: Kestrel
158+
```
159+
160+
> :::no-loc text="MinimumAgeAuthorizationHandler: Information: Evaluating authorization requirement for age >= 21":::
161+
> :::no-loc text="MinimumAgeAuthorizationHandler: Information: Current user's DateOfBirth claim (2020-01-01) doesn't satisfy the minimum age authorization requirement 21":::

0 commit comments

Comments
 (0)