Implementar Sessões Seguras no Node.js permite manter o usuário autenticado entre requisições sem expor credenciais em cada chamada. O navegador recebe um identificador aleatório em cookie, enquanto o servidor mantém o estado da sessão em um armazenamento controlado.
Uma sessão insegura pode sofrer fixation, roubo por XSS, uso via CSRF, duração excessiva ou revogação incompleta. A proteção depende de IDs criptográficos, cookies HttpOnly e Secure, rotação após login, expiração, armazenamento compartilhado, revogação e auditoria.
Neste guia, você aprenderá a criar IDs, configurar cookies, usar Redis ou PostgreSQL, impedir fixation, proteger CSRF, renovar sessões, encerrar dispositivos, aplicar step-up e testar o fluxo.
O que é uma sessão?
Uma sessão associa um identificador apresentado pelo cliente a um registro no servidor. A OWASP Session Management Cheat Sheet reúne as principais recomendações. O RFC 6265 sobre cookies HTTP descreve o mecanismo usado pelo navegador.
Para sessões baseadas em tokens, consulte Refresh Tokens no Node.js. Para autenticação forte, veja Passkeys no Node.js e TOTP no Node.js.
ID de sessão
const sessionId = randomBytes(32)
.toString('base64url');O valor precisa ser imprevisível, longo e gerado por CSPRNG. Não inclua user ID, horário ou dados pessoais.
Armazenando hash
const sessionHash = createHash('sha256')
.update(sessionId)
.digest('hex');O banco pode armazenar apenas o hash. Se a tabela vazar, os IDs ativos não aparecem em texto claro.
Tabela de sessões
CREATE TABLE user_sessions (
id UUID PRIMARY KEY,
session_hash TEXT NOT NULL UNIQUE,
user_id UUID,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
authenticated_at TIMESTAMPTZ,
last_seen_at TIMESTAMPTZ NOT NULL,
idle_expires_at TIMESTAMPTZ NOT NULL,
absolute_expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
mfa_verified_at TIMESTAMPTZ,
auth_methods TEXT[],
user_agent TEXT,
ip_prefix TEXT
);Sessão anônima
Uma sessão pode existir antes do login para CSRF, carrinho ou challenge. Após autenticar, não transforme o mesmo ID em sessão privilegiada.
Session fixation
Fixation ocorre quando o atacante conhece ou define o ID antes do login e espera que ele ganhe privilégios depois.
Rotação após login
await revokeSession(preAuthSessionId);
const authenticatedSession = await createSession({
userId,
authenticatedAt: new Date()
});Emita um novo cookie e invalide o identificador anterior.
Rotação após step-up
Ao concluir MFA ou passkey para uma ação sensível, rotacione novamente ou atualize o registro de forma atômica.
Cookie de sessão
Set-Cookie: __Host-session=...;
Path=/;
HttpOnly;
Secure;
SameSite=LaxO prefixo __Host- exige Secure, Path=/ e ausência de Domain em navegadores compatíveis.
HttpOnly
Impede que JavaScript leia o cookie. Isso reduz roubo direto por XSS, mas um script malicioso ainda pode realizar ações na sessão.
Secure
O cookie só deve ser enviado por HTTPS. Redirecionar HTTP não substitui o atributo.
SameSite
- Strict: maior proteção, pode afetar links externos.
- Lax: opção comum para aplicações web.
- None: necessário em alguns fluxos cross-site e exige Secure.
Domain
Evite compartilhar cookies com todos os subdomínios. Um subdomínio comprometido pode afetar a sessão.
Path
Para uma sessão usada em toda a aplicação, use /. Não dependa de Path como barreira forte entre aplicações da mesma origem.
Max-Age e Expires
Cookies persistentes sobrevivem ao fechamento do navegador. Para sessões sensíveis, avalie cookie sem persistência e expiração no servidor.
Estado no servidor é a fonte
Mesmo que o cookie ainda exista, uma sessão revogada ou expirada deve ser rejeitada pelo backend.
Expiração por inatividade
Atualize o prazo quando houver atividade relevante:
idle_expires_at = now() + interval '30 minutes'Expiração absoluta
Uma sessão não deve durar para sempre com atividade contínua:
absolute_expires_at = created_at + interval '30 days'Atualização de last_seen
Gravar em toda requisição aumenta carga. Atualize apenas quando a última gravação ultrapassar uma janela, como cinco minutos.
Sliding expiration
A expiração por inatividade pode deslizar, mas nunca ultrapassar o limite absoluto.
Armazenamento em memória
Um Map local funciona apenas em desenvolvimento. Reinícios apagam sessões e várias réplicas não compartilham estado.
Redis
Redis oferece TTL e acesso rápido:
SET session:{hash} {json} EX 1800Configure autenticação, TLS, limites e política de persistência conforme o risco.
PostgreSQL
PostgreSQL facilita auditoria, transações e consultas de sessões por usuário. Limpeza e índices precisam ser planejados.
Qual armazenamento escolher?
- Redis: baixa latência e expiração nativa.
- PostgreSQL: consistência, consultas e menor número de componentes.
Ambos podem funcionar quando a implementação é segura.
Store de sessão do framework
Bibliotecas como express-session exigem um store de produção. O MemoryStore não é adequado para múltiplas instâncias.
Secret de assinatura
Alguns frameworks assinam o cookie para detectar alteração. Use segredo criptográfico, rotação e armazenamento seguro.
Assinatura não cifra
Um cookie assinado continua legível. Não coloque dados sensíveis dentro dele.
Cookie com apenas ID
Manter apenas um identificador reduz tamanho e permite revogação central.
Sessões stateless
Um cookie contendo todo o estado assinado elimina lookup, mas dificulta revogação e aumenta exposição. Para privilégios e sessões longas, estado no servidor costuma ser mais controlável.
CSRF
Cookies são enviados automaticamente pelo navegador. Operações de mudança precisam de proteção CSRF.
Token CSRF
Gere um valor aleatório associado à sessão e exija em header ou campo:
X-CSRF-Token: ...Synchronizer token
O servidor armazena o token na sessão e compara com o valor enviado. Use comparação apropriada e nunca aceite apenas o cookie automático.
Double-submit cookie
Outra estratégia envia um cookie separado e um header. O valor precisa ser vinculado à sessão ou assinado para evitar substituição.
SameSite não é suficiente em todos os casos
Fluxos cross-site, navegadores antigos e ataques dentro da mesma origem exigem proteção adicional.
Métodos seguros
GET, HEAD e OPTIONS não devem modificar estado. Isso reduz exposição a navegação cross-site.
XSS
HttpOnly protege o valor, mas XSS pode executar requisições. Use CSP, escaping e dependências atualizadas.
Consulte Helmet e CSP no Node.js.
Regeneração periódica
Rotacionar o ID em eventos importantes reduz a utilidade de identificadores antigos. Não rotacione em toda requisição, pois isso cria concorrência e perda de estado.
Concorrência entre abas
Duas abas podem fazer requests durante uma rotação. Mantenha um período curto em que o ID anterior aponta para o sucessor, sem permitir uso prolongado.
Race condition no login
Use transação para revogar a sessão pré-login e criar a autenticada. Garanta que apenas o novo cookie seja retornado.
Logout
UPDATE user_sessions
SET status = 'revoked',
revoked_at = now()
WHERE session_hash = $1
AND status = 'active';Depois, expire o cookie no navegador.
Logout global
Revogue todas as sessões do usuário após comprometimento ou a pedido dele.
Lista de sessões
Mostre dispositivo aproximado, criação, último uso e sessão atual. Permita encerrar individualmente.
User agent
User agent ajuda na apresentação, mas pode ser forjado e não deve decidir autenticação sozinho.
IP
IPs mudam em redes móveis e proxies. Use como sinal de risco, não como vínculo rígido que bloqueia usuários legítimos.
Trusted proxies
Leia IP real apenas após configurar corretamente proxies confiáveis. Headers externos podem ser forjados.
Mudança de senha
Uma senha alterada pode revogar todas as sessões, exceto a atual após step-up. Defina a política e avise o usuário.
Recuperação de conta
Revogue todas as sessões e refresh tokens antigos. Uma recuperação não deve preservar acesso de um atacante.
MFA
Registre métodos usados e horário:
auth_methods: ['password', 'totp']
mfa_verified_at: '...'Step-up authentication
Exija autenticação recente para:
- alterar e-mail;
- remover MFA;
- criar token de API;
- exportar dados;
- alterar cobrança;
- impersonar usuário.
Janela de step-up
const recent = Date.now() - session.mfaVerifiedAt
< 10 * 60 * 1000;Use horário do servidor.
Autorização
A sessão identifica o usuário e contexto, mas RBAC ou ABAC decide a ação.
Consulte RBAC no Node.js e ABAC no Node.js.
Multi-tenancy
Uma sessão pode armazenar o tenant ativo, mas a aplicação deve validar que a membership continua existente. Trocar de tenant deve atualizar contexto de forma segura.
Troca de tenant
Rotacione a sessão ou atualize uma versão de contexto, evitando aceitar apenas um tenant ID enviado pelo frontend.
Permissões alteradas
Use authz_version para invalidar decisões cacheadas quando roles mudam.
Cache de sessão
Se houver cache em memória diante do store, use TTL curto e invalidação. Uma sessão revogada não pode continuar válida por minutos.
Disponibilidade do store
Se o armazenamento cai, falhe fechado para rotas autenticadas. Não crie uma sessão sem persistência.
Timeouts
O lookup de sessão precisa de timeout. Uma dependência lenta não deve prender todas as requisições.
Graceful shutdown
Conclua gravações pendentes e feche conexões do store. Sessões já persistidas continuam válidas após reinício.
Auditoria
Registre:
- login;
- logout;
- logout global;
- rotação;
- step-up;
- sessão revogada;
- recuperação;
- atividade suspeita.
Nunca registre o ID em texto claro.
Veja Logs de Auditoria no Node.js.
Métricas
Monitore sessões ativas, logins, revogações, expirações, falhas de store, latência e rotações.
Cardinalidade
Não use session ID ou user ID como labels de métricas.
Alertas
Múltiplas sessões novas, mudança rápida de localização, recuperação seguida de uso antigo e falhas de store podem exigir investigação.
Limpeza
Remova sessões expiradas em lotes e preserve apenas metadados necessários para auditoria.
Índices
CREATE INDEX idx_sessions_user_active
ON user_sessions (user_id, last_seen_at DESC)
WHERE status = 'active';Testes unitários
Teste geração, hash, expiração, rotação e regras de status.
Teste de fixation
test('troca o ID depois do login', async () => {
const anonymous = await startSession();
const authenticated = await login(
anonymous.cookie,
credentials
);
assert.notEqual(
authenticated.sessionCookie,
anonymous.cookie
);
});Teste de cookie
Confirme HttpOnly, Secure, SameSite, Path e prefixo configurado.
Teste de CSRF
Uma requisição de mudança sem token válido deve falhar, mesmo com cookie.
Teste de expiração
Use relógio controlado para testar inatividade e limite absoluto.
Teste de revogação
Após logout, o mesmo cookie não pode acessar rotas protegidas.
Teste de múltiplas réplicas
Crie a sessão em uma instância e use em outra. Isso detecta uso acidental de memória local.
Teste de store indisponível
A API deve retornar erro controlado e não considerar o usuário autenticado.
Teste de concorrência
Simule duas abas durante rotação e confirme o comportamento de sucessor sem prolongar o ID anterior.
Teste de remoção de permissão
Altere authz_version e confirme que a sessão antiga reavalia o acesso.
Erros comuns
- ID previsível: sessões podem ser adivinhadas.
- Não rotacionar após login: fixation permanece possível.
- Cookie sem HttpOnly: scripts roubam o ID.
- Cookie sem Secure: rede expõe a sessão.
- Sem CSRF: sites externos executam ações.
- MemoryStore em produção: réplicas divergem.
- Expiração apenas no cookie: servidor aceita sessão antiga.
Boas práticas
- Gere IDs criptográficos.
- Armazene somente hash.
- Rotacione após login e step-up.
- Use cookie HttpOnly, Secure e SameSite.
- Proteja CSRF.
- Use store compartilhado.
- Defina inatividade e limite absoluto.
- Permita revogação por dispositivo.
- Reavalie autorização.
- Audite sem registrar IDs.
Conclusão
Implementar Sessões Seguras no Node.js exige mais que salvar um identificador em cookie. O ID precisa ser aleatório, rotacionado após autenticação e validado em um armazenamento compartilhado.
Cookies seguros, proteção CSRF, expiração, revogação e step-up reduzem o impacto de roubo e fixation. Com auditoria, lista de dispositivos e testes entre réplicas, a aplicação mantém a conveniência das sessões sem aceitar credenciais permanentes e invisíveis ao usuário.



