Cookies são usados para sessões, preferências e tokens de renovação em aplicações Node.js. Como o navegador os envia automaticamente, uma configuração incorreta pode expor credenciais a JavaScript malicioso, conexões sem TLS, subdomínios comprometidos e ataques CSRF.
Cookies seguros exigem atributos corretos, escopo mínimo, rotação de sessão, expiração, validação no servidor e uma política clara para ambientes diferentes.
Criando um cookie de sessão
res.cookie('__Host-session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: 1000 * 60 * 60 * 8,
});O prefixo __Host- exige Secure, Path=/ e ausência de Domain em navegadores compatíveis. Isso reduz interferência de subdomínios.
HttpOnly
HttpOnly impede leitura direta por document.cookie. Ele reduz roubo de sessão por XSS, mas não impede que um script malicioso faça requisições usando o cookie. Por isso, CSP, sanitização e proteção CSRF continuam necessárias.
Secure
Secure limita o envio a HTTPS. Em produção, nunca envie sessão por HTTP. Quando TLS termina no proxy, configure trust proxy corretamente:
app.set('trust proxy', 1);Não confie em qualquer proxy sem conhecer a topologia, pois isso afeta protocolo, IP e cookies.
SameSite
- Strict: mais restritivo, pode afetar navegação iniciada por links externos;
- Lax: equilíbrio comum para sessões web;
- None: permite cross-site e exige Secure.
Aplicações com frontend e API em sites diferentes podem precisar de None. Nesse cenário, implemente CSRF explicitamente.
Domain
Omitir Domain cria um cookie host-only, enviado apenas ao host que o definiu. Use Domain=.example.com somente quando vários subdomínios realmente precisam compartilhar a credencial.
Um subdomínio vulnerável pode ampliar o impacto de cookies compartilhados.
Path
Path reduz onde o navegador envia o cookie:
res.cookie('__Secure-refresh', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/auth/refresh',
});Path não é uma barreira de segurança completa entre aplicações do mesmo host, mas reduz exposição desnecessária.
Expires e Max-Age
Max-Age define duração relativa; Expires, uma data absoluta. Cookies sem ambos são de sessão do navegador, embora navegadores possam restaurar sessões.
O servidor deve validar expiração independentemente do navegador. Um cookie antigo não deve reativar uma sessão removida.
Cookie assinado
Assinatura detecta alteração do valor, mas não o criptografa:
import cookieParser from 'cookie-parser';
app.use(cookieParser(process.env.COOKIE_SIGNING_SECRET));
res.cookie('preferences', JSON.stringify({ theme: 'dark' }), {
signed: true,
httpOnly: true,
secure: true,
sameSite: 'lax',
});Não coloque dados sensíveis em cookies apenas assinados. O usuário ainda pode ver o conteúdo.
Sessão opaca
Uma abordagem robusta é armazenar no cookie apenas um ID aleatório e manter dados da sessão no servidor:
const sessionId = crypto.randomBytes(32).toString('base64url');
await sessions.set(sessionId, {
userId: user.id,
createdAt: Date.now(),
}, { ttlSeconds: 28800 });Hash do ID pode ser armazenado para reduzir impacto de vazamento do banco.
JWT em cookie
JWT em cookie continua sendo enviado automaticamente e pode sofrer CSRF. Além disso, revogação e rotação exigem planejamento. Se usar, mantenha validade curta, claims mínimas, assinatura forte e estratégia para logout.
Refresh token
Refresh tokens devem ser longos, aleatórios, rotacionados e armazenados de forma que possam ser revogados. Um cookie HttpOnly reduz exposição a JavaScript, mas a rota de refresh precisa de SameSite, validação de origem e proteção contra replay.
Rotação de sessão
Regere o identificador depois de login, MFA, elevação de privilégio e troca de senha:
await sessionStore.destroy(oldSessionId);
const newSessionId = createSessionId();
await sessionStore.create(newSessionId, sessionData);Isso reduz session fixation.
Logout
Remova a sessão no servidor e limpe o cookie usando os mesmos atributos principais:
await sessions.delete(sessionId);
res.clearCookie('__Host-session', {
secure: true,
sameSite: 'lax',
path: '/',
});Limpar somente no navegador não revoga uma credencial copiada.
Cookies e cache
Respostas personalizadas não devem ser armazenadas em cache público. Configure:
res.set('Cache-Control', 'private, no-store');
Evite incluir Set-Cookie em respostas cacheadas por CDN sem regras específicas.
Limites de tamanho
Cookies são enviados em muitas requisições. Valores grandes aumentam banda e podem ultrapassar limites de servidor ou proxy. Guarde somente identificadores e informações mínimas.
Múltiplos aplicativos no mesmo domínio
Use nomes exclusivos, host-only e paths adequados. Dois sistemas usando session podem sobrescrever ou interpretar valores incorretos.
Ambiente local
Não desative Secure silenciosamente em qualquer ambiente. Crie configuração explícita e garanta que produção falhe se HTTPS não estiver habilitado.
const isProduction = process.env.NODE_ENV === 'production';
if (isProduction && process.env.COOKIE_SECURE !== 'true') {
throw new Error('Cookies seguros são obrigatórios em produção');
}Prefixos __Secure- e __Host-
__Secure- exige Secure. __Host- é mais restritivo e impede Domain. Prefira __Host- para sessão principal quando o escopo permitir.
CHIPS e Partitioned
Cookies particionados podem ser usados em certos cenários de terceiros. O suporte e a semântica devem ser verificados nos navegadores-alvo. Não use para contornar políticas de privacidade sem uma necessidade legítima.
Logs
Nunca registre o header Cookie, Set-Cookie, IDs de sessão ou refresh tokens. Configure redaction:
const logger = pino({
redact: [
'req.headers.cookie',
'res.headers.set-cookie',
],
});Testes
const response = await request(app).post('/login').send(credentials);
const cookie = response.headers['set-cookie'][0];
expect(cookie).toContain('HttpOnly');
expect(cookie).toContain('Secure');
expect(cookie).toContain('SameSite=Lax');
expect(cookie).toContain('Path=/');Teste logout, expiração, rotação, proxy e requisições cross-site.
Erros comuns
- armazenar senha ou dados pessoais no cookie;
- usar Domain amplo sem necessidade;
- esquecer Secure em produção;
- considerar HttpOnly proteção completa contra XSS;
- usar SameSite=None sem CSRF;
- não revogar sessão no servidor;
- registrar Cookie ou Set-Cookie;
- manter sessões indefinidamente;
- usar valor previsível;
- não rotacionar após login.
Fluxo recomendado
- use ID aleatório e opaco;
- prefira cookie host-only;
- ative HttpOnly e Secure;
- escolha SameSite conscientemente;
- defina Path e expiração;
- rotacione sessão;
- revogue no logout;
- proteja contra CSRF;
- redija logs;
- teste no proxy real.
Combine cookies seguros com CSRF no Node.js, CORS, TLS e HTTPS, Pino e Secret Management.
Consulte a documentação de Set-Cookie da MDN e o guia de sessões da OWASP.



