Do requisito do produto ao modelo de autorização
Modelar controle de acesso para APIs significa transformar regras do produto (quem pode fazer o quê, em quais condições) em um conjunto consistente de recursos, ações, contexto e políticas aplicáveis no código. Um bom modelo evita “ifs” espalhados, reduz ambiguidades e facilita auditoria e evolução.
Elementos do modelo
- Recurso: o “objeto” protegido (ex.:
orders,order_items,invoices). Em APIs REST, costuma mapear para rotas como/orderse/orders/:id. - Ação: o tipo de operação (ex.:
read,create,update,delete). Você pode acrescentar ações específicas quando necessário (ex.:cancel,approve,refund). - Contexto: informações que influenciam a decisão (ex.: usuário é dono do pedido, pertence ao mesmo tenant, status do pedido, escopo do token, horário, IP, feature flag).
- Política: regra que decide se uma ação é permitida para um recurso, dado um contexto. Ex.: “um cliente pode ler apenas pedidos do próprio usuário no mesmo tenant”.
Passo a passo: traduzindo requisitos em recursos, ações e contexto
1) Liste casos de uso e atores
Comece com frases do produto e converta para linguagem de autorização. Exemplo (sistema de pedidos):
- Cliente cria pedido
- Cliente vê seus pedidos
- Atendente vê pedidos do tenant
- Gerente aprova/cancela pedidos
- Admin gerencia tudo
Evite começar pelo framework. Comece pelo que precisa ser protegido.
2) Identifique recursos (e sub-recursos)
Transforme os “substantivos” em recursos:
orders(pedido)order_items(itens do pedido) — pode ser sub-recurso:/orders/:id/itemscustomers(opcional, se houver endpoints administrativos)
Dica prática: se um endpoint retorna ou altera uma entidade, essa entidade é um bom candidato a recurso.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
3) Defina ações padrão e ações de domínio
Para APIs, um conjunto base costuma ser:
read(GET)create(POST)update(PUT/PATCH)delete(DELETE)
Quando o produto fala em “aprovar”, “cancelar”, “reabrir”, isso geralmente não é um update genérico: é uma ação de domínio que merece política própria, porque costuma ter restrições de status e papel.
4) Levante o contexto necessário para decidir
Liste os atributos que a política precisa consultar. Em pedidos, é comum:
- Identidade:
userId - Tenant/organização:
tenantId - Papel:
role(ex.: customer, agent, manager, admin) - Propriedade:
order.customerId(dono) - Status:
order.status(draft, placed, paid, shipped, canceled)
Se a política depende de dados do recurso (ex.: order.status), você precisará carregar o recurso (ou ao menos seus metadados) antes de autorizar certas ações.
5) Escreva políticas como regras legíveis
Escreva regras em linguagem clara antes do código. Exemplo:
- orders:create: permitido para customer no próprio tenant.
- orders:read: customer pode ler apenas pedidos onde
order.customerId == userIdeorder.tenantId == tenantId; agent/manager podem ler pedidos do tenant; admin pode ler todos. - orders:cancel: customer pode cancelar se for dono e status estiver em
placed; manager pode cancelar qualquer pedido do tenant se status não forshipped.
Esse texto vira a “fonte de verdade” para implementação e testes.
Matriz recurso-ação: desenhando e usando
A matriz recurso-ação ajuda a visualizar cobertura e lacunas. Ela não substitui contexto, mas organiza o que existe.
Exemplo de matriz (papéis x ações em orders)
| Recurso | Ação | Customer | Agent | Manager | Admin |
|---|---|---|---|---|---|
| orders | create | Sim (próprio tenant) | Não | Não | Sim |
| orders | read | Sim (somente próprios) | Sim (tenant) | Sim (tenant) | Sim (todos) |
| orders | update | Limitado (ex.: draft) | Limitado | Sim (tenant) | Sim |
| orders | delete | Não | Não | Limitado | Sim |
| orders | cancel | Sim (status=placed e dono) | Não | Sim (status!=shipped) | Sim |
| orders | approve | Não | Não | Sim | Sim |
Como usar na prática:
- Para cada célula “Sim/Limitado”, escreva a política com contexto (dono, tenant, status).
- Para cada endpoint, associe um recurso e uma ação. Evite endpoints “genéricos” que fazem várias ações sem clareza.
- Revise se há ações sem política definida (lacunas) ou políticas sem endpoint (excesso).
Mapeando políticas para middlewares/guards
Uma forma robusta é separar em camadas:
- Autenticação: popula
req.user(ou contexto equivalente). - Resolução do recurso (quando necessário): carrega
orderdo banco para obtertenantId,customerId,status. - Autorização: avalia a política
can(user, action, resource)com o contexto.
Estrutura de decisão: policy engine simples
Um padrão comum é centralizar regras em uma função/serviço de políticas:
// Tipos ilustrativos (pseudocódigo/TypeScript-like)
type Action = 'read' | 'create' | 'update' | 'delete' | 'cancel' | 'approve'
type Resource = 'orders'
type User = { id: string; tenantId: string; role: 'customer'|'agent'|'manager'|'admin' }
type Order = { id: string; tenantId: string; customerId: string; status: 'draft'|'placed'|'paid'|'shipped'|'canceled' }
type Context = { order?: Order }
function can(user: User, action: Action, resource: Resource, ctx: Context): boolean {
if (user.role === 'admin') return true
if (resource === 'orders') {
if (action === 'create') {
return user.role === 'customer'
}
if (action === 'read') {
const order = ctx.order
if (!order) return false
if (order.tenantId !== user.tenantId) return false
if (user.role === 'agent' || user.role === 'manager') return true
if (user.role === 'customer') return order.customerId === user.id
}
if (action === 'cancel') {
const order = ctx.order
if (!order) return false
if (order.tenantId !== user.tenantId) return false
if (user.role === 'customer') return order.customerId === user.id && order.status === 'placed'
if (user.role === 'manager') return order.status !== 'shipped'
}
}
return false
}Observações importantes:
- Para ações que dependem do recurso (
read,cancel), a policy precisa dectx.order(ou metadados equivalentes). - Para evitar duplicação, mantenha as regras em um único lugar e chame-as a partir dos guards/middlewares.
Guard/middleware por endpoint
Mapeie cada rota para um par (recurso, ação). Exemplo:
// Exemplo de mapeamento (pseudocódigo)
// GET /orders/:id
route('GET', '/orders/:id',
requireAuth(),
loadOrderIntoContext(),
authorize({ resource: 'orders', action: 'read' }),
handlerGetOrder
)
// POST /orders
route('POST', '/orders',
requireAuth(),
authorize({ resource: 'orders', action: 'create' }),
handlerCreateOrder
)
// POST /orders/:id/cancel
route('POST', '/orders/:id/cancel',
requireAuth(),
loadOrderIntoContext(),
authorize({ resource: 'orders', action: 'cancel' }),
handlerCancelOrder
)O middleware authorize deve:
- Receber
resourceeactioncomo metadados da rota. - Usar
req.userereq.context(ou equivalente) para chamarcan(...). - Retornar erro padronizado sem vazar informação.
Quando autorizar: antes ou depois de carregar o recurso?
- Antes: quando a política depende apenas do usuário (ex.:
orders:createpara customer). Evita query desnecessária. - Depois: quando depende de atributos do recurso (dono, tenant, status). Nesse caso, carregue o mínimo necessário (ex.:
tenantId,customerId,status), não o objeto inteiro se não precisar.
Padrões de resposta: 401 vs 403 (e 404 em cenários específicos)
401 Unauthorized
Use quando o cliente não está autenticado ou a credencial é inválida/expirada. Características:
- Não há identidade confiável para avaliar políticas.
- Resposta típica:
401com um corpo de erro genérico.
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{ "error": { "code": "AUTH_REQUIRED", "message": "Authentication required." } }403 Forbidden
Use quando o cliente está autenticado, mas não tem permissão para a ação no recurso (dadas as políticas).
HTTP/1.1 403 Forbidden
Content-Type: application/json
{ "error": { "code": "FORBIDDEN", "message": "You do not have permission to perform this action." } }Evitar vazamento de informação: 403 vs 404
Em alguns domínios, revelar que um recurso existe pode ser sensível (ex.: IDs previsíveis). Uma estratégia é responder 404 Not Found quando o usuário não tem permissão para saber se o recurso existe. Exemplo: GET /orders/:id para um customer tentando acessar pedido de outro usuário.
Critério prático:
- Se a existência do recurso é informação sensível, considere 404 para “não encontrado ou não autorizado”.
- Se o cliente precisa distinguir “existe mas não pode” (ex.: painel interno), use 403 e garanta que IDs não sejam enumeráveis e que haja rate limiting.
Mensagens de erro: critérios para não vazar informação
- Não diga qual regra falhou: evite “pedido pertence a outro usuário” ou “status shipped não permite cancelamento”. Prefira mensagens genéricas.
- Use códigos de erro estáveis:
FORBIDDEN,AUTH_REQUIRED,INVALID_TOKEN. Isso ajuda o front-end sem expor detalhes. - Log detalhado apenas no servidor: inclua
userId,action,resource,resourceId, motivo interno, correlação de request. - Não exponha papéis/claims do usuário na resposta de erro.
- Padronize o formato para facilitar tratamento e observabilidade.
// Exemplo de payload de erro padronizado
{
"error": {
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action.",
"requestId": "a1b2c3d4"
}
}Exercício proposto: políticas para um sistema de pedidos
Cenário: API de pedidos multi-tenant. Recursos: orders e order_items. Status do pedido: draft, placed, paid, shipped, canceled. Papéis: customer, agent, manager, admin.
Tarefas
- 1) Desenhe a matriz recurso-ação para
orderseorder_itemscom ações:read,create,update,delete,cancel,approve. - 2) Defina o contexto mínimo necessário para cada ação (ex.: para
cancel, precisa destatusecustomerId). - 3) Escreva políticas em linguagem natural para pelo menos 10 combinações (recurso, ação, papel), incluindo restrições de tenant e status.
- 4) Mapeie endpoints para (recurso, ação). Ex.:
POST /orders/:id/cancel=> (orders,cancel). - 5) Decida o padrão de resposta para tentativas indevidas em
GET /orders/:id: quando usar403e quando usar404, justificando com base em vazamento de informação.
Critérios de validação
- As políticas cobrem propriedade (dono), tenant e status onde aplicável.
- Não há endpoints “sem ação” (toda rota tem um par recurso-ação).
- Mensagens de erro são genéricas e os detalhes ficam apenas em logs.