Como funciona JWT: autenticacao, assinatura e seguranca
Como funciona JWT na pratica: as tres partes do token, o que a assinatura garante, HS256 vs RS256, fluxo de login, expiracao e armadilhas de seguranca.
Você abriu o DevTools de uma aplicação em produção, copiou o token que ela manda em cada request e colou num decoder. Três segundos depois está lendo o e-mail do usuário, o id, o role "admin" e a data de expiração, tudo em texto claro. Ninguém te deu permissão, ninguém precisou da chave. E isso é exatamente como JWT foi projetado para funcionar. O problema não é o token estar legível: é a quantidade de gente que acha que ele está criptografado.
Este artigo explica como funciona JWT na prática: as três partes do token, o que a assinatura realmente garante, a diferença entre HS256 e RS256, o fluxo de login, o custo de ser stateless e os erros de segurança que ainda derrubam APIs hoje.
Anatomia: três partes separadas por ponto
Um JWT é uma string com três blocos Base64URL separados por ponto: header.payload.signature. Cada bloco é decodificável por qualquer pessoa, sem chave nenhuma. Base64URL não é criptografia, é codificação.
O header diz qual algoritmo assina o token e o tipo:
{
"alg": "HS256",
"typ": "JWT"
}
O payload carrega os claims, que são afirmações sobre o usuário e sobre o próprio token:
{
"sub": "1234567890",
"name": "Ana Souza",
"role": "admin",
"iat": 1721827200,
"exp": 1721830800
}
sub é o subject (quem é o usuário), iat é o momento de emissão, exp é quando o token expira. A terceira parte, a signature, é o resultado de assinar base64url(header) + "." + base64url(payload) com uma chave. Se você decodificar um token de uma sessão sua agora, vai ver header e payload em texto puro. Dá para inspecionar um token e ver os claims sem mandar nada pro servidor, o que é ótimo para debug e péssimo se você colocou dado sensível ali achando que estava escondido.
O que a assinatura garante (e o que não garante)
Aqui mora o mal-entendido mais comum. A assinatura garante integridade e autenticidade: se alguém mudar um único caractere do payload, a assinatura não bate mais e o servidor rejeita o token. E garante que quem emitiu tinha a chave. Só isso.
A assinatura não garante confidencialidade. O payload não é secreto. Qualquer um que tenha o token lê tudo. A regra prática: nunca coloque senha, número de cartão, CPF ou qualquer segredo no payload de um JWT. Coloque um id e busque o resto no servidor. Se você precisa mesmo de payload ilegível, aí entra JWE (JSON Web Encryption), que é outro padrão e raramente o que as pessoas querem quando dizem "JWT".
HS256 vs RS256: segredo compartilhado ou par de chaves
O alg do header decide como o token é assinado e verificado, e a escolha tem consequência arquitetural.
HS256 é simétrico: usa um único secret compartilhado tanto para assinar quanto para verificar. Simples, rápido, e funciona bem quando o mesmo serviço emite e valida os tokens. O problema aparece quando você tem vários serviços que precisam validar: todos precisam do secret, e qualquer um deles, se comprometido, pode forjar tokens válidos.
RS256 é assimétrico: usa uma chave privada para assinar e a chave pública correspondente para verificar. O servidor de auth guarda a privada; qualquer microserviço pode validar com a pública sem nunca poder forjar um token. Para sistemas distribuídos, federação ou quando terceiros precisam validar seus tokens, RS256 (ou ES256) é a escolha sensata. Comece com HS256 se for um monolito; vá para RS256 quando a topologia exigir.
O fluxo de autenticação na prática
O ciclo padrão tem quatro passos. O usuário faz login com credenciais. O servidor valida, monta o payload com os claims e a expiração, assina e devolve o token. O cliente guarda esse token e, em cada request subsequente, manda no header:
GET /api/orders HTTP/1.1
Host: api.exemplo.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
O servidor recebe, verifica a assinatura, confere o exp, lê os claims e decide se autoriza. Repare no que ele não faz: não consulta um banco de sessões. Toda a informação necessária está dentro do token. Esse é o ponto inteiro de JWT e também a origem dos seus problemas. Se você está projetando a API que recebe esse Bearer, vale revisar os princípios de design de uma REST API para não colar autenticação stateless num desenho de rotas inconsistente.
Stateless tem um custo: revogação
Como o servidor não guarda estado de sessão, ele não tem um botão de "deslogar agora". Um token válido é válido até expirar, ponto. Usuário teve a conta comprometida e você quer matar a sessão na hora? Com JWT puro você não consegue, porque o servidor nem sabe que aquele token existe.
As saídas todas reintroduzem algum estado: manter uma blocklist de tokens revogados (e aí você voltou a consultar um store em cada request), usar expiração curta para limitar a janela, ou versionar a credencial e checar uma flag. Nenhuma é grátis. Quem promete "auth sem estado" geralmente não chegou no requisito de revogação ainda.
Expiração e refresh tokens
A defesa prática contra um token vazado é o exp curto. Um access token de 5 a 15 minutos limita o estrago. Mas ninguém quer logar de novo a cada 15 minutos, e aí entra o refresh token: um segundo token, de vida longa, guardado com mais cuidado, cuja única função é pedir novos access tokens.
O par funciona assim: access token curto vai em cada request; quando expira, o cliente usa o refresh token para obter um novo access token sem reautenticar. O refresh token, por ser longo, é o alvo valioso, e por isso ele merece o armazenamento mais seguro que você tiver e idealmente rotação a cada uso (one-time use, detectando reuso como sinal de roubo).
Onde guardar o token (opinião)
Existem duas opções práticas no browser e elas não são equivalentes.
localStorage é conveniente e é a escolha errada para apps sensíveis. Qualquer XSS no seu site lê o localStorage inteiro e exfiltra o token. Uma única dependência comprometida no seu bundle e seus tokens vazam.
Cookie httpOnly + Secure + SameSite não é acessível por JavaScript, então um XSS não consegue ler o token diretamente. O custo é ter que lidar com CSRF (mitigado por SameSite=Strict/Lax e tokens anti-CSRF quando preciso). Para qualquer coisa que mexa com dado de usuário de verdade, prefira cookie httpOnly. localStorage só para tokens de baixo valor onde o XSS já te derrubaria de qualquer jeito.
As armadilhas clássicas de segurança
Duas vulnerabilidades vivem até hoje porque vieram do próprio design.
alg: none. A spec previu um token "sem assinatura" com alg igual a none. Uma biblioteca que respeita esse valor cegamente aceita um token onde o atacante removeu a assinatura e editou o payload à vontade. Mitigação: sua verificação precisa exigir explicitamente o algoritmo esperado e rejeitar none.
Algorithm confusion (RS256 -> HS256). Se o servidor usa RS256 mas a biblioteca decide o algoritmo pelo header do token, um atacante troca o alg para HS256 e assina usando a chave pública (que é, afinal, pública) como se fosse o secret HMAC. Bibliotecas mal configuradas verificam com sucesso. Mitigação, de novo: fixe o algoritmo aceito no servidor; nunca confie no alg que veio dentro do token.
A lição dos dois casos é a mesma: nunca deixe o token escolher como ele próprio será verificado.
Perguntas frequentes
JWT é criptografado?
Não por padrão. Um JWT comum (JWS) é apenas assinado e codificado em Base64URL, o que significa que qualquer pessoa com o token lê o payload inteiro. A assinatura protege contra adulteração, não contra leitura. Se você precisa de payload ilegível, o padrão é JWE, que é separado e bem menos comum.
Posso revogar um JWT antes de expirar?
Não com JWT puro stateless. O servidor não mantém registro do token, então não tem como invalidá-lo sob demanda. As soluções (blocklist, expiração curta, versionamento de credencial) sempre reintroduzem algum estado no servidor. Por isso access tokens de vida curta combinados com refresh tokens são a abordagem padrão.
Qual a diferença entre HS256 e RS256?
HS256 é simétrico: o mesmo secret assina e verifica, ideal para um único serviço. RS256 é assimétrico: a chave privada assina e a pública verifica, permitindo que múltiplos serviços validem sem poder forjar tokens. Para sistemas distribuídos, RS256 (ou ES256) é a escolha mais segura.
Devo guardar o JWT no localStorage?
Para apps sensíveis, não. localStorage é legível por qualquer JavaScript, então um XSS rouba o token na hora. Prefira um cookie httpOnly, Secure e SameSite, que o JavaScript não consegue ler, e trate o risco de CSRF separadamente.
Para levar
JWT é um envelope assinado e legível, não um cofre. A assinatura prova de quem veio e que não foi adulterado; ela não esconde nada. Trate o payload como público, nunca o use no localStorage para dados sensíveis, prenda o algoritmo de verificação no servidor para fechar alg: none e algorithm confusion, e aceite que stateless significa abrir mão da revogação instantânea, compensada com exp curto e refresh tokens. Decodifique seus próprios tokens de vez em quando: o que você vê ali, todo mundo também vê.
- 01 O que é software open source Open source não significa gratuito. Entenda o que a licença realmente define, as diferenças entre MIT, GPL e Apache, e como projetos open source se sustentam financeiramente.
- 02 PostgreSQL vs MySQL: diferenças práticas para quem precisa escolher Comparativo honesto entre PostgreSQL e MySQL: tipos nativos, JSONB vs JSON_EXTRACT, concorrência MVCC e extensões. Por que PostgreSQL como default faz sentido.