O ABAC no Node.js decide acesso com base em atributos do usuário, recurso, ação e ambiente. Em vez de depender apenas de uma função como editor ou administrador, a política pode considerar departamento, proprietário, classificação do dado, horário, região, estado do pedido e nível de autenticação.
ABAC oferece decisões mais precisas que RBAC, mas também aumenta complexidade. Políticas precisam ser legíveis, testáveis, auditáveis e avaliadas com dados confiáveis. Uma regra ambígua ou atributo vindo do cliente pode conceder acesso indevido.
Neste guia, você aprenderá a modelar atributos, criar políticas, implementar um motor simples, combinar RBAC e ABAC, cachear decisões, explicar negações, testar regras e evitar escalada de privilégios.
O que é ABAC?
ABAC significa Attribute-Based Access Control. A publicação NIST SP 800-162 sobre ABAC descreve o modelo. A OWASP Authorization Cheat Sheet reúne recomendações de autorização.
Para um modelo baseado em funções, consulte RBAC no Node.js. Para isolamento entre organizações, veja Multi-Tenancy no Node.js.
Quatro grupos de atributos
- Sujeito: usuário, serviço, função, departamento, plano.
- Recurso: proprietário, tenant, classificação, status.
- Ação: ler, editar, excluir, aprovar, exportar.
- Ambiente: horário, localização, dispositivo, risco, MFA.
Exemplo de política
Um usuário pode editar um documento quando:
- pertence ao mesmo tenant;
- é proprietário ou gerente do departamento;
- o documento não está bloqueado;
- a autenticação possui MFA para dados confidenciais.
Contexto de decisão
const context = {
subject: {
id: user.id,
tenantId: identity.tenantId,
departmentId: membership.departmentId,
roles: membership.roles,
mfaLevel: identity.mfaLevel
},
resource: {
type: 'document',
id: document.id,
tenantId: document.tenantId,
ownerId: document.ownerId,
departmentId: document.departmentId,
classification: document.classification,
status: document.status
},
action: 'document.update',
environment: {
requestTime: new Date(),
ipRisk: risk.ipLevel
}
};Atributos confiáveis
Tenant, usuário e MFA devem vir da autenticação validada. Propriedades do recurso devem vir do banco. Não aceite esses atributos diretamente do corpo.
Política como função
function canUpdateDocument(context) {
const { subject, resource } = context;
if (subject.tenantId !== resource.tenantId) {
return false;
}
if (resource.status === 'locked') {
return false;
}
const owner = subject.id === resource.ownerId;
const manager = subject.roles.includes('department_manager')
&& subject.departmentId === resource.departmentId;
if (!owner && !manager) {
return false;
}
if (
resource.classification === 'confidential'
&& subject.mfaLevel !== 'strong'
) {
return false;
}
return true;
}Retorno explicável
function deny(reason, policy) {
return {
allowed: false,
reason,
policy
};
}Explicações ajudam logs e suporte, mas o cliente não deve receber detalhes que revelem atributos sensíveis.
Default deny
Se nenhuma política permite explicitamente, negue. Uma exceção ou atributo ausente não deve liberar o acesso.
Combinação de políticas
Estratégias comuns:
- deny overrides;
- permit overrides;
- first applicable;
- all must permit.
Para segurança, deny overrides costuma ser mais previsível.
Motor de políticas simples
class PolicyEngine {
constructor(policies) {
this.policies = policies;
}
evaluate(context) {
const applicable = this.policies.filter(policy =>
policy.action === context.action
&& policy.resourceType === context.resource.type
);
if (applicable.length === 0) {
return deny('no_applicable_policy', 'default');
}
for (const policy of applicable) {
const result = policy.evaluate(context);
if (!result.allowed) return result;
}
return { allowed: true };
}
}Políticas em código
São fáceis de versionar e testar, mas um deploy é necessário para alterar regras.
Políticas como dados
JSON ou uma linguagem de políticas permite atualização dinâmica, porém exige schema, validação, versionamento, assinatura e sandbox.
Não use eval
Nunca execute expressões arbitrárias de uma política com eval() ou new Function(). Use um interpretador limitado e auditado.
Open Policy Agent
OPA e Rego podem centralizar políticas. A aplicação envia atributos e recebe a decisão. Avalie latência, cache, disponibilidade e governança.
Policy Decision Point
O componente que decide é chamado PDP. O middleware ou serviço que aplica a decisão é o Policy Enforcement Point.
Decisão local
Avaliar no processo reduz latência, mas exige distribuir políticas para todas as instâncias.
Decisão remota
Um serviço central simplifica governança, mas pode virar dependência crítica. Use timeout e fallback seguro.
Falha do PDP
Para operações sensíveis, falhe fechado. Para recursos públicos, uma política local mínima pode ser usada, desde que explicitamente definida.
ABAC e RBAC
Os modelos se complementam. RBAC concede uma capacidade geral, e ABAC restringe pelo contexto:
hasPermission('document.update')
&& document.tenantId === identity.tenantId
&& (
document.ownerId === identity.userId
|| identity.departmentId === document.departmentId
)Role explosion
ABAC evita criar roles como editor_do_departamento_financeiro_com_mfa. A role permanece editor e atributos refinam a decisão.
Multi-tenancy
O tenant deve ser a primeira condição de quase toda política. Uma negação cruzada precisa ocorrer antes de outras regras.
Row-Level Security
RLS protege linhas no banco, enquanto ABAC toma decisões de aplicação. Consulte Row-Level Security no Node.js.
Consultando o recurso
Não autorize apenas com o ID da URL. Carregue atributos mínimos do recurso usando tenant e ID.
TOCTOU
Entre verificar e alterar, o recurso pode mudar. Para condições críticas, valide novamente na mesma transação ou use update condicional.
Update condicional
UPDATE documents
SET content = $1,
version = version + 1
WHERE tenant_id = $2
AND id = $3
AND owner_id = $4
AND status != 'locked'
RETURNING id;Parte da política pode ser aplicada atomicamente no SQL.
Lock otimista
Inclua versão para impedir que a decisão seja baseada em estado antigo. Consulte Lock Otimista no Node.js.
Atributos ambientais
Horário, IP e dispositivo podem mudar. Use-os apenas quando realmente necessários e trate proxies confiáveis.
Geolocalização
IP não é prova precisa de localização. Não use sozinho para uma decisão de alto risco.
Nível de autenticação
Operações sensíveis podem exigir step-up authentication, como passkey ou TOTP recente.
Tempo desde MFA
const mfaRecent = Date.now() - identity.mfaAt.getTime()
< 10 * 60 * 1000;Use horário do servidor e sessão protegida.
Delegação
Um usuário pode delegar acesso temporário. Modele início, fim, escopo, revogação e auditoria.
Regras temporais
Uma permissão com expiração precisa ser verificada a cada decisão ou refletida em tokens curtos.
Cache de decisões
Uma chave pode incluir:
policy:${policyVersion}:${tenantId}:${subjectId}:${resourceId}:${resourceVersion}:${action}Sem versão de política e recurso, o cache pode conceder acesso antigo.
TTL curto
Decisões sensíveis devem ter TTL pequeno ou não ser cacheadas.
Invalidando cache
Altere uma versão de autorização quando membership, role, classificação ou política mudar.
Dados pessoais
Evite enviar atributos desnecessários a um PDP remoto. Minimize dados e proteja logs.
Logs de decisão
{
"decision": "deny",
"action": "document.update",
"resourceType": "document",
"policy": "confidential_requires_mfa",
"tenantId": "...",
"requestId": "..."
}Não registre conteúdo do documento, token ou segredo.
Auditoria
Alterações de política precisam registrar autor, versão anterior, versão nova, motivo e aprovação.
Métricas
Monitore decisões negadas, falhas do PDP, latência, políticas sem correspondência e aumento de step-up.
Cardinalidade
Use action, resource type e policy como labels controladas. Não use IDs individuais.
Testes em tabela
const cases = [
{
name: 'proprietário edita documento comum',
context: ownerContext,
expected: true
},
{
name: 'outro tenant é negado',
context: crossTenantContext,
expected: false
},
{
name: 'confidencial sem MFA é negado',
context: weakMfaContext,
expected: false
}
];Testes de propriedade
Gere atributos aleatórios e verifique invariantes, como “tenants diferentes nunca recebem permit”.
Teste de atributo ausente
Remova cada atributo obrigatório e confirme default deny, sem exceção que libere o acesso.
Teste de cache
Mude a versão do recurso ou política e confirme que a decisão anterior não é reutilizada.
Teste de concorrência
Altere o status do recurso entre leitura e escrita e confirme validação transacional.
Teste de falha do PDP
Simule timeout e verifique o fallback definido.
Revisão de políticas
Procure regras redundantes, atributos não usados, permissões amplas e exceções permanentes.
Erros comuns
- Atributo vindo do cliente: usuário muda a decisão.
- Permit por padrão: ausência de política libera acesso.
- Política sem versão: cache e auditoria ficam inconsistentes.
- eval em regras: código arbitrário é executado.
- Contexto enorme: dados sensíveis vazam.
- Decisão fora da transação: estado muda antes da escrita.
- Regras impossíveis de explicar: suporte e auditoria falham.
Boas práticas
- Use atributos confiáveis.
- Aplique default deny.
- Comece verificando tenant.
- Combine RBAC e ABAC.
- Versione políticas.
- Evite execução arbitrária.
- Defina timeout do PDP.
- Cacheie com versões.
- Registre decisões sem dados sensíveis.
- Teste invariantes e atributos ausentes.
Conclusão
O ABAC no Node.js permite decisões de autorização baseadas no contexto real, combinando atributos do usuário, recurso, ação e ambiente. Ele evita criar uma role para cada exceção e oferece controle mais fino.
A flexibilidade exige disciplina. Atributos precisam ser confiáveis, políticas devem negar por padrão e decisões críticas precisam considerar concorrência. Com versionamento, auditoria, testes e integração com RBAC e RLS, ABAC oferece autorização precisa sem transformar regras de acesso em código invisível e impossível de manter.



