DEV Community

Kenichiro Nakamura
Kenichiro Nakamura

Posted on

C#: Custom Policy Base Authorization in ASP.NET

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 as starting point.

dotnet new webapi -n policybase -au SingleOrg
Enter fullscreen mode Exit fullscreen mode

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.

using Microsoft.AspNetCore.Authorization;

namespace policybase.Authorizations;

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

    public string Permission { get; }
}
Enter fullscreen mode Exit fullscreen mode

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.

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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 usePrefix 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.

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}";
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Use policy

Register to DI

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

builder.Services.AddSingleton<IAuthorizationHandler, PermissionRequirementHandler>();
builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
Enter fullscreen mode Exit fullscreen mode

Specify in Controller

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

Controller

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

HandleRequirementAsync

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.

Top comments (1)

Collapse
 
jorgellanque profile image
Jorge LLanque

How do you add those Policies in Azure AD?