Pergunte a dez desenvolvedores o que é um JWT e você 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 constelação de siglas — JOSE, JWS, JWE, JWA, JWK, JWKS — e a confusão entre elas produz desde mal-entendidos inofensivos (“decodifiquei o token”) até erros graves de design (“coloquei a senha no payload porque ele é criptografado”). A sopa de letrinhas existe por um bom motivo: cada peça resolve um problema distinto. O problema é que, na prática, todas chegam juntas, embutidas na mesma string eyJ..., e ninguém para para separá-las.

Este artigo é o mapa mental que falta. Não vamos atacar nada aqui — é uma referência para desenvolvedores que precisam entender o ecossistema antes de implementar autenticação, e para profissionais de segurança que querem o modelo correto na cabeça antes de auditar. O fio condutor é uma única verdade que desfaz o mito mais comum: um JWT típico não é criptografado, é assinado. Assinatura e criptografia são coisas diferentes, resolvidas por specs diferentes, com garantias diferentes. Vamos percorrer cada uma, ver como se encaixam, e terminar 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. Quando alguém 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 consegue entender 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.

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

Existe ainda um documento que você deve conhecer mesmo que não seja uma das 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 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 é simplesmente 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 um “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 que ele oferece é 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 é exatamente aqui 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.

Repita mentalmente: o payload de um JWS é legível por qualquer um. Decodificar é uma operação pública, sem chave. Por isso 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 — útil em cenários onde 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 — 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. JWA não “faz” nada sozinho; ele é a referência que JWS e JWE consultam. A distinção mais importante, e 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. (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 — você 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 é equivalente a 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 mesmo para o cliente que armazena o token. Aceite a complexidade extra e siga o RFC 8725.
  • Precisa de ambos? 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. 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?”. JOSE é o guarda-chuva; JWT é o que você está dizendo; JWS prova que foi você quem disse; JWE esconde o que foi dito; JWA é a lista de como; JWK é a chave que destranca a prova. Guarde a única frase que separa quem entende de quem repete o mito: assinar não é criptografar, e decodificar nunca foi descriptografar.