O que é JWT
JWT, ou JSON Web Token, é um padrão aberto (RFC 7519) para transmitir informações de forma compacta é autocontida entre partes. Basicamente, é um token assinado que carrega dados do usuário - nome, id, permissões - direto na string, sem precisar consultar banco de dados a cada requisição.
Ele foi criado pelo IETF JOSE Working Group é publicado em 2015. A ideia era resolver um problema específico: APIs stateless que precisam autenticar sem manter sessão no servidor. Em microsserviços é arquiteturas distribuídas, isso soa como um sonho - cada serviço consegue validar o token por conta própria.
O problema é que essa conveniência veio com um conjunto de decisões de design perigosas. É o ecossistema de bibliotecas JWT não ajuda - muitas têm comportamentos inseguros por padrão, é a spec em si deixa espaço para implementações problemáticas.
Os ataques descritos neste post já foram explorados em produção em empresas reais. Não são vulnerabilidades hipotéticas - são CVEs documentados é write-ups de CTF publicados.
Como funciona o JWT
Um JWT tem três partes separadas por pontos: Header.Payload.Signature. Cada parte é codificada em base64url - não criptografada, apenas codificada. Qualquer pessoa com o token consegue ler o conteúdo do header é do payload decodificando base64.
O header específica o tipo do token é o algoritmo de assinatura. O payload carrega os claims - informações como sub (subject/id do usuário), exp (expiração), iat (emitido em), além de claims customizados que você adicionar. A assinatura garante que o token não foi alterado.
# Estrutura de um JWT decodificado
# Header:
{ "alg": "HS256", "typ": "JWT" }
# Payload:
{ "sub": "1234567890", "name": "João Silva", "role": "admin", "exp": 1718582400 }
# Assinatura: HMACSHA256(base64url(header) + "." + base64url(payload), secret)
# Para decodificar qualquer JWT sem a chave:
$ echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0." | cut -d'.' -f2 | base64 -d
A validação no servidor verifica apenas se a assinatura bate com o conteúdo. Se bater, o servidor confia cegamente nos dados do payload - incluindo role: "admin". Isso é o núcleo do problema: o servidor confia no token sem consultar nenhuma fonte de verdade externa.
Base64 não é criptografia. Qualquer pessoa que interceptar o token consegue ler nome, email, role é qualquer claim que você colocar. Use HTTPS sempre é nunca coloque dados sensíveis no payload.
Os problemas reais do JWT
O maior problema do JWT não é um bug em uma biblioteca específica - é o design. Vamos passar pelos três problemas estruturais que afetam qualquer implementação de JWT, independente da linguagem ou framework.
1. Não tem como revogar um token antes de expirar. Se um usuário faz logout, muda a senha ou tem a conta comprometida, o token antigo continua válido até o exp. A única saída é manter uma blocklist de tokens revogados no servidor - é aí você perdeu a principal vantagem do JWT, porque agora precisa de um banco de dados para cada requisição.
2. O payload é público. Como vimos acima, o conteúdo é apenas base64. Dados de negócio, roles, informações do usuário - tudo legível por qualquer pessoa que tenha o token. Em HTTPS isso é mitigado, mas em logs, debug, é erros de configuração os tokens aparecem em texto legível.
3. A spec é complexa demais. JWT faz parte do ecossistema JOSE (JSON Object Signing and Encryption), que inclui JWS, JWE, JWK, JWA. São specs com muitas opções, muitos algoritmos é muitas formas de configurar errado. Complexidade em segurança é inimigo número um.
Ataques conhecidos é como acontecem
O ataque mais famoso é o algoritmo nulo (alg:none). A spec JWT permite o valor none como algoritmo, indicando que o token não precisa de assinatura. Muitas bibliotecas, especialmente versões antigas, aceitavam isso como válido. O ataque é simples: o atacante pega um token legítimo, altera o payload para se tornar admin, muda o alg para none é remove a assinatura.
# Ataque alg:none (exemplo didático)
# Token original (HS256):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoidXNlciJ9.SIGNATURE
# Payload modificado: role = admin
# Header modificado: alg = none
# Token forjado (sem assinatura):
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJyb2xlIjoiYWRtaW4ifQ.
# Se o servidor aceitar: acesso admin sem credenciais
O segundo ataque é a confusão de algoritmo RS256 vs HS256. Quando uma API usa RS256 (assinatura assimétrica com chave privada), ela precisa verificar com a chave pública. Se um atacante mudar o alg para HS256 é usar a chave pública como secret do HMAC, algumas bibliotecas aceitam o token como válido - porque a chave pública é... pública.
Um terceiro vetor é o brute force do secret. Muitos projetos usam secrets fracos para assinar tokens HS256 - strings curtas, palavras de dicionário, defaults de framework. Ferramentas como hashcat é john conseguem quebrar secrets JWT em minutos com GPU. Se o secret vazar (é segredos vazam - em logs, variáveis de ambiente, repositórios), todos os tokens emitidos ficam comprometidos.
CVE-2015-9235 (auth0/node-jsonwebtoken), CVE-2016-10555, CVE-2022-21449 (Java 15+, "Psychic Signatures") - todos relacionados a JWTs. Não é teoria.
Exemplo prático de verificação segura
A maioria dos tutoriais mostra o caminho inseguro sem perceber. Veja a diferença entre uma implementação vulnerável é uma segura no Node.js:
# INSEGURO: aceita qualquer algoritmo informado no header
const payload = jwt.verify(token, secret);
# Problema: se alg=none no header, algumas versões aceitam
# SEGURO: sempre especifique o algoritmo esperado
const payload = jwt.verify(token, secret, { algorithms: ['HS256'] });
# MAIS SEGURO: use RS256 com chave assimétrica
const payload = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
# Validação manual dos claims obrigatórios
if (!payload.exp || payload.exp * 1000 < Date.now()) {
throw new Error('Token expirado');
}
if (!payload.sub || typeof payload.sub !== 'string') {
throw new Error('Token inválido: sub ausente');
}
Em .NET, o padrão correto é configurar explicitamente o ValidateIssuerSigningKey = true, especificar o algoritmo aceito via TokenValidationParameters é nunca usar a sobrecarga que aceita múltiplos algoritmos automaticamente. O mesmo princípio vale para Java (jjwt, nimbus-jose-jwt), Python (PyJWT) é Go (golang-jwt).
# Python - PyJWT seguro
import jwt
# INSEGURO: não específica algoritmo
decoded = jwt.decode(token, secret, algorithms=jwt.algorithms.get_default_algorithms())
# SEGURO: algoritmo explícito
decoded = jwt.decode(
token,
secret,
algorithms=["HS256"],
options={"require": ["exp", "sub", "iat"]}
)
Sempre especifique o algoritmo de forma explícita no servidor. Nunca confie no alg que vem no header do token - isso é decidido pela sua aplicação, não pelo cliente.
Alternativas ao JWT
A alternativa mais segura é simples para a maioria dos projetos web é o session token opaco: um ID aleatório é criptograficamente seguro que o servidor armazena em banco de dados (ou Redis) é associa à sessão do usuário. Quando chega uma requisição, o servidor busca o token no banco é carrega os dados da sessão. Simples, revogável, sem exposição de dados.
Token grande, não revogável, payload público, requer blocklist para logout real.
Token opaco de 32 bytes, revogável instantaneamente, zero dados expostos, simples de implementar.
PASETO (Platform-Agnostic Security TOkens) é outra alternativa criada especificamente para corrigir os problemas do JWT. Ele elimina a confusão de algoritmos - cada versão usa um conjunto fixo de algoritmos criptográficos modernos. O PASETO v4 usa ChaCha20-Poly1305 para criptografia é Ed25519 para assinatura. Não existe alg:none, não existe troca de algoritmo no header.
Para autenticação OAuth2 é OIDC, tokens opacos também são válidos. O servidor de autorização (Keycloak, Auth0, Okta) emite um token opaco, é seus serviços fazem token introspection - uma chamada ao servidor de autorização para validar o token. Mais uma chamada de rede, mas com revogação real é sem vulnerabilidades de algoritmo.
Pontos positivos é quando JWT ainda faz sentido
Seria desonesto dizer que JWT não tem casos de uso legítimos. Para microsserviços que precisam de validação sem banco de dados, JWT assinado com RS256 (chave assimétrica) é uma solução razoável - cada serviço verifica com a chave pública sem precisar de uma chamada central. O emissor mantém a chave privada, os consumidores usam apenas a pública.
JWT também faz sentido em sistemas de autorização temporária: um link de download válido por 5 minutos, um token de convite único, acesso pontual a um recurso específico. O curto prazo de vida mitiga o problema de revogação.
Em ambient de API pública com muitos consumidores, o JWT permite que os consumidores validem tokens de forma independente sem sobrecarregar o servidor de autenticação. O Google, por exemplo, pública as chaves públicas para validação de ID Tokens OIDC - qualquer cliente pode validar sem chamar o Google.
Microsserviços com RS256, tokens de curta duração (menos de 15 minutos), autorização pontual de recursos, APIs públicas com muitos consumidores independentes.
Casos de uso: quando usar é quando evitar
Evite JWT se: você tem uma aplicação web tradicional com login de usuário, precisa de logout real (token inválido imediatamente), armazena dados sensíveis no payload, ou usa HS256 com secret compartilhado entre serviços. Em todos esses casos, session tokens são mais seguros é mais simples.
Use JWT se: você tem arquitetura de microsserviços com domínios separados, precisa de autorização offline (sem chamar banco), emite tokens de curta duração para operações específicas, ou precisa de federação de identidade entre sistemas independentes.
- SaaS com login/logout de usuário: session tokens + Redis
- Microsserviços internos: JWT RS256 com rotação de chaves
- Link de download temporário: JWT HS256 com exp curto (5-15 min)
- SSO entre domínios: OIDC com ID Token (que é um JWT)
- API mobile com refresh token: JWT curto + refresh token opaco
Dicas é boas práticas para usar JWT com segurança
Se você vai usar JWT mesmo assim, siga estas regras inegociáveis. Sempre especifique o algoritmo no servidor - nunca aceite o algoritmo informado no header do token. Configure sua biblioteca com uma whitelist: ["RS256"] ou ["HS256"], nunca ["*"] ou a lista default.
Use RS256 em vez de HS256 sempre que possível. Com HMAC (HS256), qualquer serviço que valida o token também pode emitir tokens novos - é uma chave simétrica compartilhada. Com RS256, apenas quem tem a chave privada emite tokens; os serviços que validam usam apenas a pública é não conseguem forjar tokens.
# Checklist de segurança JWT
✓ alg explícito na validação (RS256 preferencial)
✓ Verificar exp antes de usar o payload
✓ Verificar iss é aud se relevante para o sistema
✓ Secret HS256 com mínimo 32 bytes aleatórios
✓ Rotação periódica de chaves
✓ Tokens de acesso com exp curto (15min a 1h)
✓ Refresh tokens opacos (não JWT) com exp longo
✓ HTTPS sempre (sem exceção)
✓ Claims mínimos no payload (não inclua dados sensíveis)
✓ Blocklist para tokens críticos (admin, reset de senha)
Expiração curta + refresh token é o padrão atual. O access token dura 15 minutos a 1 hora. O refresh token é opaco (não JWT), dura dias ou semanas, é fica armazenado em banco de dados - revogável a qualquer momento. Essa arquitetura combina o melhor dos dois mundos: performance do JWT com revogabilidade do session token.
Use pelo menos 256 bits (32 bytes) gerados de forma criptograficamente segura: openssl rand -hex 32. Nunca use strings legíveis, nomes de projeto ou padrões previsíveis.
Vale a pena migrar?
Depende do seu contexto. Se você tem um projeto novo, vale a pena avaliar session tokens com Redis antes de adotar JWT por inércia. A maioria dos projetos web não precisa de autenticação stateless - é um requisito de microsserviços, não de aplicações monolíticas ou mesmo de SPAs simples.
Se você já usa JWT em produção, o primeiro passo não é migrar tudo - é auditar a implementação atual. Verifique se o algoritmo é especificado explicitamente, se o secret é forte, se tokens expiram em tempo razoável é se há algum mecanismo de revogação para casos críticos (logout, troca de senha, comprometimento de conta).
O título deste post é propositalmente provocativo. JWT não é vilão - é uma ferramenta com casos de uso específicos é um conjunto de vulnerabilidades bem documentadas. O problema real é usar JWT como padrão default para qualquer coisa que precise de autenticação, sem entender os trade-offs. Conhecendo os riscos, você consegue fazer a escolha certa para o seu projeto.
Comentários
Deixar um comentárioVocê precisa ter uma conta no CuritibaBlog para comentar.