Authorization Pattern¶
Module-based access control beyond role-based auth.
Overview¶
flowchart TB
A[Request] --> B{Authenticated?}
B -->|No| X1[401]
B -->|Yes| C{Has Role?}
C -->|No| X2[403]
C -->|Yes| D{Module Enabled?}
D -->|No| X3[403]
D -->|Yes| E[Handler] Module Types¶
See Feature Modules for the available modules and their features.
Authorization Attribute¶
Applied to controllers to require module access:
The attribute extends AuthorizeAttribute and sets a policy:
public sealed class AuthorizeModuleAttribute : AuthorizeAttribute
{
public AuthorizeModuleAttribute(ModuleType module)
{
Policy = module.ToString();
}
}
Policy Configuration¶
Policies are configured at startup for each module:
foreach (var module in Enum.GetValues<ModuleType>())
{
builder.AddPolicy(module.ToString(), policy =>
policy.RequireAssertion(context =>
{
var claim = context.User.FindFirst(AuthConstants.ModulesClaim)?.Value;
if (string.IsNullOrEmpty(claim))
{
return true;
}
var modules = JsonSerializer.Deserialize<List<string>>(claim) ?? [];
return modules.Contains(module.ToString());
}));
}
Permission Version¶
When permissions change, the user's permission version increments:
JWT includes version claim:
Middleware validates version matches - forces token refresh on mismatch.
Combining with Roles¶
[Authorize(Roles = "Admin")]
[AuthorizeModule(ModuleType.Budget)]
public sealed record DeleteCategoryCommand(...);
Both checks must pass.
Admin Bypass¶
Admins (configured via Security__AdminUserNames) bypass module checks but still require authentication.
Related¶
- Authentication — Auth flow
- Middleware — Permission version middleware
- CQRS — Pipeline behaviors