MÓDULO 05 · CONCEITO 11 DE 14

Auth & Authorization como aspect

Duas perguntas que respondem a coisas distintas — quem é, e o que pode — merecem aspects distintos. [Authorize], Depends(security), middleware de auth, RBAC, ABAC, ReBAC, policy engines.

Tempo de leitura ~22 min Pré-requisito Conceitos 04 e 05 (middleware, interceptors) Próximo Transação e Unit of Work

Em qualquer aplicação não-trivial, duas perguntas precisam ser respondidas em quase toda chamada que cruza fronteira de segurança: "quem está chamando?" (autenticação, AuthN) e "essa identidade pode fazer essa operação?" (autorização, AuthZ). São duas perguntas distintas, com mecanismos distintos, e tratá-las como uma coisa só é a fonte mais comum de erros sutis em segurança de aplicação. Sistemas maduros separam as duas em aspects distintos: middleware faz AuthN globalmente, interceptor (ou policy) por operação faz AuthZ baseada no recurso.

A literatura, infelizmente, ajuda a confundir. Auth é prefixo ambíguo; bibliotecas chamadas "Auth" frequentemente misturam os dois mundos. Vale forçar o vocabulário no time: AuthN responde com identidade ("é o usuário Camila"); AuthZ responde com decisão sobre essa identidade ("Camila pode atualizar este pedido"). AuthN tipicamente acontece na borda do sistema (uma vez por request), com base em token JWT, sessão de cookie, ou certificado. AuthZ acontece em camadas mais internas, frequentemente por operação, e depende não só de quem é mas de o que está sendo acessado.

Há também a tentação histórica de implementar AuthZ como "verificação inline": if user.role != "admin": raise Forbidden() espalhado pelos handlers. Funciona em sistema pequeno, vira tangling em sistema crescente, e explode em sistema com regras complexas de autorização. Aspect-driven é a saída — e o tipo de aspect (middleware vs filter vs policy engine externo) varia conforme o domínio.

Este conceito articula a separação AuthN/AuthZ, mostra os modelos clássicos de autorização (RBAC, ABAC, ReBAC), e enuncia heurísticas de organização. Implementação detalhada de OAuth 2.1, OIDC, e threat modeling fica para o módulo 11 (Segurança); aqui o foco é como o cross-cutting concern se organiza em código de aplicação.

Autenticação — onde, e por quê na borda

AuthN responde "quem está chamando?". Em 2026, em aplicações web modernas, a resposta tipicamente vem de um access token assinado — geralmente JWT (JSON Web Token, RFC 7519, 2015) emitido por um provider OAuth 2.1/OIDC (Auth0, Keycloak, Cognito, Azure AD, Google Identity, etc.). O cliente envia o token via header Authorization: Bearer ...; o servidor valida assinatura, expiração, e issuer; extrai claims (subject, email, scopes, roles); e segue.

A regra arquitetural é fazer AuthN uma vez, na borda. Middleware HTTP: o primeiro middleware após tratamento de erro e correlation ID é o de AuthN. Ele extrai o token, valida, popula HttpContext.User (em .NET) ou request.state.user (em FastAPI) ou contexto similar, e entrega para o resto do pipeline. Quem vier depois lê a identidade do contexto, não revalida o token. AuthN duplicada em camadas internas é desperdício e vetor de inconsistência.

// .NET 10 — JWT bearer authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "https://auth.example.com/";
        options.Audience = "catalog-api";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = TimeSpan.FromSeconds(30),
        };
    });

builder.Services.AddAuthorization();      // ativa o pipeline de policies

var app = builder.Build();
app.UseAuthentication();                   // popula User
app.UseAuthorization();                    // executa policies por endpoint
app.MapControllers();

Note três detalhes. Primeiro: validação de JWT é automática quando configurada — UseAuthentication decodifica o token, valida assinatura via JWKS (RFC 7517) buscado periodicamente do issuer, e popula User. Segundo: ClockSkew de 30s tolera relógios ligeiramente diferentes entre servidores — sem isso, tokens emitidos há 1s podem falhar se os relógios divergem milissegundos. Terceiro: UseAuthentication e UseAuthorization são middlewares distintos, mesmo em quase todo framework. A separação é estrutural.

Autorização — RBAC, ABAC, ReBAC

AuthZ é mais sutil porque depende do modelo de autorização escolhido. Há três modelos canônicos, e a escolha tem implicações arquiteturais profundas.

RBAC — Role-Based Access Control

O modelo mais antigo, formalizado nos anos 1990 por David Ferraiolo e Richard Kuhn (NIST). Permissões são associadas a papéis (admin, editor, viewer), e usuários recebem papéis. A pergunta de autorização é "esse usuário tem papel X?". Simples, escalável, suficiente para muitos sistemas.

RBAC perde tração quando autorização depende de contexto: "editor pode editar artigo, mas só os artigos que ele mesmo criou", ou "gerente pode aprovar despesa, mas só na filial dele". Esses cenários puxam a equipe a criar dezenas de papéis inflados (editor-da-filial-SP, editor-da-filial-RJ...) ou a misturar lógica inline ("se papel == editor && resource.author_id == user.id → ok"). Sintoma clássico de "RBAC esticando demais".

ABAC — Attribute-Based Access Control

Generalização: a decisão de autorização é uma função booleana sobre atributos do usuário, do recurso, da ação, e do ambiente. "Pode editar se user.department == resource.department AND user.security_clearance >= resource.classification AND now está em horário comercial". É expressivo o suficiente para qualquer cenário, mas a complexidade pode crescer rápido.

Implementação típica de ABAC usa policy engine — OPA (Open Policy Agent), Cedar (AWS), Casbin. Você escreve regras em DSL (Rego para OPA, Cedar policy language), o engine avalia, e a aplicação só pergunta "permitido?". A separação engine ↔ aplicação é o que permite atualizar políticas sem deploy de código.

ReBAC — Relationship-Based Access Control

Modelo descrito no paper Zanzibar do Google (2019), Carl Lesniewski-Laas e equipe. Autorização baseada em grafo de relações: "Camila é proprietária do projeto P; projeto P contém o documento D; logo, Camila pode editar D". É o modelo por trás de Google Docs, GitHub, e qualquer sistema com permissões hierárquicas/colaborativas. Em 2026, bibliotecas como SpiceDB (Authzed), OpenFGA (originalmente Auth0/Okta), e Permify implementam Zanzibar para aplicações fora do Google.

A diferença prática: em ReBAC, autorização é uma query no grafo ("existe caminho de relação válido entre Camila e D que autorize action edit?"), não uma verificação local de atributos. A complexidade de modelagem é alta inicialmente, mas o sistema escala para hierarquias e compartilhamento de forma elegante.

A escolha entre os três é decisão arquitetural. Heurística pragmática: comece com RBAC; se virar dezenas de papéis inflados, considere ABAC com policy engine; se a essência do domínio é compartilhamento e hierarquia (multi-tenant SaaS, colaboração de documentos), avalie ReBAC desde o início.

Padrões de aplicação em código

Independente do modelo, a forma como AuthZ aparece no código tem três padrões frequentes. Reconhecer qual a equipe usa ajuda a articular consistência.

Atributo declarativo — [Authorize(Policy = "...")]

A regra fica anotada no método/endpoint, e o framework executa antes do handler. ASP.NET Core [Authorize(Policy = "EditarPedido")] com policy definida no startup; FastAPI Depends(require_permission("pedido:editar")); Spring @PreAuthorize("hasRole('ADMIN')"). É a forma mais limpa para regras simples (papel necessário, escopo necessário). Menos limpa para regras dependentes de recurso ("dono do pedido"), porque o atributo precisa ler o ID do path/body.

Filter ou interceptor por recurso

Quando a regra depende do recurso (e portanto precisa carregar o recurso para decidir), filter no nível de método (ou Depends em FastAPI) é o lugar. Conceito 05 mostrou o exemplo de "dono do pedido pode ler o pedido". Esse padrão consolida três operações em uma: validar que o recurso existe, validar autorização, e disponibilizar o recurso ao handler.

Policy engine externo

Para regras complexas ou que mudam sem deploy, o handler chama um engine externo: opa.Evaluate(input), spicedb.CheckPermission(...). O engine retorna booleano (com motivo opcional), e o handler segue. A vantagem é que regras vivem em DSL com versionamento próprio, podem ser auditadas, testadas separadamente, e atualizadas sem alterar a aplicação. Custos: latência adicional (rede até o engine), complexidade operacional (mais um sistema para manter).

O mesmo cenário, três abordagens

Considere o cenário canônico: API de pedidos onde GET /pedidos/{id} só pode ser acessado pelo cliente que criou o pedido ou por um operador com papel customer-support.

C# — Authorization Policy + Resource-based Authorization
// startup
builder.Services.AddAuthorization(o =>
{
    o.AddPolicy("LerPedido", p => p.Requirements.Add(new LerPedidoRequirement()));
});
builder.Services.AddScoped<IAuthorizationHandler, LerPedidoHandler>();

// requirement
public class LerPedidoRequirement : IAuthorizationRequirement { }

// handler
public class LerPedidoHandler : AuthorizationHandler<LerPedidoRequirement, Pedido>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext ctx,
        LerPedidoRequirement req, Pedido pedido)
    {
        var userId = ctx.User.FindFirstValue("sub");
        if (pedido.ClienteId == userId ||
            ctx.User.IsInRole("customer-support"))
        {
            ctx.Succeed(req);
        }
        return Task.CompletedTask;
    }
}

// endpoint
app.MapGet("/pedidos/{id}", async (Guid id, IAuthorizationService authz,
    ClaimsPrincipal user, IPedidoRepository repo) =>
{
    var pedido = await repo.ObterAsync(id);
    if (pedido is null) return Results.NotFound();

    var result = await authz.AuthorizeAsync(user, pedido, "LerPedido");
    return result.Succeeded
        ? Results.Ok(pedido)
        : Results.Forbid();
});

ASP.NET Core tem resource-based authorization como cidadão de primeira classe. O AuthorizationHandler<TRequirement, TResource> recebe o recurso e a identidade — perfeito para regras que precisam dos dois.

Python — Depends factory que carrega e autoriza
def require_pedido_access(
    id: UUID,
    user: User = Depends(current_user),
    repo: PedidoRepo = Depends(get_repo),
) -> Pedido:
    pedido = repo.obter(id)
    if pedido is None:
        raise HTTPException(status.HTTP_404_NOT_FOUND)
    if (pedido.cliente_id != user.id
            and "customer-support" not in user.roles):
        raise HTTPException(status.HTTP_403_FORBIDDEN)
    return pedido

@app.get("/pedidos/{id}")
async def obter_pedido(pedido: Pedido = Depends(require_pedido_access)) -> PedidoOut:
    return PedidoOut.from_domain(pedido)

Em FastAPI, dependency carrega o pedido e autoriza em uma operação. O endpoint recebe o pedido já validado — não repete a query. Ergonomia alta, regra explícita.

Go — middleware específico + extração no handler
// middleware: valida AuthN e popula context
func WithAuth(jwks Verifier) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            tok := r.Header.Get("Authorization")
            user, err := jwks.Validate(tok)
            if err != nil { http.Error(w, "unauthorized", 401); return }
            ctx := context.WithValue(r.Context(), userKey, user)
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

// handler com authorization explícita
func (h *Handlers) ObterPedido(w http.ResponseWriter, r *http.Request) {
    id, _ := uuid.Parse(chi.URLParam(r, "id"))
    user := userFromCtx(r.Context())

    pedido, err := h.repo.Obter(r.Context(), id)
    if err != nil { http.NotFound(w, r); return }

    if pedido.ClienteID != user.ID && !user.HasRole("customer-support") {
        http.Error(w, "forbidden", 403)
        return
    }
    respondJSON(w, 200, pedido)
}

// composição
r.Use(WithAuth(verifier))
r.Get("/pedidos/{id}", h.ObterPedido)

Go é o mais explícito. Authorization fica no handler porque depende do recurso carregado; refatorar para interceptor é possível com gRPC, mas em HTTP a comunidade prefere a versão direta.

Policy engine externo — quando vale

Aplicações com regras complexas e em mudança constante (e-com­merce multi-tenant, plataformas SaaS, sistemas regulados) podem ganhar com policy engine externo. OPA (Open Policy Agent, CNCF, 2018) é o mais difundido. A regra é escrita em Rego:

# policy.rego — regra de leitura de pedido
package catalog.pedidos

default allow := false

# dono pode ler
allow if {
    input.action == "read"
    input.resource.cliente_id == input.user.id
}

# customer-support pode ler tudo
allow if {
    input.action == "read"
    "customer-support" in input.user.roles
}

# admin pode ler em qualquer ambiente
allow if {
    input.action == "read"
    input.user.is_admin
}

A aplicação chama OPA via HTTP ou via SDK embutido (Go OPA, Python opa-python):

// Go — chamada ao OPA embutido
result, err := opa.Eval(ctx, "data.catalog.pedidos.allow",
    rego.EvalInput(map[string]any{
        "action":   "read",
        "resource": map[string]any{"cliente_id": pedido.ClienteID},
        "user":     user,
    }))
if err != nil || !result.Allowed() {
    http.Error(w, "forbidden", 403)
    return
}

Vantagens: regras em texto, versionadas em git separado se útil; auditoria centralizada (OPA loga decisão); time de segurança consegue atualizar sem deploy de código. Custos: latência (mesmo embutido, é uma camada); curva de aprendizado do Rego; mais um sistema para operar. Heurística: vale para sistemas com 50+ regras complexas; é overkill para sistema com 5 regras simples.

armadilha em produção

IDOR (Insecure Direct Object Reference). O endpoint autenticado GET /pedidos/{id} esquece a verificação de autorização baseada em recurso, e qualquer usuário autenticado consegue ler pedido de qualquer outro passando o ID. É uma das vulnerabilidades top do OWASP (categoria "Broken Access Control" no top 10 desde 2017, sendo #1 em 2021). A defesa estrutural é exatamente o que este conceito articula: todo endpoint que opera sobre recurso identificável precisa de policy de authorization que considere o usuário e o recurso. Atributo declarativo de "tem que estar autenticado" não basta — vai permitir o IDOR. Time maduro tem checklist de PR review que inclui "esse endpoint tem authorization baseada em recurso?".

Onde a separação fica clara

Em código maduro, fica visível na composição: middleware faz AuthN sem saber de operações específicas; filter/dependency faz AuthZ baseada na operação e no recurso; e handler de domínio nem menciona segurança. A leitura do handler conta o que ele faz; a leitura do filter conta as regras de quem pode fazê-lo.

Quando a separação não é clara, ficam pistas no código: if user.role != ... espalhado em handlers; decisões de autorização repetidas em múltiplos lugares com lógica ligeiramente diferente; middleware de autorização que conhece IDs de pedidos (sintoma claro de mistura). Refatorar para a separação leva tempo, mas o benefício composto é alto: menos bug, segurança auditável, onboarding mais rápido.

Anti-padrões frequentes

Authorization no domínio. Pedido.AdicionarItem(item, user) com if !user.IsAdmin: throw Forbidden. Domínio vira contaminado com plumbing. Solução: verificação fica antes do domínio, idealmente em filter, que decide se a operação prossegue.

JWT como armazenamento de permissões finas. Token com permissions: ["pedido:read:123", "pedido:write:123", ...] para cada recurso vira gigante e impossível de invalidar. Permissões finas devem ser consultadas dinamicamente; JWT carrega papéis e claims grossas.

"Anyone authenticated" como AuthZ. Endpoint protegido por [Authorize] sem policy específica autoriza qualquer usuário logado. Ok para alguns endpoints (perfil próprio, listagens públicas autenticadas), mas raramente apropriado para operações sobre recurso. Sempre pergunte: "qualquer usuário autenticado pode fazer isso?" Se a resposta é "não", policy específica é obrigatória.

Fail open em vez de fail closed. Se o serviço de autorização (engine externo) está fora, o sistema permite ou bloqueia? Default arquitetural correto: bloqueia (fail closed). Permite só se a equipe articulou explicitamente o trade-off (degradação de UX vs risco de segurança).

Não logar decisões de authorization. Sem log de "user X tentou Y, resultado Z", investigação de incidente vira adivinhação. Logue cada decisão (especialmente negativa) em formato estruturado, com correlation_id. Volume é maior que logging normal — sampling é razoável em decisões positivas; negativas devem ir 100%.

heurística do sênior

Antes de adicionar endpoint novo, articule duas frases: "esse endpoint requer que o usuário esteja autenticado?" e "que regra decide se um usuário autenticado pode chamar esse endpoint específico, eventualmente sobre esse recurso específico?". Se a segunda frase contém "qualquer autenticado", confirme em revisão que isso é intencional. Se contém "depende do recurso", policy de authorization baseada em recurso é obrigatória — atributo declarativo sozinho não basta. Esse exercício, feito antes de cada PR que adiciona endpoint, captura a maior parte dos bugs de autorização antes da review.

Por que importa para a sua carreira

Auth é tópico onde sêniores se distinguem de plenos quase automaticamente. Em entrevista de design, "como você organizaria authorization em uma API com 50 endpoints e 10 papéis?" é convite direto para articular a separação AuthN/AuthZ, escolher modelo (RBAC vs ABAC vs ReBAC com justificativa), e enunciar onde as regras vivem. Em revisão de código, perceber endpoint vulnerável a IDOR e propor a correção é serviço de senior. Em pos-mortem de incidente de segurança, articular "tinha verificação de recurso? Como o framework expressa essa verificação? Estamos consistentes?" diferencia quem participa da operação de quem só observa. E em sistemas regulados (financeiro, saúde), a articulação de AuthZ frequentemente vira tema de auditoria — saber discutir em vocabulário maduro economiza meses de retrabalho.

Como praticar

  1. Authorization baseada em recurso nas três linguagens. Implemente em ASP.NET Core (resource-based authorization), FastAPI (Depends factory), e Go (middleware + handler) o mesmo cenário do lang-compare. Acrescente teste de segurança que tenta acessar pedido de outro usuário via IDOR — verifique que a resposta é 403, não 200. Esse teste é candidato direto a checklist de release.
  2. OPA com policy não-trivial. Suba OPA localmente. Escreva em Rego uma policy que permite leitura de pedido se: (a) usuário é dono, OU (b) usuário tem papel customer-support, OU (c) usuário é admin do tenant correspondente. Integre com uma das três linguagens. Verifique que mudar a policy não exige re-deploy do serviço — só recarregar OPA. Esse exercício faz você sentir o ganho operacional de policy externa.
  3. Auditoria de IDOR em projeto existente. Pegue um projeto seu (ou aberto) e liste todos os endpoints autenticados que recebem ID de recurso no path. Para cada um, verifique se há authorization baseada em recurso. Identifique pelo menos um endpoint vulnerável a IDOR e proponha em PR a correção — idealmente promovendo o padrão para todo o projeto via filter/dependency reusável. Esse é o tipo de trabalho que ganha respeito do time de segurança.

Referências para aprofundar

  1. paper Role-Based Access Control Models — David Ferraiolo, Richard Kuhn (NIST, 1992; ANSI/INCITS 359-2004). Paper original que formalizou RBAC. Curto, didático. Disponível em csrc.nist.gov.
  2. paper Zanzibar: Google's Consistent, Global Authorization System — Lea Pugh et al., Google (USENIX, 2019). research.google/pubs — O paper fundador do ReBAC moderno. Define o modelo de relação que SpiceDB e OpenFGA implementam fora do Google.
  3. paper Guide to Attribute Based Access Control (ABAC) — NIST SP 800-162 (Vincent Hu et al., 2014). A referência canônica de ABAC. NIST formaliza vocabulário e arquitetura do modelo.
  4. livro OAuth 2 in Action — Justin Richer, Antonio Sanso (Manning, 2017). Apesar da idade, ainda é o melhor tratamento didático de OAuth 2. Útil para entender o que vai além da equação simplista de "JWT == auth".
  5. livro API Security in Action — Neil Madden (Manning, 2020). Cobertura abrangente de auth/authz em APIs modernas. Madden é researcher de segurança da ForgeRock; o livro é maduro e prático.
  6. livro Building Microservices (2ª ed.) — Sam Newman (O'Reilly, 2021). Cap. 11 (Security) inclui authorization em arquitetura distribuída — service-to-service auth, mTLS, propagação de identidade.
  7. docs OWASP Top 10 — Broken Access Control. owasp.org/www-project-top-ten — A categoria foi #1 em 2021 e é referência obrigatória. Inclui exemplos práticos de IDOR e como prevenir.
  8. docs ASP.NET Core Authorization. learn.microsoft.com/en-us/aspnet/core/security/authorization — Documentação exaustiva. As páginas de "Resource-based authorization" e "Policy-based authorization" são essenciais.
  9. docs Open Policy Agent (OPA). openpolicyagent.org — Documentação oficial do CNCF project. Tutorial de Rego é didático; cookbook de "Authorization patterns" mostra padrões reais.
  10. docs OpenFGA e SpiceDB. openfga.dev e authzed.com — As duas implementações principais de Zanzibar para aplicações fora do Google. Documentação clara, exemplos de modelagem.
  11. artigo Authorization Academy — Oso Inc. (oso.dev). osohq.com/academy — Série de tutoriais didáticos sobre RBAC, ABAC, ReBAC com exemplos comparados. Excelente material introdutório.
  12. vídeo Authorization Best Practices — Sam Scott (oso.dev co-founder), CarolinaCon e outros eventos, 2022+. YouTube. Scott articula trade-offs de modelos com exemplos de produção. Vale procurar pelas mais recentes.