JSON Web Tokens são populares porque uma API consegue verificar um sem chamar ninguém: confere a assinatura, lê as claims, pronto. Essa mesma propriedade é a origem da maioria dos problemas com JWT. Um token que você não consegue consultar é um token que você não consegue recolher, e os times descobrem isso na primeira vez que um notebook é roubado ou alguém sai da empresa.
Este post percorre um fluxo completo de autenticação JWT com tokens de acesso, refresh tokens e um diagrama de sequência, e depois cobre as verificações de que toda API precisa, onde guardar tokens no navegador, a rotação de refresh tokens, a rotação de chaves e as opções realistas de revogação.
O que é um JWT, e o que ele não é
Um JWT (RFC 7519) é uma string compacta e segura para URL, com três partes em base64url separadas por pontos: um cabeçalho, um payload de claims e uma assinatura. Decodificado, um token de acesso fica assim:
{ "alg": "RS256", "kid": "2026-10", "typ": "at+jwt" }
{
"iss": "https://auth.example.com",
"sub": "user_8412",
"aud": "orders-api",
"scope": "orders:read orders:write",
"iat": 1793606400,
"exp": 1793607300,
"jti": "a3f1c9e2"
}
Três pontos causam a maior parte dos mal-entendidos:
- Um JWT assinado não é criptografado. Qualquer pessoa com o token consegue ler o payload. Não coloque nele segredos nem dados pessoais que o portador não deveria ver.
- JWT é um formato, não um protocolo. Como um cliente obtém um token é definido em outro lugar, normalmente pelo OAuth 2.0 e pelo OpenID Connect. O diagrama de fluxo do OAuth 2.0 cobre essa parte.
- Nem todo token de um fluxo é um JWT. Tokens de acesso muitas vezes são; refresh tokens muitas vezes são strings aleatórias opacas que só o servidor de autorização consegue consultar.
As claims registradas que você vai encontrar em todo token:
| Claim | Significado | Quem verifica |
|---|---|---|
iss |
Issuer, o servidor de autorização que assinou o token | API |
sub |
Subject, normalmente o ID do usuário | API, para a autorização |
aud |
Audience, a API à qual o token se destina | API |
exp, nbf, iat |
Expiração, início da validade e horário de emissão, em segundos desde a epoch | API |
jti |
ID único do token, útil para listas de bloqueio | API ou gateway |
O fluxo, passo a passo
- Login. O usuário se autentica no servidor de autorização, normalmente pelo fluxo authorization code do OAuth com PKCE.
- Resposta com tokens. O servidor de autorização devolve um token de acesso de curta duração (um JWT, digamos de 15 minutos) e um refresh token de longa duração.
- Chamada à API. O app envia
Authorization: Bearer <access token>em cada requisição. - Validação local. A API verifica a assinatura com a chave pública do servidor de autorização, que ela buscou uma vez no endpoint JWKS e guardou em cache, e depois confere as claims. Nenhuma chamada ao servidor de autorização por requisição.
- Expiração. Depois de 15 minutos, a API responde
401 Unauthorized. - Refresh. O app envia o refresh token para o endpoint de tokens do servidor de autorização e recebe um novo token de acesso e, com rotação, um novo refresh token.
- Nova tentativa. O app repete a requisição que falhou com o novo token de acesso.
Os passos 3 e 4 são o motivo de os times escolherem JWT: milhares de requisições à API e nenhum repositório de sessões compartilhado. Os passos 5 e 6 são o preço, e é ali que fica a maior parte do trabalho de segurança.
Validar um token: a checklist
Toda API que aceita JWTs deve fazer as mesmas verificações, nesta ordem:
- Permita só os algoritmos que você espera. Nunca aceite o cabeçalho
algsem questionar. O RFC 8725 descreve os dois ataques clássicos:algdefinido comonone, e um token RS256 reescrito como HS256 para que uma biblioteca ingênua o verifique usando a chave pública como segredo HMAC. - Escolha a chave pelo
kidno JWKS em cache, e busque o conjunto de novo quando aparecer umkiddesconhecido, no máximo uma vez por intervalo curto. - Verifique a assinatura.
- Verifique
isseaud. Um token emitido para outra API precisa falhar aqui. - Verifique
expenbfcom uma pequena tolerância de relógio, como 60 segundos. - Verifique o tipo do token. Tokens de acesso do RFC 9068 carregam
typ: at+jwt, o que impede que um ID token seja reaproveitado como token de acesso. - Depois autorize: scopes, papéis e propriedade do recurso.
Com a biblioteca jose no Node.js, quase toda a lista é uma única chamada:
import { createRemoteJWKSet, jwtVerify } from 'jose'
const JWKS = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'))
export async function verifyAccessToken(token: string) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: 'https://auth.example.com',
audience: 'orders-api',
algorithms: ['RS256'],
typ: 'at+jwt',
clockTolerance: 60,
})
return payload // scopes and sub are checked by the caller
}
Quanto tempo um token de acesso deve durar?
A duração é a janela em que um token roubado funciona e não pode ser recolhido. Mais curta significa mais segura e mais tráfego de refresh. Para um usuário ativo durante uma jornada de oito horas:
| Duração do token de acesso | Exposição se for roubado | Refreshes por usuário por dia |
|---|---|---|
| 5 minutos | 5 minutos | 96 |
| 15 minutos | 15 minutos | 32 |
| 1 hora | 1 hora | 8 |
| 24 horas | 24 horas | 1 |
A maioria dos times fica entre 5 e 15 minutos para tokens de acesso. Um token de acesso que dura um dia é um cookie de sessão que você não consegue revogar.
Rotação de refresh tokens
Um refresh token vive dias ou semanas, então vazar um é pior do que vazar um token de acesso. O RFC 9700, a melhor prática atual de segurança do OAuth, exige uma de duas proteções para refresh tokens mantidos por clientes públicos, como apps de navegador e apps mobile:
- Refresh tokens vinculados ao remetente, presos a uma chave que o cliente possui, com TLS mútuo ou DPoP.
- Rotação: cada refresh devolve um novo refresh token e invalida o anterior, enquanto o servidor de autorização guarda a cadeia. Se um atacante e o cliente legítimo usam o mesmo token, um deles apresenta um token invalidado, e o servidor revoga o refresh token ativo, o que encerra a cadeia para os dois.
A rotação transforma um refresh token roubado, que seria um problema de semanas, em um problema que acaba no próximo refresh do cliente legítimo. O custo: o usuário precisa entrar de novo, e o seu endpoint de tokens precisa lidar com duas abas fazendo refresh ao mesmo tempo sem disparar a detecção de reutilização, normalmente com um curto período de tolerância.
Onde guardar tokens no navegador
| Armazenamento | Legível por JavaScript | Risco principal | Observações |
|---|---|---|---|
localStorage |
Sim | Qualquer XSS pode roubar os tokens | Sobrevive a recarregamentos; evite para refresh tokens |
| Só em memória | Sim, enquanto a página existir | XSS durante a sessão | Some ao recarregar; faça refresh na inicialização |
Cookie HttpOnly |
Não | CSRF | Use Secure, SameSite e tokens CSRF nas requisições que mudam estado |
| Backend for frontend | Não; os tokens ficam no servidor | Gestão de sessões no servidor | O navegador só guarda um cookie de sessão |
O rascunho do IETF sobre OAuth para aplicações baseadas em navegador descreve o padrão backend for frontend como a opção que mantém os tokens totalmente longe do JavaScript. Se você mantiver tokens no navegador, deixe o token de acesso em memória e o refresh token em um cookie HttpOnly restrito ao endpoint de tokens.
Revogação: o que funciona de verdade
Você não consegue “desassinar” um JWT. Só consegue fazer a API parar de aceitá-lo, e cada opção devolve um pouco do estado que você evitava:
| Estratégia | Como funciona | Custo |
|---|---|---|
| Expiração curta | Os tokens morrem sozinhos em minutos | Nenhum; a janela de exposição continua |
| Revogar o refresh token | O próximo refresh falha, então o acesso termina na próxima expiração | Uma consulta no endpoint de tokens, que já tem estado |
Lista de bloqueio por jti |
Gateways rejeitam os IDs listados até o exp deles |
Um cache compartilhado, consultado em toda requisição |
| Corte por usuário | Rejeitar tokens emitidos antes de um timestamp guardado para aquele usuário | Uma consulta por usuário, cacheável |
| Introspecção | A API pergunta ao servidor de autorização sobre cada token (RFC 7662) | Uma chamada de rede; use só em endpoints de alto risco |
Uma combinação prática: tokens de acesso de 15 minutos ou menos, refresh tokens rotativos revogados no logout e na troca de senha, e um corte por usuário para o caso raro de “sair de todos os lugares agora”.
Rotacionar as chaves de assinatura
Chaves vazam e algoritmos envelhecem, então planeje a rotação desde o primeiro dia. Publique a nova chave pública no JWKS mais ou menos um dia antes de assinar com ela, para que todo cache de API já a tenha. Mude a assinatura para o novo kid. Mantenha a chave antiga no JWKS até expirar o último token assinado com ela e depois remova-a. Uma API que busca o conjunto de novo quando vê um kid desconhecido sobrevive a uma rotação de emergência sem reiniciar.
O diagrama do fluxo JWT
Conteúdo do diagrama
- Browser app
- Auth server
- Orders API
- access token expired
- 1. Sign in (OAuth code flow + PKCE)
- 2. Access token (JWT, 15 min) + refresh token
- 3. GET /orders, Authorization: Bearer JWT
- GET /.well-known/jwks.json (once, then cached)
- public keys
- 4. 200 OK
- 5. GET /orders (expired JWT)
- 6. 401 Unauthorized
- 7. POST /token (refresh token)
- 8. New token pair, old refresh token invalidated
- 9. Retry GET /orders with new JWT
- 10. 200 OK
Como ler:
- Três linhas de vida. O app de navegador à esquerda, o servidor de autorização no meio e a API de pedidos à direita. O tempo corre para baixo.
- O login e a resposta com tokens ficam no topo: o app recebe um token de acesso e um refresh token.
- O caminho feliz: a requisição bearer para a API e a resposta. A API valida o token com as chaves JWKS em cache e nunca chama o servidor de autorização.
- O fragmento opt cobre a expiração:
401, refresh, um novo par de tokens com o refresh token antigo invalidado e a requisição repetida. - O bloco Markdown mostra o token decodificado e as verificações de validação.
Desenhar fluxos de autenticação que os revisores consigam conferir
- Nomeie cada token em cada seta, com tipo e duração: “token de acesso (JWT, 15 min)”. A ambiguidade sobre qual token vai para onde esconde bugs reais.
- Mostre onde a validação acontece. Coloque uma nota na linha de vida que verifica a assinatura e diga de onde vêm as chaves dela.
- Desenhe os caminhos de falha. Expiração, reutilização de refresh token e logout cabem em fragmentos; a maioria dos incidentes começa ali.
- Deixe os segredos fora da figura. Use valores fictícios; diagramas são compartilhados mais do que você imagina.
O exemplo de diagrama de sequência cobre linhas de vida e fragmentos em detalhe, e o diagrama de arquitetura zero trust mostra onde as verificações de token ficam em um modelo de acesso mais amplo.
Monte no Archboard
- Entre com Google ou GitHub e crie um diagrama.
- Adicione três nós Linha de vida do pack Sequência: Browser app, Auth server e Orders API.
- Desenhe as mensagens como arestas entre as linhas de vida, de cima para baixo: login, resposta com tokens, a requisição bearer e a resposta dela. Rotule cada uma com o token que ela carrega.
- Adicione um Fragmento combinado do pack Sequência com o operador
optem volta do caminho de expiração:401, pedido de refresh, novo par de tokens, nova tentativa. - Adicione um bloco Markdown do pack Básico com o token decodificado e a checklist de validação.
O exemplo usa 5 nós de diagrama e fica dentro do plano Free.
Perguntas frequentes
JWTs são criptografados? JWTs assinados não são. O payload é codificado em base64url e qualquer pessoa com o token consegue lê-lo. Existe o JSON Web Encryption para tokens criptografados, mas a maioria dos tokens de acesso é apenas assinada.
Devo guardar um JWT no localStorage? Evite para qualquer coisa de longa duração. Qualquer bug de cross-site scripting consegue ler o localStorage. Mantenha tokens de acesso em memória e refresh tokens em um cookie HttpOnly, ou mantenha os tokens em um backend for frontend.
Como faço logout de um usuário se os JWTs não têm estado? Revogue o refresh token para que nenhum token de acesso novo seja emitido, e mantenha os tokens de acesso curtos. Para efeito imediato, adicione um timestamp de corte por usuário ou uma lista de bloqueio por jti que a sua API verifique.
Qual é a diferença entre um ID token e um token de acesso? Um ID token diz ao cliente quem fez login e é destinado ao cliente. Um token de acesso é destinado a uma API. As APIs devem rejeitar ID tokens; as verificações de typ e aud deixam isso explícito.
Devo usar HS256 ou RS256? O HS256 usa um único segredo compartilhado, então todo serviço que verifica tokens também consegue emiti-los. O RS256 ou o ES256 permite que as APIs verifiquem com uma chave pública enquanto só o servidor de autorização guarda a chave privada, que é o padrão mais seguro quando há mais de um serviço.