|
1 | 1 | --- |
2 | | -title: Custom authorization policies with IAuthorizationRequirementData |
| 2 | +title: Custom authorization policies with `IAuthorizationRequirementData` |
3 | 3 | 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. |
5 | 5 | ms.author: riande |
6 | 6 | monikerRange: '>= aspnetcore-8.0' |
7 | | -ms.date: 6/4/2023 |
| 7 | +ms.date: 5/16/2025 |
8 | 8 | uid: security/authorization/iard |
9 | 9 | --- |
10 | | -# Custom authorization policies with IAuthorizationRequirementData |
| 10 | +# Custom authorization policies with `IAuthorizationRequirementData` |
11 | 11 |
|
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. |
13 | 13 |
|
14 | | -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Program.cs" highlight="9"::: |
| 14 | +## Sample app |
15 | 15 |
|
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. |
17 | 17 |
|
18 | | -:::code language="csharp" source="~/../AspNetCore.Docs.Samples/security/authorization/AuthRequirementsData/Authorization/MinimumAgeAuthorizationHandler.cs" highlight="7,19"::: |
| 18 | +## Minimum age authorize attribute |
19 | 19 |
|
20 | | -The custom `MinimumAgePolicyProvider`: |
| 20 | +The `MinimumAgeAuthorizeAttribute` implementation of <xref:Microsoft.AspNetCore.Authorization.IAuthorizationRequirementData> sets an authorization age: |
21 | 21 |
|
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"::: |
23 | 23 |
|
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 |
27 | 25 |
|
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`. |
30 | 27 |
|
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: |
32 | 29 |
|
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. |
34 | 35 |
|
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"::: |
36 | 37 |
|
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: |
38 | 39 |
|
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 | +``` |
40 | 44 |
|
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: |
42 | 46 |
|
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"::: |
44 | 48 |
|
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. |
46 | 50 |
|
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: |
48 | 52 |
|
49 | | -The sample can be tested with [`dotnet user-jwts`](xref:security/authentication/jwt) and curl: |
| 53 | +```csharp |
| 54 | +builder.Services.AddAuthentication().AddJwtBearer(); |
| 55 | +``` |
50 | 56 |
|
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