Pré-requisitos
Mapeando controllers a claims
Antes de criar o filtro de permissões, é necessário configurar um atributo customizado que associa o controller ao nome da claim permitindo que o filtro saiba qual claim verificar em cada endpoint.
namespace Kodigos.API.Attributes;
[AttributeUsage(AttributeTargets.Class)]
public class ResourceNameAttribute : Attribute
{
public string Name { get; }
public ResourceNameAttribute(string name) => Name = name;
}
Você pode criar uma pasta Attributes para deixar a estrutura do projeto organizada
Como utilizar
Para utilizar o atributo customizado, basta adicionar no topo do seu controller com o nome exatamente igual à claim cadastrada no painel do Identity. Se um controller não tem o atributo, o PermissionFilter ignora a verificação de claims e qualquer usuário autenticado consegue acessar a requisição.
[ResourceName("Clientes")] // Vincula à claim "Clientes"
[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase
{
// GET /api/Customer
// PermissionFilter verifica "Clientes" com ":view"
[HttpGet]
public async Task<KRetorno<List<ListCustomerDto>>> List() { ... }
// POST /api/Customer
// PermissionFilter verifica "Clientes" com ":edit"
[HttpPost]
public async Task<KRetorno<Customer>> Create() { ... }
// DELETE /api/Customer/5
//PermissionFilter verifica "Clientes" com ":delete"
[HttpDelete("{id}")]
public async Task<KRetorno> Delete(long id) { ... }
}
Como funciona o filtro
Para centralizar e estruturar as validações de permissões, recomenda-se criar um filtro de autorização que, junto com o atributo customizado, roda automaticamente em cada requisição.
using Kodigos.API.Attributes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Kodigos.API.Filters;
public class PermissionFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
// Consulta o [ResourceName] do controller
// Se o controller não tem o atributo, não faz verificação
var resourceAttr = context.ActionDescriptor.EndpointMetadata
.OfType<ResourceNameAttribute>()
.FirstOrDefault();
if (resourceAttr == null) return; // Sem atributo permite
// Identifica recurso, método e claims do usuário
var resource = resourceAttr.Name; // ex: "Clientes"
var method = context.HttpContext.Request.Method; // ex: "GET"
var claims = context.HttpContext.User.Claims; // Claims do JWT
bool hasPermission = false;
// Mapeia o método HTTP para a ação necessária
if (HttpMethods.IsGet(method))
{
// GET precisa de :view OU :edit
// (quem pode editar, pode visualizar)
hasPermission = claims.Any(c =>
c.Type == resource &&
(c.Value.Contains(":view") || c.Value.Contains(":edit")));
}
else if (HttpMethods.IsPost(method) ||
HttpMethods.IsPut(method) ||
HttpMethods.IsPatch(method))
{
// POST, PUT, PATCH precisa de :edit
hasPermission = claims.Any(c =>
c.Type == resource &&
c.Value.Contains(":edit"));
}
else if (HttpMethods.IsDelete(method))
{
// DELETE precisa de :delete
hasPermission = claims.Any(c =>
c.Type == resource &&
c.Value.Contains(":delete"));
}
// Se não tiver nenhuma permissão, retorna 403
if (!hasPermission)
context.Result = new ForbidResult();
}
}
Você pode criar uma pasta Filters para deixar a estrutura do projeto organizada
Registrando o filtro
O filtro precisa ser registrado globalmente para rodar em todos os controllers. Dessa forma, todo controller que tiver [ResourceName] será verificado automaticamente. Controllers sem o atributo passam direto.
builder.Services.AddControllers(options =>
{
options.Filters.Add<PermissionFilter>(); // Registra globalmente
});