|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Handling Multiple ASP.NET Core Policies with Manual Authorization Checks" |
| 4 | +description: "When implementing authorization in ASP.NET Core, Microsoft provides several ways to check multiple policies. While using **multiple `[Authorize(Policy = "...")]` attributes** works in some or most cases, there are scenarios where a more flexible approach is needed." |
| 5 | +date: 2025-02-25 00:30 |
| 6 | +author: Robert Muehsig |
| 7 | +tags: [ASP.NET Core] |
| 8 | +language: en |
| 9 | +--- |
| 10 | + |
| 11 | +{% include JB/setup %} |
| 12 | + |
| 13 | +# ASP.NET Core Policies |
| 14 | + |
| 15 | +[ASP.NET Core policies](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies) provide a structured and reusable way to enforce authorization rules across your application. |
| 16 | +The built-in features are very flexible, but we had trouble with one scenario - but depending how you write your "Requirements" this might even be possible with the built-in features. |
| 17 | +Our approach was to use the authorization service to check certain policies manually - which works quite good! |
| 18 | + |
| 19 | +# The Challenge: Combining Policies with OR Logic |
| 20 | + |
| 21 | +In one of our API use cases, we needed to allow access **either** for certain clients (e.g., specific admininstrative applications) **or** for certain users in the database. The two approaches differ: |
| 22 | + |
| 23 | +- **Client Authorization**: This is relatively straightforward and can be handled using the built-in `RequireClaim` approach. |
| 24 | +- **User Authorization**: This required checking database permissions, meaning a **custom authorization requirement** was necessary. |
| 25 | + |
| 26 | +Since both authorization paths are valid, they need to be evaluated using **OR logic**: if **either** condition is met, access should be granted. |
| 27 | + |
| 28 | +# Solution: Using the Authorization Service for Manual Policy Checks |
| 29 | +Instead of relying solely on `[Authorize]` attributes, we can leverage the **`IAuthorizationService`** to manually check policies in our code. |
| 30 | + |
| 31 | +## Step 1: Define the Authorization Policies |
| 32 | +In `Program.cs`, we define multiple policies: |
| 33 | + |
| 34 | +```csharp |
| 35 | +builder.Services.AddAuthorization(options => |
| 36 | +{ |
| 37 | + options.AddPolicy(KnownApiPolicies.AdminApiPolicyForServiceAccount, policy => |
| 38 | + { |
| 39 | + policy.RequireClaim("scope", "admin-client"); |
| 40 | + }); |
| 41 | + options.AddPolicy(KnownApiPolicies.AdminApiPolicyForLoggedInUserAdministrator, policy => |
| 42 | + { |
| 43 | + policy.Requirements.Add(new DbRoleRequirement(Custom.UserAdminInDatabase)); |
| 44 | + }); |
| 45 | +}); |
| 46 | +``` |
| 47 | + |
| 48 | +## Step 2: Manually Validate User Authorization |
| 49 | +Using `IAuthorizationService`, we can manually check if the user meets **either** of the defined policies. |
| 50 | + |
| 51 | +```csharp |
| 52 | +private async Task<AuthorizationResultType> ValidateUserAuthorization() |
| 53 | +{ |
| 54 | + var user = User; |
| 55 | + |
| 56 | + var serviceAccountAuth = await _authorizationService.AuthorizeAsync(user, KnownApiPolicies.AdminApiPolicyForServiceAccount); |
| 57 | + if (serviceAccountAuth.Succeeded) |
| 58 | + { |
| 59 | + return AuthorizationResultType.ServiceAccount; |
| 60 | + } |
| 61 | + |
| 62 | + var userAuth = await _authorizationService.AuthorizeAsync(user, KnownApiPolicies.AdminApiPolicyForLoggedInUserAdministrator); |
| 63 | + if (userAuth.Succeeded) |
| 64 | + { |
| 65 | + return AuthorizationResultType.LoggedInUser; |
| 66 | + } |
| 67 | + |
| 68 | + return AuthorizationResultType.None; |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +## Step 3: Apply the Authorization Logic in the Controller |
| 73 | + |
| 74 | +```csharp |
| 75 | +[HttpGet] |
| 76 | +public async Task<ActionResult<...>> GetAsync() |
| 77 | +{ |
| 78 | + var authResult = await ValidateUserAuthorization(); |
| 79 | + |
| 80 | + if (authResult == AuthorizationResultType.None) |
| 81 | + { |
| 82 | + return Forbid(); // Return 403 if unauthorized |
| 83 | + } |
| 84 | + |
| 85 | + using var contextScope = authResult switch |
| 86 | + { |
| 87 | + AuthorizationResultType.ServiceAccount => // ... do something with the result, |
| 88 | + AuthorizationResultType.LoggedInUser => // ..., |
| 89 | + _ => throw new UnauthorizedAccessException() |
| 90 | + }; |
| 91 | + |
| 92 | + return Ok(_userService.GetUsers(...)); |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +# Recap |
| 97 | + |
| 98 | +We use the `IAuthorizationService.AuthorizeAsync` method to check multiple policies and depending on the outcome, we can handle it depending on our needs. |
| 99 | +This approach retains the same overall structure as the "default" policy-based authorization in ASP.NET Core but provides more flexibility by allowing policies to be evaluated dynamically via the service. |
| 100 | + |
| 101 | +Keep in mind (as mentioned at the beginning): This is just one way of handling authorization. As far as we know, it works well without drawbacks while offering the flexibility we need. |
0 commit comments