ITLab.Template.DevPack.Authorization
Policy-based permission system. Provides the infrastructure to declare permissions on endpoints (RequireAuthorization("Pedidos.Create")) and on commands/queries ([RequiresPermission("Pedidos.Edit")]), with a single avaliação delegate at runtime via IPermissionEvaluator.
The DevPack is agnostic about how permissions are sourced. JWT claims, DB lookup with cache, hierarchical inheritance — all of those are decisions for the consuming product. The DevPack provides the contract and the wiring.
Componentes
| Tipo | Nome | Responsabilidade |
|---|---|---|
| Interface | IPermissionEvaluator | Contract for resolving whether the current context holds a permission |
| Interface | IRolePermissionResolver | Contract for resolving permission strings from a role-name set |
| Class | RoleBasedPermissionEvaluator | Default IPermissionEvaluator combining ICurrentUser.UserRoles + IRolePermissionResolver, with per-instance cache |
| Class | PermissionAuthorizationRequirement | IAuthorizationRequirement carrying the permission identifier |
| Class | PermissionAuthorizationHandler | AuthorizationHandler<...> that delegates to IPermissionEvaluator |
| Class | PermissionAuthorizationPolicyProvider | Treats unknown policy names as permission requirements at endpoint level |
| Attribute | RequiresPermissionAttribute | Marks Commands/Queries with the permissions they need; multiple form an AND |
| Class | AuthorizationBehavior<TRequest, TResponse> | Mediator pipeline behavior that enforces [RequiresPermission] before the handler |
| Static Class | DevPackAuthorizationExtensions | DI registration helpers (AddDevPackAuthorization, AddDevPackRoleBasedPermissionEvaluator<TResolver>) |
Como usar
1. Defina a classe estática hierárquica de permissões no projeto consumidor
Permissions catalogue location
Convenção: Application/Common/Authorization/{ProjectName}Permissions.cs, uma única classe estática com classes estáticas aninhadas por feature group:
namespace MyApp.Application.Common.Authorization;
public static class MyAppPermissions
{
public static class Pedidos
{
public const string Create = "Pedidos.Create";
public const string Edit = "Pedidos.Edit";
public const string Read = "Pedidos.Read";
public const string Delete = "Pedidos.Delete";
}
public static class Clientes
{
public const string Create = "Clientes.Create";
public const string Deactivate = "Clientes.Deactivate";
}
}
Use as constantes em dois lugares:
RequireAuthorization(MyAppPermissions.X.Y)em endpoints Minimal API.[RequiresPermission(MyAppPermissions.X.Y)]em commands/queries somente quando a permissão for contextual (depende do payload do command) — ver § Permission convention: endpoint vs command.
PermissionsGenerator (em Helpers/) extrai todas as strings:
var allPermissions = PermissionsGenerator.GenerateJson(typeof(MyAppPermissions));
2. Escolha a estratégia de resolução de permissões
Opção A — Role-based (default sugerido na 10.2.0)
Implemente IRolePermissionResolver no projeto consumidor — tipicamente uma
classe que consulta a tabela RolePermission com cache em memória ou Redis:
public sealed class DbRolePermissionResolver(MyDbContext db, IMemoryCache cache)
: IRolePermissionResolver
{
public async Task<IReadOnlyCollection<string>> ResolvePermissionsAsync(
IEnumerable<string> roles, CancellationToken ct = default)
{
var key = $"perm:{string.Join('|', roles.OrderBy(r => r))}";
return await cache.GetOrCreateAsync(key, async _ =>
{
var rolesArray = roles.ToArray();
var permissions = await db.RolePermissions
.Where(rp => rolesArray.Contains(rp.RoleName))
.Select(rp => rp.Permission)
.Distinct()
.ToListAsync(ct);
return (IReadOnlyCollection<string>)permissions;
}) ?? [];
}
}
Opção B — Implementação custom
Implemente IPermissionEvaluator direto se as regras forem mais complexas
(hierarquia, herança por organização, regras temporais).
3. Registre no Program.cs
services.AddAuthorization();
// Opção A — Role-based:
services.AddDevPackRoleBasedPermissionEvaluator<DbRolePermissionResolver>();
// Opção B — Implementação custom:
// services.AddScoped<IPermissionEvaluator, MyCustomEvaluator>();
services.AddDevPackAuthorization();
4. Aplique no endpoint (Camada 1 — early)
group.MapPost("/", CriarPedido)
.RequireAuthorization(MyAppPermissions.Pedidos.Create);
5. (Opcional) Reforce no Command/Query (Camada 2 — contextual)
[RequiresPermission(MyAppPermissions.Pedidos.Edit)]
public sealed record EditarPedidoCommand(Guid PedidoId, string Novo) : ICommand<EditarPedidoResponse>;
Permission convention: endpoint vs command
- Default (genérica): declare a permissão somente no endpoint via
RequireAuthorization(...). Suficiente para ~90% dos casos. - Contextual: adicione
[RequiresPermission(...)]no command/query apenas quando a permissão depender do payload (ex.: tenant scoping, ownership de recurso) e precisar ser re-checada em entry-points não-HTTP (background jobs, integration tests bypassando endpoints, mensageria interna).
Declarar a mesma permissão genérica nas duas camadas é redundante (duas chamadas a IPermissionEvaluator por request, sem ganho de segurança).
Em resumo:
- Endpoint (
RequireAuthorization("...")) — avalia early, antes do request body ser deserializado completamente. Default para "pode invocar esta rota?". - Pipeline behavior (
[RequiresPermission("...")]) — opt-in apenas quando a permissão for contextual ao payload, ou quando o handler é invocado fora de HTTP.
Convenção de naming
Hierárquico com pontos: {Feature}.{Operação} (ex.: Pedidos.Create, Reports.Financeiro.Export).
O que não fazer
- Não verificar permissões manualmente dentro do corpo do handler — use o sistema declarativo.
- Não hardcode strings literais de permissões espalhadas — sempre via classe estática
{Projeto}Permissions. - Não criar sistema paralelo de roles ad-hoc (
if (user.Role == "Admin")). Roles vivem no Identity; autorização funcional via permissões. - Não substituir
PermissionAuthorizationPolicyProviderpor uma versão que só atende permissões — o decorator deve ainda fazer fallback aoDefaultAuthorizationPolicyProviderpara policies registradas explicitamente.
Extensão pelo produto
- Produtos podem implementar
IPermissionEvaluatorcom cache (IMemoryCache), banco (com cache TTL) ou claims do JWT. - Produtos podem adicionar suas próprias
IAuthorizationRequirementpara regras avançadas (ex.: ownership, tenant scoping); o DevPack não opina sobre essas extensões. - Produtos podem combinar
[RequiresPermission]com outros atributos próprios em comandos para implementar políticas multi-fator.