O JSON Web Token (JWT) virou o formato padrão de "crachá digital" em APIs e sistemas de login: o servidor emite um token, o cliente o apresenta a cada requisição, e o servidor confia nele sem consultar o banco. Só que a facilidade de ler um JWT leva a um mal-entendido perigoso: quem consegue decodificar o conteúdo acha que o token foi validado. Este guia mostra a estrutura de um JWT, o que cada parte significa e os erros de segurança que mais aparecem.
Estrutura: três partes separadas por ponto
Um JWT tem a forma header.payload.signature. Cada parte é
codificada em Base64URL (a variante do Base64 explicada em
Base64 não é criptografia).
Veja um exemplo real, gerado com uma chave de teste:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0Iiwibm9tZSI6Ik1hcmlhIiwiZXhwIjoxODkzNDU2MDAwfQ.VR2CoHmeeWiQlrrI-6G_TgB4cm9mDEkWD_wf-rmoPWA
Decodificando as duas primeiras partes, obtém-se:
header: {"alg":"HS256","typ":"JWT"}
payload: {"sub":"1234","nome":"Maria","exp":1893456000}
- Header: diz o tipo do token e o algoritmo da assinatura (
HS256, aqui). - Payload: carrega as informações, chamadas de claims.
- Signature: um código calculado sobre header e payload com uma chave; prova que o conteúdo não foi alterado.
Você pode colar um token no decodificador de JWT para ver header e payload em JSON legível.
As claims padrão
A especificação (RFC 7519) define nomes curtos e comuns:
iss(issuer): quem emitiu o token.sub(subject): a quem o token se refere, em geral o identificador do usuário.aud(audience): para quem o token se destina, ou seja, qual API deve aceitá-lo.exp(expiration): instante em que o token expira, como timestamp Unix.nbf(not before): instante antes do qual o token ainda não vale.iat(issued at): instante de emissão.jti: identificador único do token.
Os campos de tempo são timestamps Unix em segundos. No exemplo acima,
exp = 1893456000 é 1º de janeiro de 2030, 00:00 UTC. Para
converter esses números em datas, use o
conversor de timestamp,
e veja como funciona esse formato em
Timestamp Unix e o problema do ano 2038.
Decodificar não é validar
Esse é o ponto central. O payload de um JWT comum não é criptografado: é só Base64URL, que qualquer um decodifica. A assinatura não esconde nada; ela apenas permite a quem tem a chave verificar que o token não foi alterado. Portanto:
- Um decodificador (como o do site) não verifica a assinatura. Mostrar o conteúdo não significa que o token é legítimo.
- Um servidor só pode confiar nas claims depois de verificar a assinatura e de checar
exp,audeiss. - Nada sigiloso deve ir no payload: senhas, documentos completos e dados sensíveis ficam legíveis para quem tiver o token.
Algoritmos: simétrico e assimétrico
- HS256 (HMAC com SHA-256): a mesma chave secreta assina e verifica. Simples, mas todo serviço que precisa verificar tokens passa a conhecer o segredo capaz de emitir tokens. O cálculo usa uma função de hash, assunto de MD5, SHA-1 e SHA-256.
- RS256 e ES256: assinatura com chave privada (só o emissor a tem) e verificação com chave pública (qualquer serviço pode ter). São mais adequados quando vários sistemas precisam validar tokens sem poder emiti-los.
Erros de segurança mais comuns
-
Aceitar
alg: none. O padrão prevê tokens "sem assinatura". Uma implementação que respeita o algoritmo declarado no header aceitaria um token forjado por qualquer pessoa. A validação deve ter uma lista fixa de algoritmos aceitos, definida no servidor. - Confusão de algoritmo. Se o servidor espera RS256 mas aceita o que vem no header, um atacante pode declarar HS256 e assinar usando a chave pública (que é pública) como se fosse o segredo. A defesa é a mesma: fixar o algoritmo esperado.
-
Não checar
exp,audeiss. Um token válido, mas expirado ou destinado a outra API, não deveria ser aceito. - Segredos fracos em HS256. Uma chave curta ou previsível pode ser descoberta por força bruta a partir de um token capturado. Use chaves longas e aleatórias.
- Tokens com validade longa. Como o servidor não consulta nenhum banco, é difícil revogar um JWT antes do vencimento. A prática comum é usar tokens de acesso de curta duração, renovados por um token de atualização controlado pelo servidor.
Onde guardar o token no navegador
Não existe opção sem custo. Guardar em localStorage deixa o
token exposto a qualquer script injetado (XSS). Guardar em cookie
HttpOnly protege contra leitura por JavaScript, mas exige
cuidado com CSRF (uso de SameSite e tokens anti-CSRF). Em
ambos os casos, o tráfego deve ser sempre por HTTPS.