Um JSON Web Token é um token compacto e seguro para URLs que carrega claims JSON assinadas, permitindo verificar requisições sem sessões no servidor. Esse é o enquadramento da própria RFC 7519 — “um meio compacto e seguro para URLs de representar claims a serem transferidas entre duas partes” — e as duas ideias contidas nele carregam tudo o que vem a seguir: o token contém seus fatos, e uma assinatura torna esses fatos verificáveis por qualquer um que tenha a chave certa, sem banco de dados no caminho.
Principais pontos
| Pergunta | Resposta |
|---|---|
| O formato | header.payload.signature — três partes Base64Url unidas por pontos |
| O truque | Qualquer um pode decodificar; só quem tem a chave pode forjar |
| Não é criptografia | O payload é legível — assinado ≠ secreto (isso é trabalho do JWE) |
| A troca | Verificação stateless ↔ nenhuma revogação nativa até o exp |
| A disciplina | Fixe algoritmos · valide iss/aud/exp · vidas curtas + rotação de refresh tokens |
Um JWT, decodificado
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiJ1c3ItOGZrMiIsInJvbGUi… . dBjftJeZ4CVP…
header { "alg": "HS256", "typ": "JWT" }
payload { "sub": "usr-8fk2", "role": "editor",
"iss": "https://api.example.com", "aud": "example-web",
"iat": 1767024900, "exp": 1767025800 } ← vida de 15 minutos
signature HMACSHA256( base64url(header) + "." + base64url(payload), secret )
Qualquer um pode DECODIFICAR as duas primeiras partes — Base64Url é empacotamento, não criptografia.
Só quem tem a chave consegue FORJAR a terceira — e esse é o truque inteiro.
O contraste que vale ver em código — a alternativa stateful contra a qual toda decisão de JWT é medida:
// JavaScript / Node.js — Back4app JS SDK
// The stateful contrast: Back4app issues revocable session tokens
const user = await Parse.User.logIn('ada', 'correct-horse-battery');
const token = user.getSessionToken(); // r:abc123… — opaque, server-side state
// Every request presents it; the server can revoke it instantly:
await Parse.User.logOut(); // token invalid NOW — no waiting for an exp claim // Flutter / Dart — Back4app Flutter SDK
// The stateful contrast: Back4app issues revocable session tokens
final user = ParseUser('ada', 'correct-horse-battery', null);
await user.login();
final token = user.sessionToken; // r:abc123… — opaque, server-side state
// Every request presents it; the server can revoke it instantly:
await user.logout(); // token invalid NOW — no waiting for an exp claim // iOS / Swift — Back4app Swift SDK
// The stateful contrast: Back4app issues revocable session tokens
let user = try await User.login(username: "ada", password: "correct-horse-battery")
let token = user.sessionToken // r:abc123… — opaque, server-side state
// Every request presents it; the server can revoke it instantly:
try await User.logout() // token invalid NOW — no waiting for an exp claim // Android / Kotlin — Back4app Android SDK
// The stateful contrast: Back4app issues revocable session tokens
val user = ParseUser.logIn("ada", "correct-horse-battery")
val token = user.sessionToken // r:abc123… — opaque, server-side state
// Every request presents it; the server can revoke it instantly:
ParseUser.logOut() // token invalid NOW — no waiting for an exp claim Como a verificação funciona
Uma precisão que os explicadores pulam: “JWT” nomeia o formato das claims; o que todo mundo de fato passa adiante é um JWS (RFC 7515) — a serialização assinada — enquanto o JWE é o irmão criptografado, para payloads que precisam permanecer ilegíveis. E a verificação são dois trabalhos, não um: checar a assinatura e depois validar as claims — exp e nbf contra o relógio, iss contra sua lista de emissores confiáveis, aud contra o identificador deste serviço. Um token perfeitamente assinado que está expirado, veio do emissor errado ou foi cunhado para outra audiência é um ataque perfeitamente assinado.
Claims: o vocabulário do payload
| Claim | Nome | Trabalho do verificador |
|---|---|---|
iss | Emissor (issuer) | Este é um emissor em que eu confio? |
sub | Sujeito (subject) | De quem se trata — o ID estável do usuário |
aud | Audiência (audience) | Foi cunhado para mim? Rejeite tokens alheios |
exp | Expiração | Rejeite após este timestamp Unix |
iat / nbf | Emitido em / não antes de | Confira a sanidade da janela de validade |
jti | ID do token | Identificador único — o gancho de que uma denylist precisa |
| custom | Papéis, tenant, plano… | Semântica do app — mínimo possível, nunca segredos |
Mantenha os payloads enxutos por dois motivos: os tokens viajam em toda requisição como headers (cookies estouram perto de 4 KB, e cada claim é banda repetida), e tudo neles é legível para quem tiver o token.
JWT vs. session tokens
| JWT (stateless) | Session token (stateful) | |
|---|---|---|
| O token é | O próprio estado, assinado | Um ponteiro opaco para estado no servidor |
| Custo por requisição | Verificação de assinatura, sem consulta | Uma consulta ao session store |
| Revogação | Nenhuma até o exp — por design | Instantânea — apague a session |
| Auth entre serviços | Qualquer serviço com a chave verifica | Os serviços precisam compartilhar o session store |
| Logout significa | O cliente descarta; o token segue válido | O token morre de verdade |
| Melhor casa | APIs, microsserviços, verificação por terceiros | Apps de backend único, sessões sensíveis |
A verdade fora de moda com que vários guias de boas práticas agora abrem: para um app renderizado no servidor com um único backend, as sessions do framework são mais simples e mais controláveis — JWTs pagam o próprio custo quando os tokens precisam ser verificados entre serviços ou por partes que não deveriam telefonar para casa a cada requisição.
O problema da revogação, com honestidade
Ser stateless é ser incapaz de revogar — a mesma propriedade, descrita duas vezes. Um token assinado é válido até o exp não importa o que tenha acontecido desde então: logout, troca de senha, banimento da conta. Toda correção reintroduz estado, então escolha o sabor: uma denylist indexada por jti + iss (a construção recomendada pela OWASP) checada a cada requisição — estado pequeno, mas estado; versionamento de chaves, que revoga todo mundo de uma vez; ou a arquitetura padrão — access tokens de vida curta (5–15 minutos) mais refresh tokens revogáveis com rotação: cada refresh emite um novo refresh token e aposenta o anterior, então um token roubado morre no primeiro replay, e o reúso de um token aposentado sinaliza o roubo e mata a família inteira de tokens. A latência de revogação passa a ser igual à vida do access token — que é a verdadeira razão de essas vidas serem curtas.
Escolhendo o algoritmo — e os ataques contra a escolha
| HS256 (HMAC) | RS256 (RSA) | EdDSA / ES256 | |
|---|---|---|---|
| Chaves | Um secret compartilhado | A privada assina, a pública verifica | A privada assina, a pública verifica |
| Verificadores precisam | Do secret (que também forja!) | Só da chave pública | Só da chave pública |
| Serve para | Emissor = verificador, uma só parte | Multisserviço, terceiros | Idem, assinaturas menores e mais rápidas |
| Aresta afiada | Secrets curtos sofrem brute force offline a partir de um único token capturado — use ≥256 bits aleatórios | Tokens maiores, mais lento | Suporte de biblioteca mais recente |
A seção de ataques para a qual a RFC 8725 existe, em três frases. alg: "none" é um valor legal de header — bibliotecas que o honram aceitam tokens sem assinatura. O ataque de confusão: um verificador que deixa o header do token escolher o algoritmo pode receber um token “HS256” assinado com a chave RSA pública do servidor usada como secret do HMAC — um valor público usado como privado. Os dois morrem do mesmo jeito: o verificador fixa em configuração os algoritmos e as chaves que aceita e nunca confia no header para escolher.
Onde armazenar JWTs no navegador
| Local | XSS rouba? | CSRF envia? | Sobrevive ao refresh da página? | Veredito |
|---|---|---|---|---|
| localStorage | Sim | Não | Sim | Evite — legível por script |
| Cookie comum | Sim (legível por script) | Sim | Sim | O pior dos dois mundos |
| Cookie HttpOnly + Secure + SameSite | Não | Mitigado pelo SameSite | Sim | Bom — para o refresh token |
| Em memória | Só enquanto o script injetado vive | Não | Não | Bom — para o access token |
| Backend-for-frontend guarda os tokens | O navegador nunca os tem | Valem as regras de cookie | Sim | O mais forte para SPAs |
O modelo de ameaça, não o folclore: localStorage troca imunidade a CSRF por roubo via XSS, cookies trocam o inverso — e é por isso que o consenso divide o par: access token em memória, refresh token em um cookie endurecido.
Casos de uso comuns
- Autenticação de APIs — o bearer token por trás dos headers
Authorizationem APIs REST e GraphQL. - Identidade em microsserviços — um token emitido pelo gateway e verificado de forma independente por cada serviço, sem session store compartilhado.
- ID tokens do OpenID Connect — a asserção de identidade assinada em todo login social — sempre um JWT.
- Handoffs entre sistemas — links assinados, payloads de webhook, concessões de download: claims verificadas por uma parte que não pode te chamar de volta.
- Dicas de autorização stateless — papéis e IDs de tenant carregados em claims, com a verificação autoritativa ainda no servidor.
JWTs ou sessions? Matriz de decisão
| Sua situação | Escolha |
|---|---|
| Backend único, app renderizado no servidor | Sessions — mais simples, revogáveis na hora |
| API pública consumida por muitos serviços | JWTs, chaves assimétricas |
| Microsserviços atrás de um gateway | JWTs — verificação local, sem store compartilhado |
| Bloqueio instantâneo é requisito inegociável | Sessions, ou JWTs + denylist e exp curto |
| Terceiros precisam verificar suas asserções | JWTs — o jogo em casa do formato |
| App mobile contra um BaaS | O mecanismo de session da plataforma — ela já escolheu |
Limitações e trade-offs
- A irrevogabilidade é estrutural. Toda mitigação — denylists, vidas curtas, rotação — é uma devolução parcial do estado que você removeu; precifique isso antes de escolher stateless.
- Claims envelhecem. Os papéis são um snapshot da emissão; um admin rebaixado continua admin até expirar. Vidas curtas limitam a janela de defasagem.
- O payload é público. Trate-o como legível pelo usuário e por qualquer ladrão de token — identificadores sim, segredos e PII não.
- Defaults de biblioteca já queimaram muita gente. Fixar algoritmos, validar claims e checar o
typsão responsabilidades da sua configuração, não garantias da biblioteca. - O tamanho se acumula. Cada claim viaja em cada requisição; payloads generosos taxam a banda mobile e podem estourar os limites de cookie.
Tokens no Back4app
O Back4app é uma plataforma open-source de Backend as a Service (BaaS) que combina banco de dados gerenciado, APIs REST e GraphQL geradas automaticamente, autenticação, armazenamento de arquivos e funções serverless com Cloud Code. A escolha da própria plataforma ilustra a matriz de decisão: as sessões de cliente usam session tokens revogáveis, mantidos no servidor — as abas de código acima — então logout, trocas de senha e o encerramento de sessões pelo dashboard têm efeito imediato, sem janela de expiração para esperar. JWTs aparecem onde pertencem: os ID tokens OIDC dos provedores de identidade são verificados no servidor pelos adaptadores de auth durante o login social, e funções de Cloud Code podem cunhar ou verificar JWTs para handoffs com terceiros usando bibliotecas padrão — com o secret de assinatura na configuração do servidor, nunca enviado aos clientes. Stateful onde o controle importa, stateless nas bordas onde a verificação precisa viajar: a arquitetura que este artigo defende, já montada.
Perguntas frequentes
O que é um JWT em termos simples?
Uma "carteira de identidade" em JSON, assinada e codificada em Base64, que o servidor emite no login. O cliente a apresenta em cada requisição, e o servidor verifica a assinatura em vez de consultar uma sessão — o próprio token carrega quem você é e até quando. Oficialmente, a pronúncia rima com "jot", segundo a RFC que o define.
Quais são as três partes de um JWT?
Header (tipo do token e algoritmo de assinatura), payload (as claims — os dados JSON de fato) e assinatura, cada parte codificada em Base64Url e unida por pontos em header.payload.signature. A assinatura é computada sobre as duas primeiras partes, então qualquer adulteração nelas quebra a verificação.
O JWT é criptografado?
Não — codificado, não criptografado. Base64Url é um formato de transporte que qualquer um reverte; cole qualquer JWT em um decodificador e o payload fica legível. A assinatura impede adulteração, não leitura. Nunca coloque segredos ou dados pessoais sensíveis no payload de um JWT; a variante criptografada (JWE) existe para necessidades reais de confidencialidade.
Qual a diferença entre JWT e session token?
Onde o estado mora. Um session token é um ID opaco apontando para estado no servidor — uma consulta por requisição, revogável na hora. Um JWT carrega o estado dentro de si — sem consulta, verificável por qualquer serviço que tenha a chave, mas válido até expirar, aconteça o que acontecer. Escala stateless versus controle instantâneo.
Onde devo armazenar um JWT no navegador?
Não no localStorage — qualquer script injetado consegue lê-lo, transformando XSS em roubo de token. O consenso atual: mantenha o access token em memória, o refresh token em um cookie HttpOnly, Secure e SameSite, e considere o padrão backend-for-frontend, que mantém os tokens totalmente fora do navegador.
Um JWT pode ser revogado?
Não por design — um token assinado vale até a claim exp, o que é o preço do stateless. Os contornos reintroduzem estado: uma denylist indexada pela claim jti, chaves versionadas ou — a resposta padrão — access tokens de vida bem curta emparelhados com refresh tokens revogáveis e rotacionados.
JWT é seguro?
O formato é sólido; as implementações falham. Os buracos clássicos: aceitar alg none, deixar o header do token escolher o algoritmo (o ataque de confusão de RS256 para HS256), secrets HMAC curtos vulneráveis a brute force e pular a validação de aud/iss/exp. Fixe seus algoritmos, valide toda claim, mantenha vidas curtas.
Qual a diferença entre JWT e OAuth?
Categorias diferentes: JWT é um formato de token; OAuth 2.0 é um framework de autorização. Eles se compõem — fluxos OAuth costumam emitir access tokens que por acaso são JWTs, e os ID tokens do OpenID Connect sempre são. Um diz como o token é construído; o outro, como os tokens são distribuídos.