How to create a custom AuthorizeAttribute in ASP.NET Core

By Tan Lee Published on Nov 12, 2024  623
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;
        }
    }
}