DevGuideTK
← Trilha Backend

Autorização (RBAC e ABAC)

Modelos para decidir o que cada usuário pode fazer, por papel ou por atributos.

O problema: identificado não quer dizer autorizado

O tópico de autenticação termina quando o servidor sabe quem fez o pedido. A autorização começa aí: essa pessoa pode fazer esta ação com este recurso?

A pergunta tem três partes, e as três importam:

ParteExemplo
QuemAna, cliente; Carla, do suporte; Davi, do financeiro
AçãoLer, editar, cancelar, reembolsar
RecursoO pedido 981, que é da Ana

Muitas implementações respondem só às duas primeiras ("clientes podem ler pedidos") e esquecem a terceira ("este pedido é deste cliente?").

A falha mais comum: trocar o número na URL

A rota GET /pedidos/{id} confere que o usuário está logado e devolve o pedido. Parece correta. A Ana abre /pedidos/981, que é dela, e depois /pedidos/982, que não é. A rota devolve.

Essa falha tem nome: referência direta insegura a objeto (IDOR), ou, na lista OWASP de riscos de APIs, autorização quebrada no nível do objeto (BOLA). Ela ocupa o primeiro lugar da lista porque é fácil de cometer e não aparece em testes com um usuário só.

Medimos com uma API de 300 pedidos, de três clientes, e um usuário que percorreu os números de 1 a 300:

RotaPedidos de outros clientes obtidos
Sem conferir o dono, números sequenciais200 de 200
Sem conferir o dono, 300 UUIDs aleatórios0
Sem conferir o dono, o UUID de um link que vazou1 de 1
Conferindo o dono, números sequenciais0

Trocar o número sequencial por um UUID atrapalha a enumeração, mas não é autorização: quem tem o link de um pedido alheio, num e-mail encaminhado, num log ou num histórico do navegador, continua lendo o pedido. A única defesa é conferir, em cada pedido, se aquele usuário pode ver aquele recurso.

RBAC: permissões por papel

No controle de acesso por papel (role-based access control, RBAC), cada usuário tem um ou mais papéis, e cada papel tem um conjunto de permissões:

PapelPermissões
clientepedido:ler
suportepedido:ler
financeiropedido:ler, pedido:reembolsar

É simples de entender, de auditar ("quem pode reembolsar?") e de administrar. O código confere permissões, e não papéis: pode(usuario, "pedido:reembolsar"), e não usuario.papel == "financeiro". Assim, um papel novo, como "gerente", não obriga a mudar o código.

O limite do RBAC aparece logo na tabela acima: cliente e suporte têm a mesma permissão, pedido:ler, mas não deveriam ver os mesmos pedidos. O cliente vê os próprios; o suporte vê todos. O papel sozinho não responde a pergunta do recurso.

ABAC: regras sobre atributos

No controle de acesso por atributos (attribute-based access control, ABAC), a regra olha atributos do usuário, do recurso e do contexto:

  • "O cliente lê o pedido se pedido.cliente_id for igual a usuario.id."
  • "O financeiro reembolsa pedidos de até R$ 1.000; acima disso, só a gerência."
  • "O suporte edita em horário comercial, e só pedidos da região dele."

É mais expressivo, e por isso mais difícil de auditar: para saber quem pode reembolsar o pedido 981, é preciso avaliar a regra com os dados daquele pedido.

Na prática, os sistemas misturam os dois: o papel diz o alcance da permissão, e um atributo do recurso decide o caso. É o modelo usado na Implementação:

Papelpedido:lerpedido:reembolsar
clientesó os própriosnão
suportetodosnão
financeirotodostodos

ReBAC: permissões por relação

Há um terceiro modelo, o controle de acesso por relação (relationship-based access control, ReBAC). A permissão vem de um caminho de relações: "Ana pode ver o documento porque é membro da equipe dona da pasta onde ele está". É o modelo do Google Drive e do GitHub, e foi descrito pelo Google no artigo do Zanzibar. Ferramentas como OpenFGA e SpiceDB implementam essa ideia. Compensa quando o compartilhamento é a regra, e não a exceção.

Negar por padrão

Uma política segura começa negando tudo e permite só o que está escrito. No teste da política em OPA da Implementação, um usuário com o papel "estagiário", que não aparece na tabela, teve todos os pedidos negados, sem nenhuma regra específica para ele. O contrário, permitir tudo e negar casos, falha no primeiro papel ou na primeira rota que alguém esquecer.

O mesmo vale para as rotas: uma rota nova sem nenhuma conferência de autorização deveria falhar nos testes, e não ficar aberta.

404 ou 403

Quando a Ana pede um pedido que não é dela, a API responde 404, como se ele não existisse. Um 403 diria "esse pedido existe, mas você não pode vê-lo", o que permite descobrir quais números existem. O 403 fica para quem pode ver o recurso, mas não pode fazer a ação: o suporte vê o pedido, e recebe 403 ao tentar o reembolso.

Isolar empresas no banco

Em sistemas com várias empresas clientes na mesma base (multi-tenant), esquecer o filtro WHERE empresa_id = ... numa só consulta vaza os dados de todas as outras. O Postgres pode aplicar o filtro sozinho, com a segurança por linha (row-level security): uma política na tabela diz quais linhas cada conexão enxerga.

Testamos com 300 pedidos de três empresas e a política empresa_id = current_setting('app.empresa_id'). A aplicação define a empresa no início de cada pedido:

SituaçãoLinhas visíveis numa consulta sem WHERE
Usuário da aplicação, empresa 2 definida100, só as da empresa 2
Usuário da aplicação, nenhuma empresa definida0
Dono da tabela, empresa 2 definida300
Dono da tabela, com FORCE ROW LEVEL SECURITY100
Superusuário, mesmo com FORCE300

O dono da tabela e o superusuário ignoram a política, e é comum a aplicação se conectar justamente com o usuário que criou as tabelas. O teste achou mais duas armadilhas, com um pool de uma conexão:

  • SET vale para a conexão, não para o pedido. Depois de um pedido da empresa 1 com SET app.empresa_id = '1', o pedido seguinte, que não definiu empresa nenhuma, recebeu as 100 linhas da empresa 1: a conexão voltou ao pool com o valor. Com SET LOCAL (ou set_config(..., true)) dentro de uma transação, o valor some no fim dela, e o pedido seguinte recebeu 0 linhas.
  • Texto vazio, e não nulo. Numa conexão onde a variável já tinha sido usada, current_setting('app.empresa_id', true) passou a devolver texto vazio, e a conversão para inteiro falhou com invalid input syntax for type integer: "". A política precisa de nullif(current_setting(...), '').

Por que isso importa quando a IA escreve o código

Um assistente que escreve uma rota protegida costuma conferir a autenticação e parar aí:

  • GET /pedidos/{id} que confere o login, mas não o dono do pedido.
  • A conferência feita na listagem (WHERE cliente_id = ...) e esquecida na rota de detalhe, de edição ou de exclusão.
  • Papéis comparados direto no código (if user.role == "admin"), espalhados pelas rotas.
  • O identificador do usuário lido do corpo ou da URL (?cliente_id=7), em vez da sessão.
  • 403 para recursos de outros usuários, revelando quais existem.
  • Segurança por linha ligada, com a aplicação conectada como dona da tabela, ou com SET em vez de SET LOCAL.

O próximo nível compara os modelos, onde colocar a conferência e as ferramentas de política.

Prompts para aprofundar

Ao abrir no ChatGPT ou no Claude, o prompt é enviado ao serviço escolhido pela URL e também fica copiado na sua área de transferência.

  • Autorização em cinco minutos

    Explique em no máximo cinco minutos de leitura o que é autorização numa API, para alguém que já sabe fazer login, mas nunca pensou em quem pode ver o quê.
    
    Cubra só o essencial: a diferença entre autenticação e autorização, a falha de trocar o número na URL, permissões por papel e por atributos, e por que negar por padrão.
    
    Use o exemplo de uma loja com clientes, suporte e financeiro. No final, me faça três perguntas curtas para eu verificar se entendi, e corrija as minhas respostas.
  • RBAC, ABAC e ReBAC por dentro

    Quero entender os três modelos de autorização: por papel (RBAC), por atributos (ABAC) e por relação (ReBAC).
    
    Aja como um mentor e vá por etapas, esperando a minha resposta em cada uma:
    1. Como cada modelo representa a regra, com um exemplo de loja.
    2. Que pergunta cada um responde bem e qual responde mal.
    3. O que é a explosão de papéis e como os atributos resolvem.
    4. Por que sistemas reais misturam papel e atributo do recurso.
    5. Quando o ReBAC compensa.
    
    Em cada etapa, me dê uma regra de negócio e pergunte como eu a expressaria no modelo.
  • Encontrar falhas de autorização

    Quero aprender a encontrar falhas de autorização em APIs, como as da lista OWASP de riscos de APIs: autorização quebrada no nível do objeto e no nível da função.
    
    Me mostre, uma de cada vez, seis rotas com código curto, algumas corretas e outras com falhas: dono não conferido, conferência só na listagem, identificador do usuário vindo do corpo, papel comparado direto, 403 que revela recursos, rota administrativa sem conferência.
    
    Para cada uma, pergunte se há uma falha e como explorá-la, e só depois explique a resposta e a correção.
  • Isolamento entre empresas no banco

    Quero entender como isolar os dados de cada empresa cliente num sistema multi-tenant.
    
    Explique:
    1. O risco de depender de um filtro em cada consulta.
    2. Filtros automáticos no ORM e o que escapa deles.
    3. A segurança por linha do Postgres: políticas, ENABLE e FORCE ROW LEVEL SECURITY, e quem a ignora.
    4. Por que SET vaza entre pedidos num pool de conexões e SET LOCAL não.
    5. Um esquema ou um banco por empresa, e o custo de operação.
    
    Depois me proponha um teste para cada armadilha e pergunte o resultado esperado.