skills/security/auth-policies/SKILL.md
Use when implementing policy-based authorization with custom requirements or permissions.
npx skillsauth add faysilalshareef/dotnet-ai-kit auth-policiesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
IAuthorizationRequirement and IAuthorizationHandler pairsIAuthorizationPolicyProvider for permission-based authpublic static class Permissions
{
public const string OrdersRead = "orders:read";
public const string OrdersCreate = "orders:create";
public const string OrdersUpdate = "orders:update";
public const string OrdersDelete = "orders:delete";
public const string ReportsView = "reports:view";
public const string UsersManage = "users:manage";
public const string SettingsManage = "settings:manage";
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class HasPermissionAttribute(string permission)
: AuthorizeAttribute(permission)
{
public string Permission { get; } = permission;
}
public sealed class PermissionRequirement(string permission)
: IAuthorizationRequirement
{
public string Permission { get; } = permission;
}
public sealed class PermissionAuthorizationHandler
: AuthorizationHandler<PermissionRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
var permissions = context.User
.FindFirstValue("permissions")?
.Split(',', StringSplitOptions.RemoveEmptyEntries)
?? [];
if (permissions.Contains(requirement.Permission))
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public sealed class PermissionAuthorizationPolicyProvider(
IOptions<AuthorizationOptions> options)
: DefaultAuthorizationPolicyProvider(options)
{
public override async Task<AuthorizationPolicy?> GetPolicyAsync(
string policyName)
{
// Check for pre-defined policies first
var policy = await base.GetPolicyAsync(policyName);
if (policy is not null)
return policy;
// Create dynamic policy for permission strings
return new AuthorizationPolicyBuilder()
.AddRequirements(new PermissionRequirement(policyName))
.Build();
}
}
// Program.cs
builder.Services.AddSingleton<IAuthorizationPolicyProvider,
PermissionAuthorizationPolicyProvider>();
builder.Services.AddSingleton<IAuthorizationHandler,
PermissionAuthorizationHandler>();
// Pre-defined policies (optional, for complex requirements)
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"))
.AddPolicy("OrderManager", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim("permissions", Permissions.OrdersCreate) &&
context.User.HasClaim("permissions", Permissions.OrdersUpdate)));
// Minimal API
app.MapGet("/orders", GetOrders)
.RequireAuthorization(Permissions.OrdersRead);
app.MapPost("/orders", CreateOrder)
.RequireAuthorization(Permissions.OrdersCreate);
app.MapDelete("/orders/{id}", DeleteOrder)
.RequireAuthorization(Permissions.OrdersDelete);
// Controller
[ApiController]
[Route("api/orders")]
public sealed class OrdersController : ControllerBase
{
[HttpGet]
[HasPermission(Permissions.OrdersRead)]
public async Task<IActionResult> GetOrders() { /* ... */ }
[HttpPost]
[HasPermission(Permissions.OrdersCreate)]
public async Task<IActionResult> CreateOrder() { /* ... */ }
[HttpDelete("{id}")]
[HasPermission(Permissions.OrdersDelete)]
public async Task<IActionResult> DeleteOrder(Guid id) { /* ... */ }
}
// Requirement for resource ownership
public sealed class OrderOwnerRequirement : IAuthorizationRequirement { }
// Handler checks if user owns the resource
public sealed class OrderOwnerAuthorizationHandler
: AuthorizationHandler<OrderOwnerRequirement, Order>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OrderOwnerRequirement requirement,
Order resource)
{
var userId = context.User
.FindFirstValue(JwtRegisteredClaimNames.Sub);
if (resource.CreatedBy == userId)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
// Usage in handler
internal sealed class UpdateOrderHandler(
IOrderRepository repository,
IAuthorizationService authorizationService,
IHttpContextAccessor httpContextAccessor)
: IRequestHandler<UpdateOrderCommand, Result>
{
public async Task<Result> Handle(
UpdateOrderCommand request, CancellationToken ct)
{
var order = await repository.FindAsync(request.OrderId, ct);
if (order is null)
return Result.Failure(Error.NotFound("Order.NotFound",
"Order not found"));
var user = httpContextAccessor.HttpContext!.User;
var authResult = await authorizationService.AuthorizeAsync(
user, order, new OrderOwnerRequirement());
if (!authResult.Succeeded)
return Result.Failure(Error.Forbidden("Order.Forbidden",
"You can only update your own orders"));
// Update order...
return Result.Success();
}
}
// Alternative: load permissions from DB instead of JWT claims
public sealed class DbPermissionAuthorizationHandler(
IServiceScopeFactory scopeFactory)
: AuthorizationHandler<PermissionRequirement>
{
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
var userId = context.User
.FindFirstValue(JwtRegisteredClaimNames.Sub);
if (userId is null) return;
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider
.GetRequiredService<AppDbContext>();
var hasPermission = await db.UserPermissions
.AnyAsync(p =>
p.UserId == Guid.Parse(userId) &&
p.Permission == requirement.Permission);
if (hasPermission)
context.Succeed(requirement);
}
}
User.IsInRole("Admin"))IAuthorizationPolicyProvider for dynamic policiesAuthorizeAttribute subclassesIAuthorizationPolicyProvider implementationsIAuthorizationHandler implementations"permissions" claim in token generationRequireAuthorization calls on endpointsHasPermissionAttribute and requirement/handler pair[HasPermission] or RequireAuthorization to all endpointsdata-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.