O RBAC no Node.js organiza autorização por funções, como administrador, gerente, editor e leitor. Cada função recebe permissões, e usuários recebem uma ou mais funções dentro de um contexto, como uma organização ou projeto.
Esse modelo é fácil de explicar e auditar, mas pode crescer de forma descontrolada quando cada exceção vira uma nova função. A implementação correta separa autenticação de autorização, verifica permissões no backend, considera o tenant e registra mudanças de acesso.
Neste guia, você aprenderá a modelar roles e permissions, implementar middleware, trabalhar com multi-tenancy, cachear decisões, revogar acesso, testar rotas e evitar escalada de privilégios.
O que é RBAC?
RBAC significa Role-Based Access Control. A referência RBAC do NIST apresenta o modelo. A OWASP Authorization Cheat Sheet reúne recomendações práticas para autorização.
Para autenticação de tokens, consulte JWT Seguro no Node.js. Para isolamento por organização, veja Multi-Tenancy no Node.js.
Autenticação versus autorização
- Autenticação: confirma quem é o usuário.
- Autorização: decide o que ele pode fazer.
Um token válido não significa que o usuário pode acessar qualquer recurso.
Role
Uma role representa um conjunto de responsabilidades:
admin
manager
editor
viewerEvite nomes vagos como special ou power_user_2.
Permission
Permissões devem representar ações específicas:
project.read
project.create
project.update
project.delete
member.invite
billing.manageModelo relacional
CREATE TABLE roles (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
name TEXT NOT NULL,
UNIQUE (tenant_id, name)
);
CREATE TABLE permissions (
id UUID PRIMARY KEY,
code TEXT NOT NULL UNIQUE
);
CREATE TABLE role_permissions (
role_id UUID NOT NULL REFERENCES roles(id),
permission_id UUID NOT NULL REFERENCES permissions(id),
PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE user_roles (
tenant_id UUID NOT NULL,
user_id UUID NOT NULL,
role_id UUID NOT NULL REFERENCES roles(id),
PRIMARY KEY (tenant_id, user_id, role_id)
);Roles globais ou por tenant?
Roles de sistema podem ser globais, enquanto roles customizadas pertencem ao tenant. Mantenha a distinção explícita.
Permission check
async function hasPermission({
tenantId,
userId,
permission
}) {
const result = await pool.query(`
SELECT 1
FROM user_roles ur
JOIN role_permissions rp
ON rp.role_id = ur.role_id
JOIN permissions p
ON p.id = rp.permission_id
WHERE ur.tenant_id = $1
AND ur.user_id = $2
AND p.code = $3
LIMIT 1
`, [tenantId, userId, permission]);
return result.rowCount === 1;
}Middleware
function requirePermission(permission) {
return async (req, res, next) => {
const allowed = await hasPermission({
tenantId: req.auth.tenantId,
userId: req.auth.userId,
permission
});
if (!allowed) {
return res.status(403).json({
code: 'FORBIDDEN',
message: 'Acesso negado'
});
}
next();
};
}Uso na rota
app.delete(
'/api/projects/:id',
authenticate,
requirePermission('project.delete'),
deleteProject
);Autorização por recurso
Uma permissão geral pode não ser suficiente. O usuário pode editar projetos apenas de uma equipe:
const project = await repository.findById({
tenantId,
projectId
});
if (!project || project.teamId !== membership.teamId) {
throw new ForbiddenError();
}Não confie no frontend
Ocultar um botão melhora experiência, mas a API precisa repetir a verificação.
Permissões no token
Incluir roles ou permissions no JWT reduz consultas, mas cria atraso na revogação. Um token emitido antes da mudança continua válido até expirar.
Tokens curtos
Use access tokens de curta duração e refresh controlado. Para mudanças críticas, mantenha versão de autorização ou lista de revogação.
Permission version
{
"sub": "user-1",
"tenant": "tenant-1",
"authz_version": 12
}A API compara com a versão atual do usuário ou tenant e rejeita tokens antigos quando necessário.
Cache de permissões
authz:${tenantId}:${userId}:${version}Inclua tenant e versão. Um cache sem contexto pode conceder acesso de outra organização.
TTL
TTL curto reduz atraso de revogação. Eventos de alteração podem invalidar imediatamente.
Default deny
Se não houver regra explícita, negue. Não use “qualquer usuário autenticado” como fallback para operações sensíveis.
Role hierarchy
Uma hierarquia pode fazer admin herdar manager e editor:
admin → manager → editor → viewerHerança simplifica, mas pode conceder permissões inesperadas. Expanda e audite o conjunto efetivo.
Separation of duties
Algumas operações exigem funções separadas: quem cria pagamento não aprova o próprio pagamento.
Role explosion
Criar roles para cada combinação leva a dezenas ou centenas de funções. Quando regras dependem de atributos e contexto, ABAC pode complementar RBAC.
Roles customizadas
Permita que um tenant crie roles selecionando permissions permitidas. Um administrador local não deve conceder privilégios reservados ao sistema.
Permissões reservadas
system.impersonate
system.manage-tenants
billing.platform-refundEssas permissões não aparecem no editor do cliente.
Convites
Um convite deve informar tenant, role permitida, expiração e destinatário. Ao aceitar, valide novamente quem emitiu e se ainda possui permissão.
Remoção de membro
Revogue sessões, invalide caches e interrompa jobs futuros. A remoção no banco não encerra automaticamente tokens longos.
Último administrador
Evite remover ou rebaixar o último administrador do tenant sem fluxo de transferência.
Escalada de privilégio
Um usuário com member.update não deve conseguir atribuir uma role superior à própria capacidade.
if (!actorCanGrant(targetRole)) {
throw new ForbiddenError();
}Mass assignment
Não aceite role, permissions ou isAdmin em um update genérico de perfil.
Multi-tenancy
Todas as associações precisam incluir tenant. O mesmo usuário pode ser admin em uma organização e viewer em outra.
Row-Level Security
RLS pode limitar linhas por tenant, enquanto RBAC decide ações. Consulte Row-Level Security no Node.js.
API Gateway
O gateway pode validar autenticação e escopos gerais, mas o serviço de domínio deve autorizar o recurso.
Service-to-service
Serviços também possuem identidades e permissions. Não reutilize uma role humana para uma aplicação.
Jobs e filas
Uma mensagem deve carregar identidade ou uma autorização derivada no momento correto. Decida se o job usa as permissões do momento do agendamento ou da execução.
Operações assíncronas
Para exportações sensíveis, revalide acesso antes de gerar e antes de disponibilizar o arquivo.
Logs de auditoria
Registre:
- ator;
- tenant;
- role ou permission alterada;
- valor anterior e novo;
- alvo;
- horário;
- request ID;
- resultado.
Não registre segredos
Auditoria deve conter a decisão, não token ou senha.
Métricas
Monitore 403, falhas por permission, mudanças de role e tentativas de escalada. Não use user ID como label de alta cardinalidade.
Observabilidade
Um log de decisão pode incluir:
{
"authorization": "denied",
"permission": "project.delete",
"tenantId": "...",
"resourceType": "project"
}Resposta 401 ou 403?
- 401: autenticação ausente ou inválida.
- 403: identidade válida sem permissão.
Ocultar existência
Em recursos sensíveis, retornar 404 para um recurso fora do escopo pode reduzir enumeração. Mantenha comportamento consistente.
Testes unitários
Teste matriz de roles e permissions com casos permitidos e negados.
Teste de middleware
test('nega usuário sem project.delete', async () => {
const response = await request(app)
.delete('/api/projects/42')
.set('Authorization', viewerToken);
assert.equal(response.status, 403);
});Teste entre tenants
Um admin do tenant A não deve acessar recursos do tenant B.
Teste de revogação
Remova a role, invalide o cache e confirme que a próxima requisição falha.
Teste de escalada
Um gerente não pode conceder admin se não possui a permission específica.
Teste de token antigo
Altere authz_version e confirme rejeição ou redução do acesso.
Testes de propriedade
Gere combinações aleatórias de roles e confira que nenhuma permission não atribuída aparece no conjunto efetivo.
Revisão periódica
Liste usuários com roles privilegiadas, roles sem uso e permissions perigosas. Remova acesso que não é mais necessário.
Erros comuns
- Role fixa no código: mudanças ficam espalhadas.
- Confiar no frontend: API fica exposta.
- Permissões em token longo: revogação demora.
- Cache sem tenant: acesso cruza organizações.
- Update genérico de role: ocorre escalada.
- Role explosion: modelo fica impossível de manter.
- Sem auditoria: mudanças privilegiadas não são rastreadas.
Boas práticas
- Modele permissions específicas.
- Use default deny.
- Autorize no backend.
- Inclua tenant.
- Cacheie com versão.
- Use tokens curtos.
- Proteja roles reservadas.
- Evite escalada de privilégio.
- Audite mudanças.
- Teste matriz de acesso.
Conclusão
O RBAC no Node.js transforma funções organizacionais em conjuntos de permissões verificáveis. Ele funciona bem quando responsabilidades são relativamente estáveis e compreensíveis.
A implementação segura exige mais que um campo role no usuário. Tenant, recurso, revogação, cache e auditoria precisam fazer parte da decisão. Com permissions explícitas, default deny e testes de escalada, RBAC oferece autorização previsível sem depender da interface ou de tokens indefinidamente válidos.



