Autorização por papéis (RBAC) em APIs back-end

Capítulo 12

Tempo estimado de leitura: 12 minutos

+ Exercício

O que é RBAC e quando ele é a escolha certa

RBAC (Role-Based Access Control) é um modelo de autorização em que você atribui papéis (roles) a identidades (usuários, contas de serviço) e define o que cada papel pode fazer por meio de permissões. Na prática, RBAC funciona bem quando:

  • as regras de acesso são relativamente estáveis e alinhadas a funções organizacionais (ex.: Admin, Suporte, Usuário);
  • você quer reduzir a complexidade de regras por usuário (evitando permissões individuais em massa);
  • há necessidade de auditoria simples: “quem tem o papel X pode fazer Y”.

RBAC tende a ser insuficiente quando as regras dependem fortemente de atributos dinâmicos (ex.: “pode editar pedidos apenas se o valor < 1000 e o pedido estiver no estado X”), ou quando o acesso é altamente contextual. Nesses casos, RBAC costuma ser combinado com checagens por recurso (ownership) e políticas mais granulares.

Vocabulário prático: papel, permissão, escopo e herança

Papel (role)

Um agrupador de permissões. Ex.: admin, support, user.

Permissão (permission)

Uma capacidade atômica que pode ser checada no back-end. É recomendável modelar permissões como ação + recurso (e opcionalmente um escopo). Exemplos:

  • orders:read
  • orders:update
  • users:read
  • users:manage

Escopo (scope)

Uma restrição de “onde” a permissão vale. Em APIs multi-tenant, o escopo mais comum é a organização (tenant). Ex.: um usuário pode ter support na organização A e apenas user na organização B.

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

Herança de papéis (role hierarchy)

Herança significa que um papel “inclui” outro (ex.: admin inclui support inclui user). Pode reduzir repetição, mas também pode criar permissões implícitas difíceis de auditar.

Quando usar herança:

  • há uma hierarquia real e estável de responsabilidades;
  • as permissões são cumulativas e raramente há exceções;
  • você tem testes e documentação que deixam explícito o que cada papel herda.

Quando evitar herança:

  • papéis são “perfis” diferentes, não níveis (ex.: Financeiro vs. Suporte);
  • há muitas exceções (“Admin não pode X”, “Suporte pode Y mas não Z”);
  • você precisa de auditoria estrita e previsibilidade (permissões implícitas viram armadilhas).

Passo a passo: desenhando RBAC para uma API

1) Liste recursos e ações do domínio (sem pensar em UI)

Para o exemplo, considere dois recursos: pedidos e usuários. Ações típicas:

  • Pedidos: create, read, update, cancel, refund
  • Usuários: read, invite, update, deactivate, role.assign

Transforme isso em permissões consistentes (um padrão de nomenclatura ajuda muito):

  • orders:create, orders:read, orders:update, orders:cancel, orders:refund
  • users:read, users:invite, users:update, users:deactivate, users:role.assign

2) Defina papéis com base em responsabilidades reais

Exemplo solicitado:

  • Admin: administra usuários e pedidos, inclusive ações sensíveis.
  • Suporte: ajuda clientes e resolve problemas operacionais, com limites.
  • Usuário: opera seus próprios pedidos e vê seus dados.

3) Mapeie papéis → permissões (tabela de referência)

PermissãoAdminSuporteUsuário
orders:createSimNãoSim (próprio)
orders:readSim (todos)Sim (todos)Sim (próprio)
orders:updateSim (todos)Sim (limitado)Sim (próprio, limitado)
orders:cancelSim (todos)Sim (limitado)Sim (próprio, se permitido)
orders:refundSimNãoNão
users:readSimSim (limitado)Não
users:inviteSimNãoNão
users:updateSimNãoSim (próprio perfil)
users:deactivateSimNãoNão
users:role.assignSimNãoNão

Observe que a tabela mistura “Sim” com qualificadores como “próprio” e “limitado”. Isso é comum: RBAC define o pode tentar, e regras por recurso (ex.: ownership, estado do pedido) definem o pode de fato em cada caso.

4) Decida como representar permissões no código

Dois formatos comuns:

  • Lista de permissões efetivas no contexto da requisição (derivada dos papéis): simples de checar (hasPermission('orders:refund')), mas exige cuidado com cache e mudanças de papel.
  • Lista de papéis e um resolvedor no servidor que expande papéis → permissões: centraliza a lógica e facilita auditoria.

Um padrão prático é manter um catálogo central de permissões e um mapa de papéis:

// Exemplo conceitual (TypeScript/Node) - catálogo centralizado
const ROLE_PERMISSIONS = {
  admin: [
    'orders:create','orders:read','orders:update','orders:cancel','orders:refund',
    'users:read','users:invite','users:update','users:deactivate','users:role.assign'
  ],
  support: [
    'orders:read','orders:update','orders:cancel',
    'users:read'
  ],
  user: [
    'orders:create','orders:read','orders:update','orders:cancel',
    'users:update'
  ]
} as const;

type Permission = typeof ROLE_PERMISSIONS[keyof typeof ROLE_PERMISSIONS][number];

type AuthContext = {
  userId: string;
  orgId: string;
  roles: string[]; // papéis no tenant atual
  permissions: Set<Permission>;
};

function buildPermissions(roles: string[]): Set<Permission> {
  const perms = new Set<Permission>();
  for (const role of roles) {
    const list = (ROLE_PERMISSIONS as any)[role] ?? [];
    for (const p of list) perms.add(p);
  }
  return perms;
}

function requirePermission(ctx: AuthContext, perm: Permission) {
  if (!ctx.permissions.has(perm)) {
    const err = new Error('FORBIDDEN');
    (err as any).status = 403;
    throw err;
  }
}

5) Mapeie permissões para endpoints (rota → permissão)

Crie um mapeamento explícito entre rotas e permissões. Isso evita “autorização espalhada” e facilita documentação e testes.

// Exemplo conceitual de mapeamento
const ROUTE_PERMISSIONS = [
  { method: 'POST', path: '/orders', perm: 'orders:create' },
  { method: 'GET', path: '/orders/:id', perm: 'orders:read' },
  { method: 'PATCH', path: '/orders/:id', perm: 'orders:update' },
  { method: 'POST', path: '/orders/:id/cancel', perm: 'orders:cancel' },
  { method: 'POST', path: '/orders/:id/refund', perm: 'orders:refund' },

  { method: 'GET', path: '/users', perm: 'users:read' },
  { method: 'POST', path: '/users/invite', perm: 'users:invite' },
  { method: 'PATCH', path: '/users/:id', perm: 'users:update' },
  { method: 'POST', path: '/users/:id/deactivate', perm: 'users:deactivate' },
  { method: 'POST', path: '/users/:id/role', perm: 'users:role.assign' }
] as const;

Na implementação real, você aplicaria um middleware/guard que, ao registrar a rota, associa a permissão exigida e executa requirePermission antes do handler.

6) Autorize ações no nível do recurso (ownership e limites operacionais)

RBAC sozinho não resolve regras como “usuário só pode ver o próprio pedido”. Uma prática comum é:

  • Primeiro: checar a permissão do endpoint (ex.: orders:read).
  • Depois: checar a regra do recurso (ex.: se o pedido pertence ao usuário, ou se o papel permite acesso global).
// Exemplo conceitual: leitura de pedido
async function getOrder(ctx: AuthContext, orderId: string) {
  requirePermission(ctx, 'orders:read');

  const order = await Orders.findById(orderId);
  if (!order) throw Object.assign(new Error('NOT_FOUND'), { status: 404 });

  // Multi-tenant: sempre validar tenant
  if (order.orgId !== ctx.orgId) throw Object.assign(new Error('NOT_FOUND'), { status: 404 });

  const isAdminOrSupport = ctx.roles.includes('admin') || ctx.roles.includes('support');
  const isOwner = order.userId === ctx.userId;

  if (!isAdminOrSupport && !isOwner) {
    throw Object.assign(new Error('FORBIDDEN'), { status: 403 });
  }

  return order;
}

Note o uso de NOT_FOUND quando o orgId não bate: isso reduz vazamento de informação entre tenants (evita confirmar que um recurso existe em outra organização).

RBAC em multi-tenant: papel por organização

Modelagem recomendada

Em multi-tenant, evite um único campo role no usuário. Em vez disso, modele associação por organização:

  • organizations
  • users
  • memberships: (user_id, org_id, role) ou (user_id, org_id, roles[])

Isso permite que o mesmo usuário tenha papéis diferentes em cada organização.

Passo a passo do fluxo por requisição

  • 1) Identificar a organização alvo (orgId) via subdomínio, header (X-Org-Id) ou path (/orgs/:orgId).
  • 2) Carregar a membership do usuário naquela organização.
  • 3) Construir o contexto de autorização com roles e permissions daquele tenant.
  • 4) Executar checagens de rota e de recurso sempre considerando ctx.orgId.
// Exemplo conceitual de resolução de tenant e papéis
async function buildAuthContext(req): Promise<AuthContext> {
  const userId = req.auth.userId;
  const orgId = req.headers['x-org-id'];
  if (!orgId) throw Object.assign(new Error('ORG_REQUIRED'), { status: 400 });

  const membership = await Memberships.findOne({ userId, orgId });
  if (!membership) throw Object.assign(new Error('FORBIDDEN'), { status: 403 });

  const roles = membership.roles ?? [membership.role];
  const permissions = buildPermissions(roles);

  return { userId, orgId, roles, permissions };
}

Cuidados específicos em multi-tenant

  • Não confie apenas em filtros no banco; valide orgId no acesso ao recurso.
  • Evite permissões globais sem necessidade. Se existir “super-admin”, trate como um caso separado e auditável.
  • Cache com cautela: se você cacheia permissões, invalide quando membership mudar (troca de papel, remoção da org).

Herança na prática: como implementar sem perder controle

Se você decidir usar herança, prefira herança explícita e resolvida em tempo de build/deploy (config) ou em um único módulo central, e não espalhada em vários lugares.

// Exemplo conceitual de herança
const ROLE_INHERITS = {
  admin: ['support'],
  support: ['user'],
  user: []
} as const;

function expandRoles(roles: string[]): string[] {
  const visited = new Set<string>();
  const stack = [...roles];
  while (stack.length) {
    const r = stack.pop()!;
    if (visited.has(r)) continue;
    visited.add(r);
    const parents = (ROLE_INHERITS as any)[r] ?? [];
    for (const p of parents) stack.push(p);
  }
  return [...visited];
}

function buildPermissionsWithInheritance(roles: string[]): Set<Permission> {
  return buildPermissions(expandRoles(roles));
}

Boas práticas:

  • mantenha herança rasa (2–3 níveis no máximo);
  • documente permissões efetivas por papel (incluindo herdadas);
  • tenha testes que falham se uma permissão “vazar” para um papel indevido.

Anti-patterns comuns em RBAC (e como evitar)

1) “Papéis demais” (role explosion)

Sintoma: cada pequena variação vira um novo papel (support_level_1, support_level_2, support_orders_only...). Isso dificulta auditoria e manutenção.

Como evitar:

  • use permissões atômicas e poucos papéis principais;
  • para variações raras, considere permissões adicionais por usuário/tenant (ex.: “feature flags” de permissão) em vez de criar um novo papel para tudo;
  • revise papéis periodicamente e remova os não usados.

2) Permissões acopladas à UI

Sintoma: permissões do tipo canSeeRefundButton ou menu:settings. Isso quebra quando a UI muda e não representa o que a API precisa proteger.

Como evitar:

  • modele permissões como ações de domínio (orders:refund), não como elementos de interface;
  • a UI pode usar as permissões de domínio para decidir o que exibir, mas a fonte de verdade é a API.

3) Autorização espalhada e inconsistente

Sintoma: cada endpoint faz checagens diferentes, com mensagens e códigos variados.

Como evitar:

  • centralize checagens em middlewares/guards e helpers (requirePermission);
  • padronize respostas (403 para falta de permissão, 404 para recurso fora do tenant, quando aplicável);
  • mantenha um catálogo único de permissões.

4) Confiar em dados do cliente para papel/tenant

Sintoma: o cliente envia role=admin ou orgId e o servidor aceita sem validar membership.

Como evitar:

  • papéis e memberships devem vir do servidor (banco/serviço de identidade);
  • orgId pode ser indicado pelo cliente, mas deve ser validado contra a membership do usuário.

Consistência com testes e documentação

Testes: matriz de acesso por papel

Crie uma suíte de testes que percorra uma matriz de endpoints × papéis, garantindo que:

  • o papel correto recebe 2xx;
  • papéis sem permissão recebem 403;
  • em multi-tenant, acesso cross-org não vaza (idealmente 404).
// Exemplo conceitual de teste de autorização (pseudo)
describe('RBAC Orders', () => {
  const cases = [
    { role: 'admin', method: 'POST', path: '/orders/123/refund', expected: 200 },
    { role: 'support', method: 'POST', path: '/orders/123/refund', expected: 403 },
    { role: 'user', method: 'POST', path: '/orders/123/refund', expected: 403 }
  ];

  for (const c of cases) {
    it(`${c.role} ${c.method} ${c.path} => ${c.expected}`, async () => {
      const token = await loginAsRoleInOrg(c.role, 'orgA');
      const res = await request(c.method, c.path, { token });
      expect(res.status).toBe(c.expected);
    });
  }
});

Documentação: declarar permissões por endpoint

Documente cada endpoint com a permissão exigida e observações de escopo (próprio vs. todos, limites do suporte). Um formato simples:

  • POST /orders/:id/refund — Permissão: orders:refund — Escopo: org — Observação: apenas Admin
  • GET /orders/:id — Permissão: orders:read — Escopo: org — Observação: Admin/Suporte (todos), Usuário (apenas próprios)

Se você usa OpenAPI, uma prática é adicionar uma extensão (ex.: x-permission) para manter isso próximo do contrato da API, e validar em testes que toda rota documentada tem permissão definida.

Exemplo completo: Admin, Suporte e Usuário em pedidos e usuários

Regras resumidas

  • Admin: pode gerenciar usuários (convidar, desativar, atribuir papéis) e executar reembolso.
  • Suporte: pode ler pedidos de qualquer usuário na org e atualizar/cancelar dentro de limites; pode ler usuários de forma limitada (ex.: nome, email, status), sem alterar papéis.
  • Usuário: pode criar e gerenciar seus próprios pedidos; pode atualizar seu próprio perfil; não vê lista de usuários.

Checagens típicas por endpoint

  • POST /orders: requer orders:create; no handler, associa userId do contexto ao pedido.
  • GET /orders/:id: requer orders:read; no handler, permite se Admin/Suporte ou se order.userId == ctx.userId.
  • POST /orders/:id/refund: requer orders:refund; no handler, valida estado do pedido (ex.: pago) e tenant.
  • GET /users: requer users:read; no handler, para Suporte retorna campos limitados (projeção) e sempre filtra por orgId.
  • POST /users/:id/role: requer users:role.assign; no handler, impede atribuir admin se sua política exigir dupla aprovação (regra adicional), e registra auditoria.

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

Em um cenário multi-tenant usando RBAC, qual abordagem descreve corretamente como autorizar o acesso a um pedido específico (GET /orders/:id) sem vazar informações entre organizações?

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

Você errou! Tente novamente.

Em multi-tenant, a autorização deve combinar checagem de permissão da rota com validação de tenant e regras por recurso (ownership). Retornar 404 quando o orgId não bate ajuda a evitar vazamento de informação entre organizações.

Próximo capitúlo

Autorização por atributos e regras (ABAC) 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
67%

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.