How to create a custom AuthorizeAttribute in ASP.NET Core

By FoxLearn 11/12/2024 2:39:31 AM   5
In ASP.NET Core, the AuthorizeAttribute no longer provides the AuthorizeCore method as in previous versions of ASP.NET MVC

The ASP.NET Core team recommends using the policy-based authorization approach instead of the older AuthorizeCore method, which has been deprecated. The new approach is based on defining authorization policies and applying them with the [Authorize] attribute.

The introduction of policy-based authorization in ASP.NET Core is a powerful feature, but it's not always the best fit for every scenario, especially when dealing with scenarios that require fine-grained control over permissions, such as when each action or controller requires a specific claim, like "CanCreateOrder," "CanReadOrder," etc.

In such scenarios, using the IAuthorizationFilter interface provides a lightweight and effective way to enforce claim-based requirements on controllers or actions.

This approach bypasses the complexity of defining policies and handlers, and allows you to check for specific claims directly, offering a more concise and flexible solution for many common authorization needs.

public class ClaimRequirementAttribute : TypeFilterAttribute
{
    public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new object[] {new Claim(claimType, claimValue) };
    }
}

public class ClaimRequirementFilter : IAuthorizationFilter
{
    readonly Claim _claim;

    public ClaimRequirementFilter(Claim claim)
    {
        _claim = claim;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
        if (!hasClaim)
        {
            context.Result = new ForbidResult();
        }
    }
}

Usage

[Route("api/resource")]
public class MyController : Controller
{
    [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
    [HttpGet]
    public IActionResult GetResource()
    {
        return Ok();
    }
}

In ASP.NET Core 2, the framework reintroduced the ability to inherit from AuthorizeAttribute, but with a key change: you now need to implement either IAuthorizationFilter or IAsyncAuthorizationFilter for custom authorization logic.

For example:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private readonly string _someFilterParameter;

    public CustomAuthorizeAttribute(string someFilterParameter)
    {
        _someFilterParameter = someFilterParameter;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            // it isn't needed to set unauthorized result 
            // as the base class already requires the user to be authenticated
            // this also makes redirect to a login page work properly
            // context.Result = new UnauthorizedResult();
            return;
        }

        // you can also use registered services
        var someService = context.HttpContext.RequestServices.GetService<ISomeService>();

        var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter);
        if (!isAuthorized)
        {
            context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            return;
        }
    }
}