Scenarios may be useful:
- Using an external service to provide policy evaluation.
- Using a large range of policies, so it doesn't make sense to add each individual authorization policy with an AuthorizationOptions.AddPolicy call.
- Creating policies at runtime based on information in an external data source (like a database) or determining authorization requirements dynamically through another mechanism.
Applied: from ASP.NET Core 8
Step 1: Implement a custom Authorize Attribute
In my case, I implement a UserFeatureAuthorizeAttirbute
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
namespace PracticalAPI.AuthorizationRequirementData
{
public enum FeatureOperator
{
And = 1,
Or = 2
}
public class UserFeatureAuthorizeAttribute : AuthorizeAttribute
, IAuthorizationRequirement
, IAuthorizationRequirementData
{
internal const string PolicyPrefix = "UserFeature_";
public FeatureOperator Operator { get; set; }
public string[] Features { get; set; }
public UserFeatureAuthorizeAttribute(FeatureOperator featureOperator, params string[] features)
{
Operator = featureOperator;
Features = features;
}
public UserFeatureAuthorizeAttribute(string feature)
{
Operator = FeatureOperator.And;
Features = new string[] { feature };
}
public IEnumerable<IAuthorizationRequirement> GetRequirements()
{
yield return this;
}
}
}
Step 2: Implement the AuthorizationHandler
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using System.Globalization;
using System.Threading.Tasks;
using System;
namespace PracticalAPI.AuthorizationRequirementData
{
public class UserFeatureAuthorizationHandler : AuthorizationHandler<UserFeatureAuthorizeAttribute>
{
private readonly ILogger<UserFeatureAuthorizationHandler> _logger;
private static string featureType = "feature";
public UserFeatureAuthorizationHandler(ILogger<UserFeatureAuthorizationHandler> logger)
{
_logger = logger;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
UserFeatureAuthorizeAttribute requirement)
{
if (requirement.Operator == FeatureOperator.And)
{
foreach (var feature in requirement.Features)
{
_logger.LogWarning("Evaluating authorization requirement for feature: {Feature}", feature);
if (!context.User.HasClaim(featureType, feature))
{
context.Fail();
return Task.CompletedTask;
}
}
context.Succeed(requirement);
return Task.CompletedTask;
}
foreach (var feature in requirement.Features)
{
_logger.LogWarning("Evaluating authorization requirement for feature: {Feature}", feature);
if (context.User.HasClaim(featureType, feature))
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
context.Fail();
return Task.CompletedTask;
}
}
}
Step 3: Register the UserFeatureAuthorizationHandler
using AuthRequirementsData.Authorization;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder();
....
// Use Custom Authorization
// builder.Services.AddSingleton<IAuthorizationHandler, UserFeatureAuthorizationHandler>()
var app = builder.Build();
app.MapControllers();
app.Run();
Step 4: Use the custom authorization attribute in controller
[UserFeatureAuthorize("Feature1")]
public async Task<ActionResult<bool>> MyAction([FromBody] RequestModel model)
{
// Logic code go here
}
Yup, this is one of many options that you can use to implement a good authorization for your application. You have to figure out which option is fit your scenario.
Happy coding!
Top comments (0)