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
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; }
}
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;
}
}
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}";
}
}
}
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);
}
}
}
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>();
Specify in Controller
Then we can use custom attribute in controller. Add [Permission("GetWeather")]
to Get
method in WeatherForecastController.cs
.
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 withGetWeather
string - PermissionAttribute set
PermissionGetWeather
as Policy name - Policy Provider use the name to add new requirement by using
PermissionRequirement
withGetWeather
- Finally
PermissionRequirementHandler
receives added requirement as argument and handles it
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)
How do you add those Policies in Azure AD?