Claims-Based Authorization and Policies in C

This article provides a practical guide to implementing claims-based security and custom authorization policies in modern C# applications using ASP.NET Core. You will learn the fundamentals of claims, how to define simple and requirement-driven policies, how to apply them declaratively to endpoints, and how to perform imperative security checks directly within your business logic.

Understanding Claims and Identities

In .NET, an identity is represented by a ClaimsIdentity, which contains a collection of claims. A claim is a key-value pair issued by a trusted authority describing a specific user attribute—such as an email address, department, or date of birth. Rather than relying solely on traditional role checks, claims provide fine-grained context about what an identity is and what it represents.

Configuring Basic Claims-Based Policies

Policies decouple authorization logic from controllers and endpoints. You register authorization policies in your application's setup file (Program.cs) using AddAuthorization.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication();
builder.Services.AddAuthorization(options =>
{
    // Requires the presence of a specific claim, regardless of value
    options.AddPolicy("MustBeEmployee", policy =>
        policy.RequireClaim("EmployeeId"));

    // Requires the claim to have one of the specified values
    options.AddPolicy("ITDepartmentOnly", policy =>
        policy.RequireClaim("Department", "IT", "DevOps"));
});

var app = builder.Build();

Applying Policies to Endpoints

You can apply policies declaratively using the [Authorize] attribute on controllers or Minimal API route handlers.

Using Controllers:

[ApiController]
[Route("api/[controller]")]
public class InternalDataController : ControllerBase
{
    [HttpGet]
    [Authorize(Policy = "ITDepartmentOnly")]
    public IActionResult GetRestrictedData()
    {
        return Ok(new { Data = "Sensitive IT Configuration" });
    }
}

Using Minimal APIs:

app.MapGet("/api/employee-portal", () => "Welcome to the intranet")
   .RequireAuthorization("MustBeEmployee");

Creating Custom Authorization Requirements and Handlers

When authorization rules require custom logic beyond simple string matching—such as calculating age or validating against a database—you combine custom requirements with authorization handlers.

1. Define the Requirement

A requirement implements IAuthorizationRequirement:

using Microsoft.AspNetCore.Authorization;

public class MinimumExperienceRequirement : IAuthorizationRequirement
{
    public int RequiredYears { get; }

    public MinimumExperienceRequirement(int requiredYears)
    {
        RequiredYears = requiredYears;
    }
}

2. Implement the Authorization Handler

The handler evaluates the claims of the current user against the requirement:

using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;

public class MinimumExperienceHandler : AuthorizationHandler<MinimumExperienceRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        MinimumExperienceRequirement requirement)
    {
        var hireDateClaim = context.User.FindFirst(c => c.Type == "HireDate");

        if (hireDateClaim != null && DateTime.TryParse(hireDateClaim.Value, out var hireDate))
        {
            var yearsOfExperience = (DateTime.UtcNow - hireDate).TotalDays / 365.25;

            if (yearsOfExperience >= requirement.RequiredYears)
            {
                context.Succeed(requirement);
            }
        }

        return Task.CompletedTask;
    }
}

3. Register the Policy and Handler

Register the handler in the dependency injection container and add the policy to authorization options:

builder.Services.AddSingleton<IAuthorizationHandler, MinimumExperienceHandler>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("SeniorStaffOnly", policy =>
        policy.Requirements.Add(new MinimumExperienceRequirement(5)));
});

Performing Imperative Authorization Checks

When authorization depends on runtime data or resources not known until the method executes, inject IAuthorizationService to evaluate policies imperatively.

[ApiController]
[Route("api/documents")]
public class DocumentController : ControllerBase
{
    private readonly IAuthorizationService _authorizationService;

    public DocumentController(IAuthorizationService authorizationService)
    {
        _authorizationService = authorizationService;
    }

    [HttpPost("{id}/publish")]
    public async Task<IActionResult> PublishDocument(int id)
    {
        var result = await _authorizationService.AuthorizeAsync(User, "SeniorStaffOnly");

        if (!result.Succeeded)
        {
            return Forbid();
        }

        return Ok($"Document {id} published.");
    }
}