Permission-based Authorization in ASP.NET Core with AuthorizationPolicyProvider
There are various approaches to implement dynamic permission-based authorization; In this post I want to implement Custom AuthorizationPolicyProvider to simplify permission-based authorization mechanism in ASP.NET Core.
Introduction
According to the authorization infrastructure in ASP.NET Core, you can use the following piece of code to apply claim-based authorization with custom permission claim-type:
services.AddAuthorization(options =>
{
options.AddPolicy("View Projects",
policy => policy.RequireClaim(CustomClaimTypes.Permission, "projects.view"));
});
And you can use it like below:
[Authorize("View Projects")]
public IActionResult Index(int siteId)
{
return View();
}
This approach is integrated and very simple and you don’t need to do any customization; but, in one real project or in enterprise scale, it is hard to define all permissions as claim-base policies. Fortunately, ASP.NET Core supports to implement Custom AuthorizationPolicyProvider and register it in DI system. One of its uses is:
Using a large range of policies (for different room numbers or ages, for example), so it doesn’t make sense to add each individual authorization policy with an AuthorizationOptions.AddPolicy call.
Implement AuthorizationPolicyProvider
For this purpose, we can implement AuthorizationPolicyProvider or inherit from DefaultAuthorizationPolicyProvider that registered in DI system as default provider.
public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
public AuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)
: base(options)
{
}
public override Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (!policyName.StartsWith(PermissionAuthorizeAttribute.PolicyPrefix, StringComparison.OrdinalIgnoreCase))
{
return base.GetPolicyAsync(policyName);
}
var permissionNames = policyName.Substring(PermissionAuthorizeAttribute.PolicyPrefix.Length).Split(',');
var policy = new AuthorizationPolicyBuilder()
.RequireClaim(CustomClaimTypes.Permission, permissionNames)
.Build();
return Task.FromResult(policy);
}
}
In this implementation, GetPolicyAsync is responsible to find and return one policy based on policyName. However, we can automate the process of defining the policy by overriding it and using an instance of AuthorizationPolicyBuilder. In the body of GetPolicyAsync method, first checked that received policyName starts with “PERMISSION:” or not; then split policyName with ‘,’ character to retrieve permission names. Finally, define policy with retrieved permissions and return it.
Now, To replace this implementation with default registered, use the following code in startup:
services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
Implement PermissionAuthorizeAttribute
As the last step, we need to implement custom AuthorizeAttribute to manipulate Policy property and store permission-names as a comma-separated string in this property.
public class PermissionAuthorizeAttribute : AuthorizeAttribute
{
internal const string PolicyPrefix = "PERMISSION:";/// <summary>
/// Creates a new instance of <see cref="AuthorizeAttribute"/> class.
/// </summary>
/// <param name="permissions">A list of permissions to authorize</param>
public PermissionAuthorizeAttribute(params string[] permissions)
{
Policy = $"{PolicyPrefix}{string.Join(",", permissions)}";
}
}
And to use it:
[PermissionAuthorize(PermissionNames.Projects_View)]
public IActionResult Get(FilteredQueryModel query)
{
//...
}
[PermissionAuthorize(PermissionNames.Projects_Create)]
public IActionResult Post(ProjectModel model)
{
//...
}
Conclusion
With approach that explained in this post, you can simply and with minimum customization, apply dynamic permission authorization in your ASP.NET Core project.
Also, you can find complete implementation in DNTFrameworkCore repository.