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.