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:readorders:updateusers:readusers: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.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
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:refundusers: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ão | Admin | Suporte | Usuário |
|---|---|---|---|
orders:create | Sim | Não | Sim (próprio) |
orders:read | Sim (todos) | Sim (todos) | Sim (próprio) |
orders:update | Sim (todos) | Sim (limitado) | Sim (próprio, limitado) |
orders:cancel | Sim (todos) | Sim (limitado) | Sim (próprio, se permitido) |
orders:refund | Sim | Não | Não |
users:read | Sim | Sim (limitado) | Não |
users:invite | Sim | Não | Não |
users:update | Sim | Não | Sim (próprio perfil) |
users:deactivate | Sim | Não | Não |
users:role.assign | Sim | Não | Nã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:
organizationsusersmemberships:(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
rolesepermissionsdaquele 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
orgIdno 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 (
403para falta de permissão,404para 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);
orgIdpode 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 AdminGET /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: requerorders:create; no handler, associauserIddo contexto ao pedido.GET /orders/:id: requerorders:read; no handler, permite se Admin/Suporte ou seorder.userId == ctx.userId.POST /orders/:id/refund: requerorders:refund; no handler, valida estado do pedido (ex.: pago) e tenant.GET /users: requerusers:read; no handler, para Suporte retorna campos limitados (projeção) e sempre filtra pororgId.POST /users/:id/role: requerusers:role.assign; no handler, impede atribuiradminse sua política exigir dupla aprovação (regra adicional), e registra auditoria.