JWT do jeito certo: erros comuns de implementação e como evitar
1. Fundamentos do JWT: o que todo desenvolvedor precisa saber
JSON Web Token (JWT) é um padrão aberto (RFC 7519) que define uma forma compacta e autossuficiente de transmitir informações entre partes como um objeto JSON. Um token JWT é composto por três partes separadas por pontos:
- Header: contém o tipo do token e o algoritmo de assinatura
- Payload: contém as claims (declarações) como
sub,exp,iss,aud - Signature: garante que o token não foi alterado
Exemplo de estrutura:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
O fluxo típico de autenticação envolve: emissão do token pelo servidor após login, transporte via cabeçalho HTTP Authorization: Bearer <token>, e verificação em cada requisição protegida.
É crucial entender a diferença entre JWT, JWS e JWE:
- JWT: o conceito geral
- JWS (JSON Web Signature): JWT assinado, mas não criptografado — o mais comum
- JWE (JSON Web Encryption): JWT criptografado, garantindo confidencialidade
Use JWS para transmitir informações públicas não sensíveis; use JWE quando precisar proteger dados confidenciais no payload.
2. Erro fatal: armazenar segredos e chaves no código-fonte
Um dos erros mais comuns e perigosos é hardcodar segredos de assinatura diretamente no código. Isso expõe a aplicação a ataques graves se o repositório vazar.
Exemplo do que NÃO fazer:
const jwt = require('jsonwebtoken');
const SECRET = 'minha-senha-super-secreta-123'; // NUNCA FAÇA ISSO
Boa prática:
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET; // Use variáveis de ambiente
Para ambientes mais robustos, utilize gerenciadores de secrets como HashiCorp Vault ou AWS Secrets Manager. Além disso, implemente rotação periódica de chaves.
Quanto ao algoritmo de assinatura:
- HS256 (HMAC com SHA-256): usa uma chave secreta simétrica — mais simples, mas exige que o segredo nunca seja exposto
- RS256 (RSA com SHA-256): usa par de chaves pública/privada — recomendado para sistemas distribuídos, pois a chave pública pode ser compartilhada com segurança
3. Validação de token: o que NÃO pode faltar
Validar um token vai muito além de decodificar o payload. É obrigatório verificar:
- Assinatura: garante que o token não foi forjado
- exp (expiration): impede uso de tokens vencidos
- iss (issuer): confirma que o token veio da fonte esperada
- aud (audience): crucial para evitar que tokens de um serviço sejam usados em outro
Exemplo de validação correta:
jwt.verify(token, SECRET, {
algorithms: ['RS256'],
issuer: 'https://auth.meusistema.com',
audience: 'https://api.meusistema.com'
}, (err, decoded) => {
if (err) return res.status(401).json({ error: 'Token inválido' });
// continua...
});
Ataques comuns a evitar:
- None algorithm attack: o invasor altera o header para "alg":"none" e remove a assinatura. A biblioteca deve rejeitar tokens sem assinatura.
- Algorithm confusion: se o servidor espera RS256 mas o token usa HS256, o invasor pode usar a chave pública (que é conhecida) para assinar com HS256. Sempre restrinja os algoritmos aceitos.
Implemente também uma lista de revogação (blacklist) para tokens comprometidos, armazenando o jti (JWT ID) ou o hash do token em um cache Redis com TTL igual ao tempo restante do token.
4. Armazenamento inseguro do token no cliente
O local onde o token é armazenado no cliente determina a superfície de ataque:
| Armazenamento | Risco XSS | Risco CSRF | Recomendado? |
|---|---|---|---|
| LocalStorage | Alto (qualquer script acessa) | Baixo | Não |
| sessionStorage | Alto | Baixo | Não |
| Cookie HttpOnly | Baixo (inacessível via JS) | Médio | Sim, com cuidados |
Padrão recomendado:
Set-Cookie: access_token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/api
Para proteção contra CSRF, utilize tokens CSRF ou implemente o padrão SameSite=Strict.
Alternativa moderna: BFF (Backend for Frontend). O frontend nunca recebe o JWT diretamente; o BFF gerencia tokens e mantém sessões HttpOnly, eliminando riscos de XSS no token.
5. Payload inchado e vazamento de dados sensíveis
Nunca coloque dados sensíveis no payload de um JWT comum (JWS). Lembre-se: JWT não é criptografado — qualquer um com o token pode ler o payload usando apenas base64.
Erro comum:
// Payload com dados sensíveis — NUNCA FAÇA
{
"sub": "123",
"name": "João",
"cpf": "123.456.789-00",
"password_hash": "$2b$10$..."
}
Certo:
{
"sub": "123",
"role": "admin",
"iat": 1700000000,
"exp": 1700086400
}
Se precisar transmitir dados sensíveis, use JWE (criptografia) ou armazene os dados em sessão no servidor e coloque apenas um identificador no JWT.
Além disso, evite payloads grandes. Tokens muito longos aumentam a latência de rede e podem exceder limites de cabeçalhos HTTP (8 KB em muitos servidores). Mantenha claims essenciais apenas.
6. Renovação e expiração mal planejadas
Tokens com expiração muito longa (dias ou meses) aumentam drasticamente a janela de ataque. Um token roubado com validade de 30 dias é um desastre de segurança.
Boa prática:
- Access token: 15-30 minutos
- Refresh token: 7-30 dias (com rotação)
Implementação de refresh tokens:
// Emissão
const accessToken = jwt.sign({ sub: userId }, ACCESS_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ sub: userId, jti: uuid() }, REFRESH_SECRET, { expiresIn: '7d' });
// Renovação
app.post('/refresh', (req, res) => {
const { refreshToken } = req.body;
jwt.verify(refreshToken, REFRESH_SECRET, (err, decoded) => {
if (err) return res.status(401).json({ error: 'Refresh inválido' });
// Verificar se o jti não está na blacklist
const newAccess = jwt.sign({ sub: decoded.sub }, ACCESS_SECRET, { expiresIn: '15m' });
const newRefresh = jwt.sign({ sub: decoded.sub, jti: uuid() }, REFRESH_SECRET, { expiresIn: '7d' });
// Invalidar refresh antigo (rotação)
blacklist.add(decoded.jti);
res.json({ accessToken: newAccess, refreshToken: newRefresh });
});
});
Para logout efetivo, invalide os refresh tokens no servidor (mantendo uma blacklist ou versão do token no banco).
7. Tratamento de erros e logging inadequados
Erros de validação de JWT podem revelar informações valiosas para atacantes.
Evite:
// NUNCA faça isso
res.status(401).json({ error: 'Token expirado em 2024-01-01 15:30:00 UTC' });
Faça:
res.status(401).json({ error: 'Token inválido ou expirado' });
No logging interno, registre apenas metadados não sensíveis:
logger.warn('Token inválido', { userId: decoded?.sub, ip: req.ip });
Nunca logue tokens completos — isso pode expor credenciais em sistemas de monitoramento.
8. Testes e segurança contínua
Implemente testes automatizados para cenários críticos:
// Exemplo de teste com Jest
describe('JWT Validation', () => {
test('rejects token with invalid signature', () => {
const fakeToken = jwt.sign({ sub: 1 }, 'wrong-secret');
expect(validateToken(fakeToken)).toBe(false);
});
test('rejects expired token', () => {
const expiredToken = jwt.sign({ sub: 1, exp: Math.floor(Date.now()/1000) - 3600 }, SECRET);
expect(validateToken(expiredToken)).toBe(false);
});
test('rejects token with none algorithm', () => {
const noneToken = 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxIn0.';
expect(validateToken(noneToken)).toBe(false);
});
});
Utilize ferramentas de análise estática como GitLeaks ou TruffleHog para detectar segredos hard-coded antes do commit. Mantenha as bibliotecas JWT atualizadas e monitore CVEs nos canais oficiais (NVD, GitHub Security Advisories).
Referências
- RFC 7519 - JSON Web Token — Documentação oficial do padrão JWT, definindo estrutura, claims e algoritmos.
- JWT.io - JSON Web Token Debugger — Ferramenta interativa para decodificar, verificar e depurar tokens JWT com suporte a múltiplos algoritmos.
- Auth0 - JWT Handbook — Guia completo sobre melhores práticas de implementação de JWT, incluindo segurança e armazenamento.
- OWASP - JSON Web Token Cheat Sheet — Referência de segurança da OWASP com recomendações contra ataques comuns como none algorithm e algorithm confusion.
- CWE-347: Improper Verification of Cryptographic Signature — Descrição detalhada da vulnerabilidade de não verificar assinaturas corretamente em tokens JWT.
- GitLeaks - Detect Secrets in Git Repos — Ferramenta open-source para detectar segredos hard-coded, incluindo chaves JWT, antes do commit.
- NVD - National Vulnerability Database — Base de dados oficial de CVEs para monitorar vulnerabilidades em bibliotecas JWT.