Modelagem de controle de acesso para APIs: recursos, ações e políticas

Capítulo 2

Tempo estimado de leitura: 8 minutos

+ Exercício

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 /orders e /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/items
  • customers (opcional, se houver endpoints administrativos)

Dica prática: se um endpoint retorna ou altera uma entidade, essa entidade é um bom candidato a recurso.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

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 == userId e order.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 for shipped.

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)

RecursoAçãoCustomerAgentManagerAdmin
orderscreateSim (próprio tenant)NãoNãoSim
ordersreadSim (somente próprios)Sim (tenant)Sim (tenant)Sim (todos)
ordersupdateLimitado (ex.: draft)LimitadoSim (tenant)Sim
ordersdeleteNãoNãoLimitadoSim
orderscancelSim (status=placed e dono)NãoSim (status!=shipped)Sim
ordersapproveNãoNãoSimSim

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 order do banco para obter tenantId, 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 de ctx.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 resource e action como metadados da rota.
  • Usar req.user e req.context (ou equivalente) para chamar can(...).
  • 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:create para 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: 401 com 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 orders e order_items com ações: read, create, update, delete, cancel, approve.
  • 2) Defina o contexto mínimo necessário para cada ação (ex.: para cancel, precisa de status e customerId).
  • 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 usar 403 e quando usar 404, 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.

Agora responda o exercício sobre o conteúdo:

Em uma API de pedidos, quando é mais adequado carregar os dados do pedido antes de aplicar a autorização em um endpoint como GET /orders/:id?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Quando a decisão exige contexto do recurso (ex.: tenant, propriedade e status), é necessário carregar ao menos os metadados do pedido para avaliar a política corretamente.

Próximo capitúlo

Autenticação baseada em sessão e cookie no back-end

Arrow Right Icon
Capa do Ebook gratuito Autenticação e Autorização no Back-end: Sessões, JWT e Boas Práticas
11%

Autenticação e Autorização no Back-end: Sessões, JWT e Boas Práticas

Novo curso

18 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.