CSRF, ou Cross-Site Request Forgery, acontece quando um site malicioso induz o navegador de uma vítima autenticada a enviar uma requisição para outra aplicação. O ataque explora credenciais enviadas automaticamente, principalmente cookies de sessão.
CORS não elimina CSRF. O navegador pode bloquear a leitura da resposta e ainda assim enviar formulários ou requisições simples. A proteção precisa combinar cookies adequados, tokens, validação de origem e desenho seguro das rotas.
Quando a aplicação está exposta
O risco é maior quando:
- a autenticação usa cookies;
- rotas alteram estado com POST, PUT, PATCH ou DELETE;
- GET executa ações;
- cookies permitem contexto cross-site;
- não há token CSRF;
- a origem não é validada;
- sessões permanecem ativas por muito tempo.
GET deve ser seguro
Não use GET para excluir, confirmar, pagar, alterar senha ou executar ações. Crawlers, previews e links externos podem disparar GET automaticamente.
// Evite
app.get('/account/delete', deleteAccount);
// Prefira
app.delete('/account', requireAuth, requireCsrf, deleteAccount);SameSite
Cookies com SameSite=Lax ou Strict reduzem o envio em contextos cross-site:
res.cookie('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
});Strict é mais restritivo, mas pode prejudicar fluxos legítimos iniciados por links externos. None permite cross-site e exige Secure; nesse caso, uma proteção CSRF explícita torna-se essencial.
Synchronizer token
O servidor gera um token aleatório ligado à sessão. O frontend envia o token em um campo ou header:
import crypto from 'node:crypto';
function createCsrfToken(req) {
const token = crypto.randomBytes(32).toString('base64url');
req.session.csrfToken = token;
return token;
}Validação:
function requireCsrf(req, res, next) {
const received = req.get('x-csrf-token');
const expected = req.session.csrfToken;
if (!received || !expected) {
res.status(403).json({ error: 'csrf_validation_failed' });
return;
}
const a = Buffer.from(received);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
res.status(403).json({ error: 'csrf_validation_failed' });
return;
}
next();
}Compare valores de tamanho igual e não registre o token.
Double-submit cookie
O servidor envia um cookie CSRF legível pelo JavaScript e o cliente repete o valor em um header. O servidor compara os dois.
Para reduzir substituição do cookie por subdomínios ou atacantes, assine o valor e vincule-o à sessão. Não use um valor previsível.
Validação de Origin
const allowedOrigins = new Set([
'https://app.example.com',
]);
function validateOrigin(req, res, next) {
const origin = req.get('origin');
if (!origin || !allowedOrigins.has(origin)) {
res.status(403).json({ error: 'origin_not_allowed' });
return;
}
next();
}Para requisições antigas sem Origin, o Referer pode ser usado como fallback, com parsing completo da URL. Não compare apenas prefixos de string.
Headers personalizados
Exigir X-CSRF-Token faz o navegador enviar preflight em chamadas cross-origin. Isso acrescenta uma barreira, mas não substitui o token e a validação de origem.
Tokens por sessão ou por requisição
Token por sessão é mais simples. Token rotativo por requisição reduz reutilização, mas complica abas paralelas, retries e navegação. Escolha conforme risco e usabilidade.
Rotação de sessão
Ao autenticar, elevar privilégio ou trocar senha, regenere o ID da sessão e considere renovar o token CSRF. Isso reduz session fixation e uso de contexto antigo.
APIs com Bearer token
Quando o token é enviado manualmente no header Authorization e não fica em cookie, o navegador não o anexa automaticamente, reduzindo o CSRF clássico. Porém, XSS pode roubar o token. A arquitetura precisa proteger ambos os vetores.
Cookies de refresh token
Uma aplicação pode manter access token em memória e refresh token em cookie HttpOnly. A rota de refresh ainda recebe cookie automaticamente e precisa de SameSite, validação de origem e, conforme a arquitetura, token CSRF.
Formulários HTML
<form method="post" action="/profile">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="submit">Salvar</button>
</form>Valide o token antes da lógica de negócio. Limite tamanho do body e Content-Type.
GraphQL
GraphQL costuma usar POST para queries e mutations. Não trate todo POST como alteração, mas aplique proteção à operação autenticada. Bloqueie GET para mutations e limite Content-Type e operações persistidas quando adequado.
Webhooks não usam sessão de navegador
Webhooks devem usar assinatura, timestamp, proteção contra replay e segredo compartilhado ou mTLS. Token CSRF não é o mecanismo correto.
Login CSRF
Um atacante pode forçar a vítima a entrar na conta controlada pelo atacante, fazendo ações posteriores serem registradas na conta errada. A rota de login também pode exigir token e validação de origem.
Logout CSRF
Forçar logout parece menos grave, mas pode ser usado para interrupção ou confusão. Use POST com proteção para logout e não um link GET.
Subdomínios
Cookies com Domain=.example.com são enviados a vários subdomínios. Um subdomínio comprometido pode ampliar o risco. Prefira cookies host-only e prefixos como __Host- quando possível.
Falhas e resposta
Retorne 403 com código estável. Não informe se o token existia, expirou ou divergiu. Registre apenas contexto seguro: rota, origem, método, request ID e motivo categorizado.
Rate limiting
Falhas CSRF repetidas podem indicar automação, frontend antigo ou ataque. Aplique rate limiting e alertas sem bloquear usuários legítimos por uma única falha.
Testes
await request(app)
.post('/profile')
.set('Cookie', sessionCookie)
.expect(403);
await request(app)
.post('/profile')
.set('Cookie', sessionCookie)
.set('Origin', 'https://app.example.com')
.set('X-CSRF-Token', csrfToken)
.expect(200);Teste token ausente, incorreto, de outra sessão, origem não permitida, GET indevido e cookie cross-site.
Erros comuns
- confiar apenas em CORS;
- usar GET para alterações;
- colocar token CSRF em cookie HttpOnly sem outra forma de leitura;
- aceitar qualquer Origin;
- usar token previsível;
- não vincular o token à sessão;
- registrar tokens;
- ignorar login e refresh;
- configurar SameSite=None sem proteção.
Fluxo recomendado
- torne GET seguro;
- configure cookies Secure, HttpOnly e SameSite;
- gere token aleatório;
- vincule à sessão;
- exija header ou campo;
- valide Origin;
- proteja login, logout e refresh;
- teste fluxos cross-site;
- monitore falhas.
Combine a proteção com CORS no Node.js, Helmet, Rate Limiting e HTTPS.
Consulte a referência da OWASP sobre CSRF e a documentação de cookies da MDN.




