Policy-Based Authorization in ASP.NET Core
Tags: aspnetcore, authorization, contextfail
Published: 2023-04-05 00:48:18

---
title: Policy-Based Authorization in ASP.NET Core
published: true
date: 2023-03-19 11:30:00 UTC
tags: aspnetcore,authorization,contextFail
canonical_url: https://coderethinked.com/policy-based-authorization-in-asp-net-core/
---

## What is a policy-based authorization?

Unlike [role-based authorization](https://dev.to/karthikchintala/role-based-authorization-in-aspnet-core-1ok3-temp-slug-5148205), which solely depends on the roles assigned to the users. A policy-based authorization uses requirements that will provide access to a resource when succeeded.

A requirement is a collection of data used to evaluate the current user.

## Why do we need policy-based authorization?

With policy-based authorization, we have custom logic to provide access to a resource by configuring the policy-based authorization.

## Composing a policy for policy-based authorization

To create a policy-based authorization we need 2 things:

1. A Policy Requirement: It is a collection of parameters that a policy can use when evaluating access to the user.
2. A Policy Handler: This is responsible for evaluating against the given requirements and determining if access is allowed.

Let’s say we have two pages on our website which represent two products. So, we will create two policies and the users who have access to the products will gain access to the resource.

Let’s create `ProductAccessRequirement` and `ProductAccessHandler` classes.

```csharp
public class ProductAccessRequirement : IAuthorizationRequirement
{
    public string[] ProductIds { get; set; }

    public ProductAccessRequirement(string[] productIds)
    {
        ProductIds = productIds;
    }
}
```

Our `ProductAccessRequirement` will accept a list of product ids as the requirements.

Notice we have implemented the `IAuthorizationRequirement` interface for our requirement class. You will understand why we did it later in the article.

These will be configured in the `Startup.cs` or `Program.cs` (for .net 6+ programs) and will be utilized in the handler.

Here is our handler.

```csharp
public class ProductAccessHandler : AuthorizationHandler<ProductAccessRequirement>
{
    private readonly IUserAccessRepository _userAccessRepository;
    public ProductAccessHandler(IUserAccessRepository userAccessRepository)
    {
        _userAccessRepository = userAccessRepository;
    }
    
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ProductAccessRequirement requirement)
    {
        var user = ClaimExtensions.GetClaim(context.User, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "nameidentifier");
        if (user == null) {
          context.Fail();
          return Task.CompletedTask;
        }

        var hasAccess = _userAccessRepository.HasAccess(user.Value, requirement.ProductIds, CancellationToken.None);
        if (hasAccess) context.Succeed(requirement);

        return Task.CompletedTask;
    }
}
```

Notice how we inherit from `AuthorizationHanlder` abstract generic class and pass in our `ProductAccessRequirement` as a type parameter.

We have to override the `HandleRequirementAsync` method, which is executed when we declare our endpoints to have policy-based authorizations.

Look at how we are setting the `context.Fail()` call when the user is null and the success call by invoking `context.Succeed()` when the user has access to the product ids.

### Why do we care when the user object is null? 

Well, authorization and authentication are two different things. **The authorization handlers are still invoked even if the user is not authenticated**.

> **We don’t have to explicitly specify `context.Fail()` instead we could just return a completed task (`Task.CompletedTask`)**, which is treated as a failure if there are no `context.Succeed` calls after executing the handler.

### When should we call `context.Fail()` in authorization handlers?

First things first, a **requirement can have multiple handlers defined and registered**.

When all the handlers for a requirement are executed and if we want to fail the authorization when the requirements are not satisfied in any of the handlers then invoking `context.Fail()` this will force the failure even though the other handlers succeeded.

And we have a user repository, which will check whether the user has access to the product ids or not. For the user-to-product access map, I’ve created a dictionary with user ids and their respective product ids.

Here is our user access repository class.

```csharp
public class UserAccessRepository : IUserAccessRepository
{
    private readonly Dictionary<string, string[]> userIdToProductAccessMap = new Dictionary<string, string[]>()
    {
        { "2a3aa5e6-071d-4d40-8cf9-5f986c8e5ded", new string[] { Constants.ProductCodes.StandardUser, Constants.ProductCodes.PremiumUser } },
        { "509bac4d-0648-4131-b0f5-52ce03a4069d", new string[] { Constants.ProductCodes.LimitedUser } },
        { "6bdeb8ac-2eb4-4cc1-a3ab-ed848a6967a9", new string[] { Constants.ProductCodes.StandardUser } },
        { "fc764407-c2d7-46b2-8652-9f7eb71dd5af", new string[] { Constants.ProductCodes.StandardUser, Constants.ProductCodes.PremiumUser } }
    };

    public bool HasAccess(string userId, string[] products, CancellationToken cancellationToken)
    {
        var userExistsInList = userIdToProductAccessMap.TryGetValue(userId, out var userProductIds);
        if (!userExistsInList || userProductIds?.Any() == false) return false;

        return products.All(p => userProductIds.Contains(p));
    }
}

```

If we used a real database connection to fetch the user-related product ids, then the authorization will become slow as we’d have to fetch from the database for every user. I’d recommend using some caching system such as redis or Memcached to have those values without having much latency.

### Adding authorization in Program.cs

Now, let’s wire things up in the `Program.cs` file (.NET 6 and later versions) or in `Startup.cs` file (< .NET 6).

```csharp
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("PremiumOnly", policy => policy.AddRequirements(new ProductAccessRequirement(new string[]
    {
        Constants.ProductCodes.PremiumUser
    })));
    
    options.AddPolicy("StandardOnly", policy => policy.AddRequirements(new ProductAccessRequirement(new string[]
    {
        Constants.ProductCodes.StandardUser
    })));

    options.AddPolicy("PremiumAndStandard", policy => policy.AddRequirements(new ProductAccessRequirement(new string[]
    {
        Constants.ProductCodes.StandardUser,
        Constants.ProductCodes.PremiumUser
    })));
});
```

We have defined 3 different policies and here is what they all mean.

1. PremiumOnly: We added the PremiumUser product code as a requirement to the policy. This allows only users who have `PremiumUser` product ID.
2. StandardOnly: We added the standard user product code as a requirement to the policy. This allows only users who have `StandardUser` product ID.
3. PremiumAndStandard: This access policy allows users to have premium user **AND** standard user access. The AND condition is because the `HasAccess` method defined in the user access repository will return true only if all the product IDs match in the requirements match with the user product IDs.

Here is how the policies are created in the authorization options.

```csharp
options.AddPolicy("PremiumOnly", policy => policy.AddRequirements(new ProductAccessRequirement(new string[]
{
    Constants.ProductCodes.PremiumUser
})));
```

The `AddPolicy` method has two overloads. We used the one that takes the following 2 parameters

1. Name of the policy
2. An action delegate of type `Action<AuthorizationPolicyBuilder>`

We add requirements by calling the `.AddRequirements` on the policy and passing our `ProductAccessRequirement` as an argument.

The AddRequirements method accepts the instances of type IAuthorizationRequirement. This is why we have implemented our ProductAccessRequirement from `IAuthorizationRequirement` interface though the interface has nothing to be implemented.

Now, all we have to do is add these policies to the authorize attributes. Ex: `[Authorize(Policy = "PremiumOnly")]`

## Defining the policies on the controllers or razor pages

Applying policies is the same for controllers and razor pages except that you cannot apply the authorize attribute to the razor page handlers.

For the purpose of the article, I’ve created two razor pages. One for the premium user and the other for a standard user.

```csharp
[Authorize(Policy = "PremiumOnly")]
public class PremiumLoungeModel : PageModel
{
    public void OnGet()
    {
    }
}

[Authorize(Policy = "StandardOnly")]
public class StandardLoungeModel : PageModel
{
    public void OnGet()
    {
    }
}
```

Here is what the lounge pages look when the user has access to them.

![Premium lounge access with policy based authorization](https://coderethinked.com/wp-content/uploads/2023/03/policy-based-authorization-premium-lounge-coderethinked-com.png)
_With premium lounge access_

![Standard lounge access with policy based authorization](https://coderethinked.com/wp-content/uploads/2023/03/policy-based-authorization-standard-lounge-coderethinked-com.png)
_With Standard Lounge access_

Within the `_Layout.cshtml` file, I’ve included the links to these lounges like this to show these only if the user is signed in.

```html
@if (SignInManager.IsSignedIn(User))
{
    <li class="nav-item">
        <a class="nav-link text-dark special-link" asp-area="" asp-page="/PremiumLounge" style="background: yellow;">Premium Lounge</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark special-link-2" asp-area="" asp-page="/StandardLounge" >Standard Lounge</a>
    </li>
}
```

For the purpose of this article, I’ve included the premium lounge link to the signed-in user as well so that everyone can see it but if they try to access the lounge they should get an access denied message.

Here is how the access denied error looks for both standard and premium lounge pages.

![policy-based-authorization-access-denied-coderethinked-com](https://coderethinked.com/wp-content/uploads/2023/03/policy-based-auth-access-denied-coderethinked-com-1024x278.png)
_Access denied error_

Let’s see how these policies work with a quick demo

<video controls src="https://coderethinked.com/wp-content/uploads/2023/03/policy-based-auth-aspnetcore-demo-cropped.mp4"></video>
_policy-based authorization demo_

## References

- [Policy-based authorization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-7.0)
- [Source code on Github](https://github.com/karthikchintala1/Authorizations/tree/policy-based-authorizations)

The post [Policy-Based Authorization in ASP.NET Core](https://coderethinked.com/policy-based-authorization-in-asp-net-core/) appeared first on [Code Rethinked](https://coderethinked.com).