C#: Custom Policy Base Authorization in ASP.NET
Published: 2022-08-08 05:50:00

ASP.NET core provides many methods to authorize users, such as Role base, Claim base and Policy base.

As Role and Claim base are straight forward, I explain how to use Policy base with example.

## Prerequisites

- .NET 6 Web API
- Azure AD
- Visual Studio

## Scenario

In case we cannot obtain enough information from Azure AD as role or claim, then we need to use custom data store to check user authorization. In this article, I use Azure AD just for authentication, and use policy to authorize users.


The policy passed permission name which we can evaluate against custom data store to see if user has the permission.

I also use Custom Policy Provider so that I can easily register many policies in the future as each controller endpoint has different permission requirements.

## Create Web API

I use [webapi template](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-new-sdk-templates#webapi) as starting point.

```shell
dotnet new webapi -n policybase -au SingleOrg
```

Once a project is generated, update appsettings.Development.json for authentication.

The solution already contains authentication/authorization settings in ``Program.cs`` and ``WeatherForecastController.cs``.

## Add Policy related code

### IAuthorizationRequirement

First thing we need to do is to create ``Requirement``.

Create Authorizations folder (optional) in project and add ``PermissionRequirement.cs``. In this class, I specify What we evaluate.

```csharp
using Microsoft.AspNetCore.Authorization;

namespace policybase.Authorizations;

public class PermissionRequirement: IAuthorizationRequirement
{
    public PermissionRequirement(string permission) =>
        Permission = permission;

    public string Permission { get; }
}
```

### AuthorizationHandler

Next, we create handler for the requirement. We can use IAuthorizationHandler to handle multiple requirements, but as we only handle PermissionRequirement in this example, I inherit from AuthorizationHandler. Add ``PermissionRequirementHandler.cs``.

I omit permission verification here and simply mark the requirement as success.

```csharp
using Microsoft.AspNetCore.Authorization;

namespace policybase.Authorizations;

public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>
{  
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        // TODO: Write logic to verify permission
        context.Succeed(requirement);
        return Task.CompletedTask;
    }
}
```

### AuthorizeAttribute

We can use ``[Authorize(Policy = <policy name>)]`` to specify policy if we register policy name one by one. But we use custom provider to dynamically generate policy, so we implement AuthorizeAttribute. Add ``PermissionAttribute.cs``.

The reason we use``Prefix`` is to easily distinguish policies. We want map these attributes into PermissionRequirement. 

The setter sets value into Policy property so that this is recognized as policy.

```csharp
using Microsoft.AspNetCore.Authorization;

namespace policybase.Authorizations;

public class PermissionAttribute : AuthorizeAttribute
{
    const string POLICY_PREFIX = "Permission";

    public PermissionAttribute(string requiredPermission) =>
        RequiredPermission = requiredPermission;

    public string? RequiredPermission
    {
        get 
        {
            return Policy?.Substring(POLICY_PREFIX.Length);
        }
        set 
        {
            Policy = $"{POLICY_PREFIX}{value}";
        }
    }
}
```

### IAuthorizationPolicyProvider

Finally, we add custom policy provider to dynamically register policy. Add ``PermissionPolicyProvider.cs``. We add requirement when ``GetPolicyAsync`` is called. The policyName argument contains ``Prefix`` to identify the policy.

```csharp
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;

namespace policybase.Authorizations;

public class PermissionPolicyProvider : IAuthorizationPolicyProvider
{
    const string POLICY_PREFIX = "Permission";

    public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
    {
        return Task.FromResult(
            new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
            .RequireAuthenticatedUser().Build());
    }

    public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
    {
        return Task.FromResult<AuthorizationPolicy?>(null);
    }

    public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
    {
        if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase))
        {
            var policy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme);
            policy.AddRequirements(new PermissionRequirement(policyName.Substring(POLICY_PREFIX.Length)));
            return Task.FromResult((AuthorizationPolicy?)policy.Build());
        }
        else
        {
            return Task.FromResult<AuthorizationPolicy?>(null);
        }
    }
}
```

## Use policy

### Register to DI
 
Once we complete all code, we need to register them in Program.cs.

```csharp
builder.Services.AddSingleton<IAuthorizationHandler, PermissionRequirementHandler>();
builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
```

### Specify in Controller

Then we can use custom attribute in controller. Add ``[Permission("GetWeather")]`` to ``Get`` method in ``WeatherForecastController.cs``.

![Controller](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t2n3yqq4auvyh1no5ijr.png)
 
## Debug for test

Let's debug to see if all parts work as expected. Put break point to ``GetPolicyAsync`` method in ``PermissionPolicyProvider.cs`` and ``HandleRequirementAsync`` method in ``PermissionRequirementHandler.cs``.

This is how it works.

- User calls WebAPI by adding Bearer authorization header
- The endpoint has ``Permission`` attribute with ``GetWeather`` string
- PermissionAttribute set ``PermissionGetWeather`` as Policy name
- Policy Provider use the name to add new requirement by using ``PermissionRequirement`` with ``GetWeather``
- Finally ``PermissionRequirementHandler`` receives added requirement as argument and handles it

![GetPolicyAsync](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/392wnp99i51d6pk5guqe.png)
 
![HandleRequirementAsync](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cv4gade5i4ch9im63b9n.png)
 
## Summary

By using Custom Policy Provider and Custom Authorization Attribute, we can easily setup dynamic policy and evaluate. In the handler, we can use dependency injection to inject any code for evaluation, which I often inject external service or database service to query if user meet requirements.