Skip to main content

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.
ResourceNameAttribute.cs
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.
CustomerController.cs
[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.
PermissionFilter.cs
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.
Program.cs
builder.Services.AddControllers(options =>
{
    options.Filters.Add<PermissionFilter>();  // Registra globalmente
});