Skip to main content

Policy-based Authorization

Overview

Policy-based authorization provides a flexible, reusable way to define authorization rules based on requirements and handlers.

Basic Policy

1. Define Policy

builder.Services.AddAuthorization(options =>
{
options.AddPolicy("MinimumAge", policy =>
policy.RequireClaim("Age", "18", "19", "20" /* ... */));

options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));

options.AddPolicy("EmailVerified", policy =>
policy.RequireClaim("EmailVerified", "true"));
});

2. Apply Policy

app.MapGet("/api/adult-content", () => "18+ content")
.RequireAuthorization("MinimumAge");

[Authorize(Policy = "AdminOnly")]
public class AdminController : ControllerBase { }

Custom Requirements

1. Create Requirement

public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }

public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}

2. Create Handler

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
var dateOfBirthClaim = context.User.FindFirst(c => c.Type == "DateOfBirth");

if (dateOfBirthClaim == null)
return Task.CompletedTask;

var dateOfBirth = DateTime.Parse(dateOfBirthClaim.Value);
var age = DateTime.Today.Year - dateOfBirth.Year;

if (dateOfBirth > DateTime.Today.AddYears(-age))
age--;

if (age >= requirement.MinimumAge)
{
context.Succeed(requirement);
}

return Task.CompletedTask;
}
}

3. Register Handler and Policy

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

builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));

options.AddPolicy("AtLeast21", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

Complex Policies

Multiple Requirements (AND logic)

options.AddPolicy("SeniorEmployee", policy =>
{
policy.RequireRole("Employee");
policy.Requirements.Add(new MinimumAgeRequirement(21));
policy.RequireClaim("Department", "Engineering", "Sales");
});

Custom Policy Provider

public class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
{
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith("MinimumAge"))
{
var age = int.Parse(policyName.Substring("MinimumAge".Length));
var policy = new AuthorizationPolicyBuilder()
.AddRequirements(new MinimumAgeRequirement(age))
.Build();

return Task.FromResult(policy);
}

return Task.FromResult<AuthorizationPolicy>(null);
}

// Other methods...
}

// Usage
[Authorize(Policy = "MinimumAge18")]
[Authorize(Policy = "MinimumAge21")]

Accessing HttpContext in Handler

public class SubscriptionHandler : AuthorizationHandler<SubscriptionRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;

public SubscriptionHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}

protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
SubscriptionRequirement requirement)
{
var httpContext = _httpContextAccessor.HttpContext;
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

// Check subscription status from database
// if (hasActiveSubscription)
// context.Succeed(requirement);
}
}

Real-World Example: Premium Content

// Requirement
public class PremiumUserRequirement : IAuthorizationRequirement { }

// Handler
public class PremiumUserHandler : AuthorizationHandler<PremiumUserRequirement>
{
private readonly IUserService _userService;

public PremiumUserHandler(IUserService userService)
{
_userService = userService;
}

protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PremiumUserRequirement requirement)
{
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

if (userId == null)
return;

var isPremium = await _userService.IsPremiumUserAsync(userId);

if (isPremium)
{
context.Succeed(requirement);
}
}
}

// Registration
builder.Services.AddScoped<IAuthorizationHandler, PremiumUserHandler>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("PremiumOnly", policy =>
policy.Requirements.Add(new PremiumUserRequirement()));
});

// Usage
app.MapGet("/api/premium-content", () => "Premium content")
.RequireAuthorization("PremiumOnly");

Interview Questions

Q: When would you use policy-based authorization over role-based? A: Use policies when you need complex authorization logic, multiple conditions, or reusable authorization rules that go beyond simple role checks.

Q: Can a policy have multiple requirements? A: Yes, all requirements must succeed (AND logic). Use multiple handlers for OR logic.

Resources