Comprehensive Guide to Custom Authorization Attributes in ASP.NET Core

Nov 12, 2025 · Programming · 15 views · 7.8

Keywords: ASP.NET Core | Custom Authorization | Policy Authorization | IAuthorizationFilter | TypeFilterAttribute

Abstract: This article provides an in-depth exploration of various methods for implementing custom authorization attributes in ASP.NET Core, with a primary focus on policy-based authorization mechanisms and custom authorization filters. It details how to create dependency injection-enabled custom authorization attributes using TypeFilterAttribute combined with IAuthorizationFilter, and how to build flexible, extensible authorization systems through policies, requirements, and handlers. Through concrete code examples, the article demonstrates complete implementation processes ranging from simple authorization checks to complex business logic validation, offering practical technical guidance for developers.

Introduction

In modern web application development, authorization mechanisms are crucial components for ensuring system security. ASP.NET Core provides a robust authorization framework, but in practical projects, we often need to implement custom authorization logic based on specific business requirements. Unlike traditional ASP.NET, ASP.NET Core has undergone significant refactoring in its authorization mechanism, removing the AuthorizeCore method and introducing a more flexible and extensible authorization system.

Overview of ASP.NET Core Authorization Mechanism

The authorization system in ASP.NET Core is built upon identity authentication, defining access control rules in a declarative manner. The system provides multiple authorization approaches, including role-based authorization, claim-based authorization, and policy-based authorization. Among these, policy-based authorization is the officially recommended approach by Microsoft, which decomposes authorization logic into three core concepts: policies, requirements, and handlers, achieving high configurability and reusability of authorization logic.

Implementation Methods for Custom Authorization Attributes

Using TypeFilterAttribute and IAuthorizationFilter

In ASP.NET Core, while you cannot directly inherit from AuthorizeAttribute and override authorization methods, you can create custom authorization attributes by implementing the IAuthorizationFilter interface combined with TypeFilterAttribute. This approach allows access to services in the dependency injection container during the authorization process, providing support for complex authorization logic.

Here is an example implementation of a custom authorization attribute based on session ID validation:

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();
        }
    }
}

When using this, we can apply the custom authorization attribute to controllers or action methods:

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

Policy-Based Authorization Implementation

Policy-based authorization is the recommended approach in ASP.NET Core, which decomposes authorization logic into three independent parts: policies, requirements, and handlers. This design makes authorization logic more modular and testable.

Here is a complete implementation of an age validation requirement:

public class Over18Requirement : AuthorizationHandler<Over18Requirement>, IAuthorizationRequirement
{
    public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
        {
            context.Fail();
            return;
        }

        var dobVal = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value;
        var dateOfBirth = Convert.ToDateTime(dobVal);
        int age = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-age))
        {
            age--;
        }

        if (age >= 18)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
    }
}

Register the policy during application startup:

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", 
        policy => policy.Requirements.Add(new Over18Requirement()));
});

Use the policy in controllers:

[Authorize(Policy = "Over18")]
public class RestrictedController : Controller
{
    // Controller code
}

Dependency Injection in Custom Authorization

In practical applications, authorization logic often needs to access external services such as database connections, caching services, or other business logic services. By combining TypeFilterAttribute with dependency injection, we can inject required services into custom authorization filters.

Here is an example of a custom authorization filter that supports dependency injection:

public class SessionRequirementFilter : IAuthorizationFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ISessionService _sessionService;

    public SessionRequirementFilter(IHttpContextAccessor httpContextAccessor, ISessionService sessionService)
    {
        _httpContextAccessor = httpContextAccessor;
        _sessionService = sessionService;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var sessionId = _httpContextAccessor.HttpContext!.Request.Headers["X-Session-Id"].FirstOrDefault();
        
        if (string.IsNullOrEmpty(sessionId) || !_sessionService.ValidateSession(sessionId))
        {
            context.Result = new UnauthorizedResult();
            return;
        }
    }
}

Performance Optimization Considerations

When implementing custom authorization logic, performance is a critical factor to consider. Here are some optimization recommendations:

Best Practices

Based on practical project experience, we summarize the following best practices:

  1. Prioritize policy-based authorization mechanisms as they provide better testability and maintainability
  2. Use custom authorization attributes for simple claim checks
  3. Ensure separation of authorization logic from business logic to maintain code clarity
  4. Write unit tests for custom authorization components to ensure logical correctness
  5. Consider security aspects and avoid exposing sensitive information during authorization processes

Conclusion

ASP.NET Core provides a flexible and powerful authorization framework that supports multiple custom authorization implementation methods. By appropriately selecting and using these mechanisms, developers can build authorization systems that are both secure and easy to maintain. Although policy-based authorization mechanisms have a steeper learning curve, they offer the best extensibility and testability, making them the preferred choice for large projects. For simple authorization requirements, custom authorization attributes provide a quick implementation path. Regardless of the chosen approach, understanding the working principles of authorization mechanisms and best practices is key to ensuring system security.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.