Gerenciamento de sessão seguro
1. Fundamentos de Sessões Web Seguras
Uma sessão web representa o estado de interação entre um cliente e um servidor durante um período de atividade contínua. Quando um usuário faz login, o servidor cria uma sessão para manter o contexto autenticado sem exigir que as credenciais sejam reenviadas a cada requisição. A segurança desse mecanismo é crítica porque uma sessão comprometida equivale a um acesso não autorizado completo.
A principal diferença entre sessão no servidor e token no cliente está no armazenamento do estado. Em sessões tradicionais, o servidor mantém os dados da sessão (em memória, banco ou cache) e envia apenas um identificador ao cliente. Já em abordagens stateless como JWT, o token contém os próprios dados da sessão, assinados criptograficamente. Cada modelo tem implicações de segurança distintas.
O ciclo de vida seguro de uma sessão compreende três fases:
- Criação: geração de um identificador imprevisível e associação ao usuário autenticado
- Manutenção: verificação contínua da validade e integridade da sessão
- Destruição: invalidação explícita no servidor, não apenas no cliente
2. Geração e Armazenamento Seguro de Identificadores
Gerar session IDs previsíveis é uma das falhas mais exploradas. Use geradores criptograficamente seguros, como o módulo crypto do Node.js:
const crypto = require('crypto');
const sessionId = crypto.randomBytes(32).toString('hex');
O tamanho mínimo recomendado é de 128 bits (16 bytes), mas 256 bits (32 bytes) oferece margem confortável contra ataques de força bruta. A entropia deve vir de uma fonte segura do sistema operacional, não de funções como Math.random().
O armazenamento do session ID deve usar cookies com atributos de segurança obrigatórios:
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600
HttpOnly: impede acesso via JavaScript, bloqueando ataques XSSSecure: exige conexão HTTPS, prevenindo interceptação em redeSameSite=Strict: restringe envio do cookie a requisições originadas do mesmo sitePath=/: limita o escopo do cookie ao domínio específico
3. Proteção contra Sequestro de Sessão (Session Hijacking)
O sequestro de sessão ocorre quando um atacante obtém o session ID de um usuário legítimo. A primeira linha de defesa é regenerar o identificador após eventos críticos:
// Após login bem-sucedido
req.session.regenerate((err) => {
if (err) return handleError(err);
req.session.userId = user.id;
req.session.createdAt = Date.now();
res.redirect('/dashboard');
});
A validação de fingerprint (combinação de IP e User-Agent) pode adicionar segurança, mas deve ser usada com cautela: IPs podem mudar em redes móveis ou VPNs, criando falsos positivos. Uma abordagem mais robusta é registrar o fingerprint e alertar o usuário sobre mudanças, sem bloquear automaticamente.
Implemente dois tipos de expiração:
- Expiração absoluta: invalida a sessão após um tempo máximo (ex: 24 horas)
- Expiração por inatividade: invalida após período sem atividade (ex: 30 minutos)
// Verificação de inatividade
const maxInactive = 30 * 60 * 1000; // 30 minutos
if (Date.now() - req.session.lastActivity > maxInactive) {
req.session.destroy();
return res.status(401).send('Sessão expirada por inatividade');
}
req.session.lastActivity = Date.now();
4. Defesas contra Fixação de Sessão (Session Fixation)
Fixação de sessão ocorre quando um atacante força a vítima a usar um session ID conhecido. A defesa principal é nunca aceitar um session ID fornecido pelo cliente sem autenticação prévia. Isso significa que o servidor deve sempre gerar um novo identificador após o login, independentemente de qual ID foi enviado.
A troca obrigatória do identificador após autenticação bem-sucedida é a medida mais eficaz:
// Após validação de credenciais
if (validCredentials) {
const newSessionId = crypto.randomBytes(32).toString('hex');
// Armazena nova sessão com dados do usuário
await storeSession(newSessionId, { userId: user.id });
// Invalida sessão antiga
await deleteSession(oldSessionId);
// Define novo cookie
res.cookie('sessionId', newSessionId, cookieOptions);
}
Sempre invalide sessões antigas ao criar uma nova sessão autenticada. Não basta sobrescrever os dados — o identificador anterior deve ser removido do armazenamento do servidor.
5. Cookies Seguros e Configuração de Headers
A configuração fina de cookies previne múltiplos vetores de ataque. Use Expires ou Max-Age para definir tempo de vida, mas prefira Max-Age por ser relativo ao momento da criação:
Set-Cookie: sessionId=xyz789; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400
O prefixo __Host- vincula o cookie à origem completa (domínio + porta), impedindo que subdomínios maliciosos o leiam:
Set-Cookie: __Host-sessionId=abc123; Secure; Path=/; HttpOnly; SameSite=Strict
Já o prefixo __Secure- exige que o cookie seja marcado como Secure, mas não impõe restrições de path. Use __Host- sempre que possível para maior segurança.
Headers de segurança adicionais no Set-Cookie incluem:
- Domain: evite definir domínios amplos; omita para restringir ao servidor de origem
- Partitioned: útil em ambientes de terceiros, mas requer análise cuidadosa de privacidade
6. Logout e Encerramento de Sessão
Um erro comum é apenas remover o cookie do lado cliente, deixando a sessão ativa no servidor. O logout seguro deve invalidar a sessão no servidor:
app.post('/logout', (req, res) => {
const sessionId = req.cookies.sessionId;
if (sessionId) {
// Remove do banco ou cache
sessionStore.destroy(sessionId, (err) => {
if (err) console.error('Erro ao destruir sessão:', err);
});
}
// Limpa cookie do cliente
res.clearCookie('sessionId', { path: '/' });
res.status(200).send('Logout realizado');
});
Para logout em todos os dispositivos (single sign-out), mantenha um registro de todas as sessões ativas de um usuário:
// Ao fazer login, adiciona à lista de sessões do usuário
await db.collection('userSessions').insertOne({
userId: user.id,
sessionId: newSessionId,
createdAt: new Date()
});
// No logout global
await db.collection('userSessions').deleteMany({ userId: user.id });
// Remove cada sessão do cache
userSessions.forEach(s => sessionStore.destroy(s.sessionId));
7. Boas Práticas em APIs e Sessões Stateless
Para APIs RESTful, JWTs são comuns, mas exigem cuidados específicos. Use assinatura forte com HS256 (segredo compartilhado) ou RS256 (par de chaves):
// Geração de JWT com expiração curta
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ algorithm: 'HS256', expiresIn: '15m' }
);
No lado cliente, armazene tokens em memória (variável JavaScript) sempre que possível. Evite localStorage por ser acessível via XSS. Para SPAs, use cookies HttpOnly com refresh tokens armazenados em memória:
// Refresh token rotativo
app.post('/refresh', (req, res) => {
const refreshToken = req.cookies.refreshToken;
if (!refreshToken) return res.status(401).send('Token ausente');
const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
// Gera novo access token e novo refresh token
const newAccessToken = generateAccessToken(decoded.userId);
const newRefreshToken = generateRefreshToken(decoded.userId);
// Invalida refresh token antigo (blacklist)
blacklistToken(refreshToken);
res.json({ accessToken: newAccessToken, refreshToken: newRefreshToken });
});
8. Monitoramento e Resposta a Incidentes
Registre eventos de sessão sem expor dados sensíveis:
// Logging seguro
logger.info({
event: 'login_success',
userId: user.id,
ip: req.ip,
timestamp: new Date().toISOString(),
sessionId: hashSessionId(req.sessionID) // hash para correlação sem expor ID
});
Detecte anomalias como múltiplos IPs simultâneos para o mesmo usuário:
// Verificação de múltiplos IPs
const activeSessions = await getActiveSessions(userId);
const uniqueIPs = new Set(activeSessions.map(s => s.ip));
if (uniqueIPs.size > 3) {
alertSecurityTeam(userId, 'Múltiplos IPs detectados');
invalidateAllSessions(userId);
}
Em caso de vazamento suspeito, implemente invalidação forçada:
- Force logout de todas as sessões do usuário afetado
- Exija reautenticação com verificação adicional (2FA)
- Rotacione chaves de assinatura se JWTs foram comprometidos
Referências
- OWASP Session Management Cheat Sheet — Guia completo da OWASP sobre práticas seguras de gerenciamento de sessão, incluindo geração de IDs, cookies e proteção contra ataques.
- MDN Web Docs: Set-Cookie — Documentação oficial sobre o header Set-Cookie, com detalhes sobre atributos HttpOnly, Secure, SameSite e prefixos.
- Node.js Crypto Documentation — Referência oficial da API crypto.randomBytes para geração segura de identificadores de sessão.
- JWT.io: Introduction to JSON Web Tokens — Guia introdutório sobre JWTs, incluindo algoritmos de assinatura e boas práticas de expiração.
- PortSwigger: Session Fixation — Artigo técnico detalhado sobre ataques de fixação de sessão e como preveni-los.
- Redis: Session Store Best Practices — Tutorial sobre uso de Redis como armazenamento de sessão, com exemplos de expiração e invalidação.