Pergunte a dez desenvolvedores o que é um JWT e você vai ouvir dez respostas parecidas: “é um token criptografado que guarda a sessão do usuário”. A primeira metade está quase sempre errada. Em volta do JWT orbita uma porção de siglas (JOSE, JWS, JWE, JWA, JWK, JWKS) e confundir umas com as outras gera desde mal-entendidos inofensivos (“decodifiquei o token”) até erros graves de design (“coloquei a senha no payload porque ele é criptografado”). Cada uma dessas siglas resolve um problema distinto, e é por isso que todas existem. O incômodo é que, na prática, elas chegam juntas, embutidas na mesma string eyJ..., e quase ninguém para para separá-las.

Este artigo é o mapa mental que costuma faltar. Não vamos atacar nada aqui. É uma referência para quem precisa entender o ecossistema antes de implementar autenticação, e para o pessoal de segurança que quer o modelo certo na cabeça antes de auditar. O fio condutor desfaz o mito mais comum: um JWT típico não vem criptografado, e sim assinado. Assinatura e criptografia são coisas diferentes, resolvidas por specs diferentes, com garantias diferentes. Vamos percorrer cada uma, ver como se encaixam e fechar com um guia objetivo de quando usar o quê.

JOSE: o guarda-chuva que conecta tudo

JOSE significa JSON Object Signing and Encryption, e é o nome da família de especificações do IETF que padroniza como representar chaves, assinaturas e criptografia usando JSON. Quem diz “JWT” está, na verdade, invocando metade do JOSE sem perceber. As cinco specs centrais foram publicadas juntas em 2015 e se referenciam mutuamente. Você não entende uma de verdade sem as outras.

A divisão de trabalho é limpa. Uma spec define o contêiner de dados (JWT), duas definem os formatos de proteção (JWS para assinar, JWE para criptografar), uma define o catálogo de algoritmos que esses formatos podem usar (JWA) e uma define como representar as chaves (JWK). Tudo em JSON, tudo serializável em uma string compacta segura para URLs.

Sigla O que faz RFC
JWT JSON Web Token: contêiner de claims (afirmações sobre uma entidade). Sempre serializado como JWS ou JWE. RFC 7519
JWS JSON Web Signature: assina (ou aplica MAC a) um payload. Garante integridade e autenticidade. RFC 7515
JWE JSON Web Encryption: criptografa um payload. Garante confidencialidade (e integridade do conteúdo). RFC 7516
JWK JSON Web Key: representa uma chave criptográfica em JSON. Um conjunto delas é um JWK Set (JWKS). RFC 7517
JWA JSON Web Algorithms: registro de algoritmos para JWS e JWE (os valores de alg e enc). RFC 7518

Vale conhecer ainda um documento que não está entre as cinco specs centrais: o RFC 8725, o JSON Web Token Best Current Practices (BCP 225). Ele consolida as recomendações de segurança aprendidas com anos de implementações erradas. Se você só vai ler um documento do conjunto, leia esse, logo depois deste artigo.

JWT: o contêiner de claims

O RFC 7519 define o JWT como um meio compacto e seguro para URLs de transmitir claims entre duas partes. Uma claim é só um par chave-valor, uma afirmação sobre uma entidade. O JWT não é, em si, um formato de criptografia nem de assinatura. Ele é o conteúdo, o JSON com as claims. Quem fornece a proteção é o invólucro: JWS ou JWE.

As claims se dividem em três grupos. As registered claims são padronizadas e têm nomes curtos reservados:

  • iss (issuer): quem emitiu o token.
  • sub (subject): a quem o token se refere (tipicamente o ID do usuário).
  • aud (audience): para quem o token se destina (qual API/serviço deve aceitá-lo).
  • exp (expiration time): NumericDate (segundos desde a época Unix) após o qual o token não vale mais.
  • nbf (not before): NumericDate antes do qual o token ainda não vale.
  • iat (issued at): quando foi emitido.
  • jti (JWT ID): identificador único do token, útil para revogação e mitigação de replay.

Além dessas, há public claims (registradas na IANA ou namespaced por URI para evitar colisão) e private claims (acordadas entre as partes, como role, tenant_id, email).

O ponto que organiza todo o resto: um JWT é sempre serializado como um JWS ou como um JWE. Não existe “JWT cru” na rede. Quando você recebe aquela string com pontos, ela já é um JWS ou um JWE carregando um JWT como payload. E na esmagadora maioria dos casos (tokens OIDC, tokens de API, sessões stateless) é um JWS, assinado e não criptografado.

JWS: assinatura = integridade e autenticidade

O RFC 7515 define o JWS, o formato que assina um payload. A garantia é dupla: integridade (o conteúdo não foi alterado depois de assinado) e autenticidade (foi mesmo o detentor da chave quem o emitiu). O que ele não oferece é confidencialidade, e é aí que mora o mal-entendido número um.

Na compact serialization (a forma comum), um JWS tem exatamente três partes separadas por ponto:

BASE64URL(header) . BASE64URL(payload) . BASE64URL(assinatura)
eyJhbGciOiJSUzI1NiIsImtpZCI6IjJhZSJ9.eyJzdWIiOiI0MiJ9.NHVtX2Z4a2g...
└─────────── header ───────────┘ └─ payload ─┘ └── assinatura ──┘

As duas primeiras partes são apenas base64url de um JSON. Não há criptografia: qualquer pessoa que intercepte o token decodifica e lê o header e o payload. A terceira parte é a assinatura, calculada sobre BASE64URL(header) . BASE64URL(payload) com a chave e o algoritmo indicados em alg.

Guarde isto: o payload de um JWS é legível por qualquer um. Decodificar é uma operação pública, sem chave. Daí a regra de ouro: nunca coloque um segredo no payload de um JWS. Senha, número de cartão, token de API de terceiro, nada disso. Se precisa esconder o conteúdo, você precisa de JWE.

Existe também a JSON serialization, mais verbosa, que permite múltiplas assinaturas sobre o mesmo payload. Ela serve quando várias partes precisam atestar o mesmo documento. Mas na vida de API e OIDC, é compact serialization de três partes o tempo inteiro.

JWE: criptografia = confidencialidade

O RFC 7516 define o JWE, o formato que de fato criptografa o payload. A garantia aqui é a confidencialidade, somada à integridade do conteúdo. O JWE usa criptografia autenticada (AEAD), então o conteúdo fica ilegível para quem não tem a chave, e qualquer adulteração do ciphertext ou do header protegido é detectada na verificação da tag.

Na compact serialization, um JWE tem exatamente cinco partes separadas por ponto:

BASE64URL(protected header) . BASE64URL(encrypted key) . BASE64URL(iv) . BASE64URL(ciphertext) . BASE64URL(authentication tag)

A mecânica por trás dessas cinco partes é a criptografia híbrida. Uma Content Encryption Key (CEK) aleatória cifra o conteúdo simetricamente (rápido), e essa CEK é então protegida (cifrada ou derivada) a partir da chave do destinatário, conforme o algoritmo indicado em alg. As cinco partes mapeiam diretamente nisso: o protected header descreve os algoritmos (e serve como dado adicional autenticado), a encrypted key é a CEK protegida, o iv é o vetor de inicialização, o ciphertext é o conteúdo cifrado, e a authentication tag valida o ciphertext e o header protegido.

Quando usar JWE? Sempre que o token precisar carregar dados sensíveis que trafegam por partes não confiáveis ou ficam armazenados em lugares inspecionáveis. Pense em tokens que passam por intermediários, ou que contêm PII que não deve vazar nem para o próprio cliente que os guarda. O custo é maior: mais complexidade e mais armadilhas de implementação. Na prática, o número de partes é o jeito mais rápido de identificar o formato no meio do tráfego: se você vê três partes, é JWS; cinco, é JWE.

JWA: o catálogo de algoritmos

O RFC 7518 é o registro, o catálogo que diz quais valores alg e enc são válidos e o que cada um significa. O JWA não “faz” nada sozinho; ele é a referência que JWS e JWE consultam. A distinção mais importante, e a mais confundida, é entre os dois campos:

  • alg — presente tanto em JWS quanto em JWE. Em um JWS, identifica o algoritmo de assinatura ou MAC. Em um JWE, identifica o algoritmo de gestão de chave (como a CEK é protegida). Em ambos os casos vive no header.
  • enc — presente somente em JWE. Identifica o algoritmo de criptografia do conteúdo (como a CEK cifra o payload). Um JWS nunca tem enc.

Valores típicos de alg para assinatura/MAC (JWS):

  • HS256 — HMAC com SHA-256. Simétrico: o mesmo segredo gera e verifica a tag (tecnicamente um MAC, não uma assinatura).
  • RS256 — RSASSA-PKCS1-v1_5 com SHA-256. Assimétrico: chave privada assina, pública verifica.
  • PS256 — RSASSA-PSS com SHA-256. Variante mais moderna de assinatura RSA.
  • ES256 — ECDSA com curva P-256 e SHA-256. Assimétrico, assinaturas menores.

Valores típicos de alg para gestão de chave (JWE): RSA-OAEP, ECDH-ES, dir (direct: usa uma chave simétrica já compartilhada diretamente como CEK, sem cifrar chave nenhuma — nesse caso a parte encrypted key fica vazia).

Valores típicos de enc (só JWE): A128GCM, A256GCM (AES em modo GCM), A128CBC-HS256 (AES-CBC combinado com HMAC-SHA-256).

Não troque os campos na cabeça. RS256 é um alg de assinatura de JWS; A256GCM é um enc de conteúdo de JWE. Um token assinado com RS256 e criptografado com um enc como A256GCM é um JWE aninhado (veremos adiante), não uma escolha “ou um ou outro”.

JWK e JWKS: a representação da chave

O RFC 7517 resolve um problema prático: como publicar e referenciar chaves criptográficas em JSON. Um JWK é um objeto JSON que descreve uma chave. Os campos comuns:

  • kty (key type): RSA, EC, oct (simétrica/octet).
  • kid (key ID): identificador da chave, usado para casar com o header do token.
  • use: sig (assinatura) ou enc (criptografia).
  • alg: algoritmo pretendido para a chave.

Cada tipo tem seus parâmetros próprios: RSA usa n (módulo) e e (expoente público); EC usa crv, x, y; uma chave simétrica usa k. Uma chave pública RSA fica assim:

{
  "kty": "RSA",
  "use": "sig",
  "kid": "2ae",
  "alg": "RS256",
  "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx...",
  "e": "AQAB"
}

Várias chaves juntas formam um JWK Set (JWKS): um objeto JSON com um array keys. É assim que um provedor de identidade publica suas chaves públicas de verificação, tipicamente no endpoint /.well-known/jwks.json:

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "2ae",
      "alg": "RS256",
      "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx...",
      "e": "AQAB"
    },
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "9f1",
      "alg": "RS256",
      "n": "qDi7Tx4DhNvPQsl1ofxxc2ePQFcs-L0mXYo6TGS64CY_2vmrk...",
      "e": "AQAB"
    }
  ]
}

O elo final é o kid. Quando um JWS chega, o verificador lê o kid do header do token, procura no JWKS a chave cujo kid bate e usa o n/e dela para verificar a assinatura. O kid é o que permite rotação de chaves sem quebrar tokens antigos: o JWKS pode publicar a chave nova e a antiga ao mesmo tempo, cada token apontando para a sua. Um cuidado: o kid é um campo controlado por quem monta o token. Ele serve para selecionar a chave dentro de um conjunto confiável, nunca para confiar cegamente em uma chave ou caminho que o emissor aponte.

Juntando tudo: a anatomia de um token real

Pegue um access token ou ID token OIDC qualquer, o tipo de token que você recebe ao logar via Google, Auth0, Keycloak, Cognito. Ele tem três partes (logo, é um JWS):

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjJhZSJ9.eyJpc3MiOiJ...

Decodificando a primeira parte (o header):

{ "alg": "RS256", "typ": "JWT", "kid": "2ae" }

E a segunda (o payload, o JWT propriamente dito):

{
  "iss": "https://login.exemplo.com",
  "sub": "auth0|64f1c0",
  "aud": "https://api.exemplo.com",
  "exp": 1769904000,
  "iat": 1769900400,
  "scope": "openid profile email"
}

Agora veja as peças se encaixarem, todas na mesma string:

  • É um JWT (RFC 7519): o payload é o contêiner de claims — iss, sub, aud, exp, iat.
  • Serializado como um JWS (RFC 7515): três partes, payload assinado e legível.
  • Com um algoritmo do JWA (RFC 7518): alg: RS256.
  • Verificado com uma chave publicada como JWK (RFC 7517), encontrada em um JWKS servido em https://login.exemplo.com/.well-known/jwks.json.
  • Identificada pelo kid: 2ae do header, que casa com o kid do JWK certo dentro daquele JWKS.

Uma frase amarra tudo: um access token OIDC é um JWT, serializado como um JWS compact, assinado com um algoritmo do JWA, cuja chave pública é publicada como um JWK dentro de um JWKS, identificada pelo kid no header. Seis siglas, um único token.

Decodificar não é descriptografar

Este é o mal-entendido que mais causa dano em produção. “Eu decodifiquei o JWT” e “eu descriptografei o JWT” são frases sobre operações completamente diferentes. Em um JWS, repetindo, o caso da esmagadora maioria, o payload é só base64url. Decodificá-lo não exige chave, segredo, nem permissão. É uma transformação pública e reversível que qualquer pessoa com o token faz em um clique. Não há nada de criptográfico nisso.

A consequência prática é direta: confidencialidade só vem do JWE. Se você precisa que o conteúdo do token seja secreto e está usando um JWS, você não tem confidencialidade nenhuma. Tem um JSON em texto claro com um carimbo de autenticidade. Colocar uma senha, um segredo de API ou dados sensíveis no payload de um JWS é o mesmo que escrevê-los num cartão postal e confiar que ninguém vai virar para ler.

A assinatura protege contra adulteração, não contra leitura. Ela garante que o atacante não consegue mudar role: user para role: admin sem invalidar o token, desde que a verificação seja feita corretamente. E “desde que seja feita corretamente” é justamente onde mora uma família inteira de vulnerabilidades reais, do alg: none à confusão de algoritmo. Se você quer entender como essas verificações falham e como atacantes transformam um JWS mal validado em bypass de autenticação, leia o complemento ofensivo deste artigo: Vulnerabilidades em JWT.

Qual usar?

A decisão raramente é difícil quando você separa as garantias:

  • Precisa de integridade e autenticidade? Use JWS. É o caso de praticamente todo token de sessão, access token e ID token. Você quer provar quem emitiu e impedir adulteração, e não se importa que o conteúdo seja legível, porque não há segredo nele. Prefira algoritmos assimétricos (RS256, ES256, PS256) quando emissor e verificador são partes distintas, para não compartilhar um segredo.
  • Precisa de confidencialidade? Use JWE. O conteúdo precisa ficar ilegível para terceiros, e até para o cliente que armazena o token. Aceite a complexidade extra e siga o RFC 8725.
  • Precisa dos dois? Use um nested JWT: assine primeiro (gerando um JWS) e depois cifre esse JWS dentro de um JWE. A ordem importa. Assinar e depois cifrar é o padrão recomendado, porque garante que a assinatura cobre o conteúdo original e não fica exposta em claro. O resultado é um JWE cujo payload, ao ser decifrado, revela um JWS para verificar.

E o mais importante: quando não usar JWT. Para sessões de usuário em aplicações web tradicionais, um cookie de sessão opaco com estado no servidor costuma ser mais simples e mais seguro. Você consegue revogar na hora, não carrega dados sensíveis no cliente e não depende de validação correta de assinatura em cada serviço. O JWT brilha em cenários stateless, distribuídos, cross-service, onde a verificação descentralizada compensa a perda de revogação imediata. Se você não tem esse problema, talvez não precise dessa solução. A pergunta certa nunca é “qual algoritmo?”, e sim “eu preciso mesmo de um token autocontido?”.


A sopa de letrinhas para de assustar no instante em que você troca a pergunta “o que significa cada sigla?” por “qual garantia cada peça fornece?”. O JOSE é o guarda-chuva. O JWT é o que você está dizendo, o JWS prova que foi você quem disse e o JWE esconde o que foi dito. O JWA lista os algoritmos disponíveis, e o JWK é a chave que destranca a prova. Guarde a frase que separa quem entende de quem repete o mito: assinar não é criptografar, e decodificar nunca foi descriptografar.