Como decodificar e inspecionar tokens JWT
Os JSON Web Tokens (JWT) são a forma mais comum de gerir autenticação em aplicações web modernas. Quando algo corre mal com a autenticação, um utilizador é desligado inesperadamente, as permissões estão erradas, uma API devolve 401, descodificar o JWT é geralmente o primeiro passo de depuração. Compreender as três partes de um JWT, as claims padrão, os algoritmos com que pode ser assinado e as armadilhas comuns transforma a depuração de auth de vudu numa verificação rotineira.
Uma breve história do JWT
Os JWT foram padronizados na RFC 7519 em maio de 2015, depois de vários anos de iteração em rascunho na IETF. O formato baseou-se em desenhos anteriores de tokens compactos (assertions SAML, simples cookies opacos), mas acrescentou duas coisas que lhes faltavam: uma forma JSON estrita legível em qualquer linguagem, e uma codificação base64url-safe que sobrevivia a parâmetros de URL, cabeçalhos HTTP e campos de formulário sem novo escape. As especificações complementares, JWS (RFC 7515) para assinaturas, JWE (RFC 7516) para cifragem, e JWA (RFC 7518) para nomes de algoritmo, formam em conjunto a família JOSE (JavaScript Object Signing and Encryption).
O OAuth 2.0 e o OpenID Connect adotaram JWT como formato de token por defeito pouco depois, razão pela qual quase todos os fornecedores de auth modernos (Auth0, Okta, Cognito, Keycloak, Firebase, Supabase, Clerk) os emitem hoje. A combinação de tokens autocontidos e backends sem estado mostrou-se um encaixe muito natural para microsserviços e gateways de API. A desvantagem é que os JWT são notoriamente fáceis de usar mal, e a última década produziu um fluxo constante de CVEs em bibliotecas que não validaram o algoritmo com cuidado.
O que está dentro de um JWT
Um JWT tem três partes, separadas por pontos:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
Cabeçalho: contém o algoritmo (HS256, RS256, etc.) e o tipo de token.
{"alg": "HS256", "typ": "JWT"}
Payload: contém claims (afirmações de dados) sobre o utilizador e o token.
{"sub": "1234567890", "name": "Alice", "exp": 1700000000}
Assinatura, um hash criptográfico que verifica que o token não foi adulterado. Não consegue lê-lo sem a chave de assinatura.
Cada secção está codificada em base64url, o que significa que usa - e _ em vez de + e / e omite o padding = final. Base64url não é cifragem; colar apenas o segmento do meio em qualquer descodificador revela o payload. É por design: os segmentos do meio são desenhados para serem legíveis pelos serviços ao longo do caminho, a assinatura é a única parte que prova autenticidade.
Claims JWT comuns
As claims padrão estão registadas na IANA e definidas na RFC 7519. A maioria é opcional, mas as abaixo estão quase sempre presentes.
| Claim | Nome completo | O que contém |
|---|---|---|
sub | Subject (sujeito) | ID ou identificador de utilizador |
exp | Expiration (expiração) | Timestamp Unix de quando o token expira |
iat | Issued At (emitido em) | Timestamp Unix de criação do token |
iss | Issuer (emissor) | Quem criou o token (o seu servidor de auth) |
aud | Audience (público) | A quem o token se destina |
nbf | Not Before (não antes) | O token não é válido antes deste momento |
jti | JWT ID | Identificador único do token |
azp | Authorized Party | A parte a quem o token foi emitido (OIDC) |
scope / scp | Scopes OAuth | Permissões concedidas, frequentemente separadas por espaço |
email | Identificador OIDC padrão de utilizador | |
name | Nome | Nome de exibição (OIDC) |
nonce | Nonce | Valor OIDC de proteção contra repetição |
kid (cabeçalho) | Key ID | Qual chave de assinatura foi usada (para lookup JWKS) |
Para além do conjunto padrão, as aplicações acrescentam as suas próprias claims personalizadas (roles, tenant_id, feature_flags, permissions). Os nomes de claims personalizadas não têm namespace por defeito, o que significa que dois serviços diferentes podem usar o mesmo nome para significar coisas diferentes; a convenção OIDC de prefixar com um URI (https://myapp.com/roles) evita a colisão.
Como descodificar um JWT
- Cole o seu token: introduza o JWT completo (formato header.payload.signature) no descodificador. Descodificadores baseados em browser processam-no localmente, o token nunca sai da página.
- Veja as secções descodificadas: a ferramenta mostra o cabeçalho (algoritmo), payload (claims) e assinatura como JSON formatado, com timestamps mostrados tanto como inteiros Unix como datas legíveis.
- Verifique as claims: examine o tempo de expiração, emissor, sujeito, público, e quaisquer claims personalizadas que conduzam a sua lógica de autorização.
- Compare com expectativas: cruze o emissor com o fornecedor de auth que configurou, o público com a API a que o token é enviado, e qualquer claim de role/scope com as permissões que o utilizador deveria ter.
- Teste de viagem no tempo: passe o rato sobre
iat,nbfeexppara ver se o token é atualmente válido, vai expirar em breve, ou foi emitido há tanto tempo que a sua tolerância de desvio de relógio já não o cobre.
Algoritmos de assinatura
Nem todos os JWT usam a mesma cripto. O cabeçalho alg diz-lhe a que família pertence a assinatura, e cada uma tem propriedades de segurança muito diferentes.
| Algoritmo | Família | Tipo de chave | Quando escolher |
|---|---|---|---|
HS256 | HMAC | Segredo partilhado | Apps de um serviço; nunca partilhe o segredo entre equipas |
HS384 / HS512 | HMAC | Segredo partilhado | O mesmo que HS256 com digests maiores |
RS256 | RSA | Par de chaves pública/privada | O mais comum para OIDC; verificadores só precisam da chave pública |
RS384 / RS512 | RSA | Par de chaves | O mesmo que RS256 com chaves maiores |
PS256 / PS384 / PS512 | RSA-PSS | Par de chaves | RSA moderno, recomendado sobre RS para novos deploys |
ES256 / ES384 / ES512 | ECDSA | Par de chaves de curva elíptica | Chaves menores que RSA, verificação mais rápida |
EdDSA | Ed25519 | Par de chaves curva de Edwards | O mais recente, pequeno, rápido; ainda não universal |
none | Nenhum | Nenhuma | Proibido em produção; algumas bibliotecas antigas ainda aceitam |
Algoritmos assimétricos (RS*, PS*, ES*, EdDSA) permitem que qualquer serviço verifique um token com apenas uma chave pública, daí dominarem o OIDC. Simétrico (HS*) está bem dentro de uma única aplicação mas torna-se um pesadelo para rodar ou distribuir entre vários consumidores.
Depurar com JWT
Token expirado? Verifique a claim exp. Converta o timestamp Unix para uma data legível. Se está no passado, o token expirou e precisa de ser renovado. A maioria das bibliotecas JWT rejeita tokens expirados por defeito; se a sua app os aceita, é um bug de segurança.
Permissões erradas? Procure claims de role ou scope no payload. Variam por implementação, mas parecem-se frequentemente com "role": "admin" ou "scope": "read write profile".
Problemas de identidade de utilizador? A claim sub identifica o utilizador. Verifique que corresponde ao ID de utilizador esperado. Note que alguns fornecedores usam GUIDs opacos enquanto outros usam endereços de e-mail; o descodificador mostra-lhe exatamente o que está lá.
Token não aceite? Verifique a claim aud (público). Se a API espera um valor de público específico e o token tem outro, será rejeitado. Disparidades de público são um sintoma comum de encaminhar um token para o serviço errado.
Erros 401 após deploy? Verifique a claim iss (emissor). Um novo tenant de fornecedor de auth ou uma chave de assinatura trocada altera o URL do emissor; se o seu verificador ainda confia no antigo, todos os tokens parecem inválidos.
Problemas de desvio de relógio? Se iat está ligeiramente no futuro ou exp ligeiramente no passado, o relógio do servidor pode estar a derivar. A maioria das bibliotecas JWT permite alguns segundos de margem; se não, um relógio sincronizado por NTP resolve.
Armadilhas comuns
- Confiar no cabeçalho
algsem lista branca, a vulnerabilidade JWT clássica era um servidor aceitar o algoritmo que o token dizia usar. Um token comalg: none(sem assinatura) oualg: HS256(assinado com a sua chave pública como segredo) podia forjar qualquer payload. Fixe o verificador ao algoritmo exato esperado. - Pôr segredos no payload, o payload é codificado em base64url, não cifrado. Qualquer pessoa com o token consegue lê-lo. Nunca inclua passwords, chaves API, ou qualquer coisa que não colocaria numa query string.
- Tokens de longa duração sem revogação, um JWT de 30 dias não pode ser revogado sem uma lista negra de tokens ou armazenamento de sessão. Mantenha os tokens de acesso curtos (5-60 minutos) e use um fluxo de token de refresh para sessões mais longas.
- Esquecer o desvio de relógio, servidores em fusos diferentes ou com relógios à deriva rejeitam tokens que deveriam ser válidos. Permita 30-60 segundos de margem em
expenbf. - Confiar no emissor sem verificação, a claim
issfaz parte do payload que qualquer um pode escrever. Verificar que o seu valor corresponde ao emissor configurado é obrigatório; pô-lo em lista branca impede um atacante de cunhar tokens que afirmem vir do seu fornecedor. - Reutilizar segredos HS256 entre ambientes, o mesmo segredo em dev e prod significa que um token de dev vazado funciona em produção. Use chaves por ambiente, idealmente buscadas a um gestor de segredos.
- Guardar tokens em localStorage, localStorage é legível por qualquer JavaScript, por isso um único bug de XSS exfiltra os tokens de todos os utilizadores. Cookies HttpOnly com SameSite=Lax (ou Strict) são o default mais seguro.
- Registar tokens inteiros, logs de aplicação que capturam JWT completos vazam os tokens para qualquer pessoa com acesso aos logs. Trunque aos primeiros 10 caracteres ou registe apenas o
jti. - Ignorar a rotação
kid, quando um fornecedor roda as chaves de assinatura, os novos tokens têm um novo cabeçalhokid. Um verificador que cacheia o JWKS para sempre vai começar a rejeitar tokens válidos. Re-busque o JWKS em falha de key-id. - Misturar JWT com sessões de forma inconsistente, alguns endpoints atrás de JWT, outros atrás de sessões cookie, leva a bugs em que um utilizador autenticado aparece como não autenticado numa rota. Escolha um modelo por serviço.
Alternativas ao JWT
JWT é dominante mas não é a única opção. Cada alternativa troca propriedades diferentes.
| Mecanismo | Força | Fraqueza |
|---|---|---|
| JWT (JWS) | Autocontido, fácil entre serviços | Não pode ser revogado sem estado extra |
| Tokens opacos + introspeção | Fácil de revogar, esconde claims | Cada pedido toca no servidor de auth |
| Sessões do lado do servidor | Modelo mais simples, revogação instantânea | Difícil escalar entre serviços |
| PASETO | Substituto mais seguro do JWT (sem confusão alg) | Ecossistema mais pequeno |
| Macaroons | Atenuação embutida (direitos delegados) | Suporte de bibliotecas limitado |
| OAuth 2.0 + tokens de acesso JWT | Padrão da indústria para APIs | Especificação grande, fácil de mal implementar |
| Tokens ID OIDC | Identidade de utilizador padrão + JWT | Frequentemente confundidos com tokens de acesso |
| Certificados de cliente mTLS | Autenticação mais forte na camada de transporte | Overhead de gestão de certificados |
Para a maioria das equipas, a escolha é entre JWT e tokens opacos. JWT vence quando a verificação tem de ser barata e offline; os tokens opacos vencem quando a revogação tem de ser instantânea.
Privacidade e o descodificador
O descodificador JWT corre inteiramente no seu browser. O token que cola é dividido, descodificado em base64url, e o JSON é parsado e bem-formatado sem qualquer pedido de rede. Não há registo dos tokens descodificados, nem analítica sobre as claims que contêm, nem maneira de ninguém reconstruir para quem estava a depurar. Os JWT contêm frequentemente identificadores de utilizador, endereços de e-mail, nomes de roles internos e IDs de tenant, exatamente o tipo de metadados que não quer enviar para o servidor de um estranho. Descodificar do lado do cliente mantém essa informação na sua máquina, o que é o default correto para qualquer tarefa de depuração que toque autenticação.
Perguntas frequentes
Posso verificar a assinatura de um JWT com um decodificador?
Não. A verificação de assinatura requer o segredo de assinatura ou chave pública, que fica no seu servidor. Um decodificador mostra o que há dentro do token, mas a verificação criptográfica deve acontecer no seu backend. Nunca confie em um JWT não verificado em produção.
É seguro colar um JWT em uma ferramenta online?
Sim, quando a ferramenta roda no seu navegador. Decodificadores baseados em navegador processam o token localmente, nada é enviado a um servidor. Evite ferramentas que fazem requisições de rede com seu token.
O que é a claim exp?
A claim exp (expiration) é um timestamp Unix indicando quando o token expira. Depois desse momento, o token deve ser rejeitado. Sempre verifique essa claim ao depurar problemas de autenticação.
JWTs podem ser criptografados?
JWTs padrão (JWS) são assinados mas não criptografados, qualquer um pode decodificar o payload. Tokens JWE (JSON Web Encryption) são criptografados, mas são menos comuns. Nunca coloque dados sensíveis (senhas, segredos) no payload de um JWT padrão.
What is the alg none vulnerability?
Early JWT libraries accepted tokens with an alg header set to "none", meaning the signature could be omitted entirely. An attacker who set this header could forge any payload. Modern libraries reject "none" by default, but legacy systems may still be exposed; always allow-list the expected algorithm rather than trusting the header.
How should I store a JWT on the client?
HttpOnly secure cookies with SameSite=Lax (or Strict) are the safest default; they cannot be read by JavaScript, which mitigates XSS token theft. localStorage is convenient but vulnerable to any XSS bug. Never store long-lived JWTs alongside untrusted scripts.