JWT vs PASETO é uma troca entre flexibilidade e resistência ao uso indevido. O JWT (RFC 7519, normalmente um JWS) deixa o token indicar o próprio algoritmo a partir de um registro extenso, o que permitiu bugs clássicos como alg: none e a confusão de RS256 com HS256. O PASETO (Platform-Agnostic SEcurity TOkens) fixa a criptografia por versão do protocolo, de modo que um token v4 public é sempre Ed25519 e um token v4 local é sempre XChaCha20 com BLAKE2b.
Visão geral
| JWT (JWS) | PASETO v4 | |
|---|---|---|
| Especificação | RFC 7519 com RFC 7515/7518 (padrões do IETF) | Especificação comunitária (paseto-standard/paseto-spec); os rascunhos do IETF expiraram |
| Formato | base64url(header).base64url(claims).base64url(signature) | v4.public. + base64url(mensagem ‖ assinatura), .footer opcional |
| Seleção do algoritmo | Cabeçalho alg, a partir do registro da IANA | Fixada pela versão e pela finalidade |
| Assinatura com chave pública | ES256, RS256, PS256, EdDSA/Ed25519 e outros | v4.public: apenas Ed25519 (v3.public: ECDSA P-384) |
| Tokens simétricos | HS256 (apenas MAC, claims legíveis) ou JWE | v4.local: XChaCha20 + BLAKE2b-MAC (criptografado e autenticado) |
none e confusão de algoritmo | Possíveis se o verificador estiver mal configurado | Impossíveis de expressar |
| Asserções implícitas (autenticadas, não transmitidas) | Não | Sim (v3 e v4) |
| Tamanho do exemplo (medido abaixo) | 227 caracteres | 280 caracteres |
| Formato de chave | JWK e JWKS | PASERK (k4.public…, k4.local…) |
| Ecossistema | Todos os IdPs, OAuth/OIDC, gateways, serviços de nuvem | Bibliotecas em muitas linguagens; pouco suporte em IdPs e gateways |
Diferenças de projeto
Um verificador de JWT vê um cabeçalho que indica o algoritmo e precisa decidir se confia nele. A RFC 7518 registra mais de uma dúzia de algoritmos de assinatura e de MAC, além de none, e historicamente as bibliotecas escolhiam o caminho de verificação pelo cabeçalho, o que produziu a onda de bugs de 2015: tokens sem assinatura aceitos e chaves públicas RSA usadas como segredos de HMAC (veja HS256 vs RS256). A RFC 8725 corrige isso com regras de configuração que todas as bibliotecas modernas oferecem, mas cada implantação precisa aplicá-las.
O PASETO substitui a negociação por versões. Cada versão fixa uma primitiva por finalidade: a v4 usa Ed25519 para tokens public e criptografia XChaCha20 com um MAC BLAKE2b com chave para tokens local; a v3 usa algoritmos do NIST (ECDSA P-384, AES-256-CTR com HMAC-SHA-384) para ambientes que precisam deles. Um verificador configurado para tokens v4 public não pode ser enganado para aceitar outra coisa, e as chaves têm tipo, de modo que uma chave local não pode ser usada como chave pública. A codificação de pré-autenticação (PAE) vincula a versão, a finalidade, o payload, o rodapé (footer) e as asserções implícitas opcionais, como um identificador de tenant que os dois lados conhecem, mas que o token não carrega.
Duas outras diferenças importam na prática. Os tokens simétricos local do PASETO são criptografados, enquanto os tokens simétricos HS256 do JWT têm apenas um MAC e continuam legíveis; o equivalente JWT do v4.local é um JWE. Além disso, o PASETO não tem nada equivalente à descoberta via JWKS, então a distribuição de chaves fica por sua conta.
Lado a lado
O script abaixo, executado no Node.js 26.10 com o paseto 3.1.4 e o jose 6.2.13, emite as mesmas claims com uma única chave Ed25519 nos dois formatos.
import { generateKeyPairSync } from "node:crypto";
import { SignJWT, jwtVerify } from "jose";
import { V4 } from "paseto";
const claims = { sub: "user-42", scope: "orders:read" };
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
// PASETO v4.public: Ed25519 is implied by the version; no alg header exists.
const paseto = await V4.sign(claims, privateKey, { expiresIn: "15m", issuer: "https://auth.example.com" });
const pasetoClaims = await V4.verify(paseto, publicKey, { issuer: "https://auth.example.com" });
// JWT with the same key: the verifier must pin the algorithm itself.
const jwt = await new SignJWT(claims).setProtectedHeader({ alg: "EdDSA" }).setIssuer("https://auth.example.com").setExpirationTime("15m").sign(privateKey);
const { payload } = await jwtVerify(jwt, publicKey, { algorithms: ["EdDSA"], issuer: "https://auth.example.com" });
console.log("PASETO prefix:", paseto.slice(0, 10), "length:", paseto.length, "sub:", pasetoClaims.sub);
console.log("JWT prefix:", jwt.slice(0, 10), "length:", jwt.length, "sub:", payload.sub);
PASETO prefix: v4.public. length: 280 sub: user-42
JWT prefix: eyJhbGciOi length: 227 sub: user-42
Aqui o PASETO fica um pouco mais longo porque a biblioteca acrescenta iat e usa timestamps ISO 8601, enquanto o JWT usa datas numéricas; o tamanho das assinaturas é idêntico. Repare na opção algorithms: ["EdDSA"] do lado do JWT: essa única linha é o que o PASETO torna desnecessário.
Ecossistema e interoperabilidade
O JWT está embutido no OAuth 2.0 (tokens de acesso da RFC 9068), nos tokens de ID do OpenID Connect, no DPoP, no SD-JWT, no FAPI, na identidade de cargas de trabalho na nuvem e em praticamente todo gateway de API e provedor de identidade. Há bibliotecas para todas as linguagens, com anos de revisão de segurança.
A especificação do PASETO é mantida no GitHub pelo projeto PASETO standard; os Internet-Drafts anteriores do IETF expiraram sem virar RFCs. Há bibliotecas para muitas linguagens, mas a qualidade e as versões cobertas variam, gateways e provedores de identidade raramente aceitam PASETO, e não existe um mecanismo padrão de descoberta. Escolher o PASETO, portanto, significa ficar responsável pelo emissor, pelos verificadores e pela distribuição de chaves.
Quando escolher cada um
- OAuth ou OpenID Connect, consumidores terceiros, gateways de API: JWT, com uma lista de permissões de algoritmos por chave.
- Tokens internos entre serviços em um sistema poliglota que você controla: qualquer um dos dois; PASETO se todas as linguagens tiverem uma biblioteca v4 mantida.
- Tokens criptografados armazenados pelos clientes (dados de sessão em um cookie): o PASETO
v4.localé mais simples que o JWE; com JWT, use JWE comdirouA256KWeA256GCM. - Ambientes sujeitos a restrições do FIPS: JWT com ES256 ou PS256, ou PASETO v3; o XChaCha20 e o BLAKE2b da v4 não são aprovados pelo FIPS.
- Equipes com pouca experiência em segurança de tokens: o PASETO elimina uma classe de erros de configuração; uma biblioteca de JWT com padrões seguros (como o
jose, que exige que os algoritmos correspondam aos tipos de chave) reduz a diferença.
Notas de migração
- Não misture formatos atrás de um único verificador: encaminhe pelo prefixo (
v4.public.versuseyJ) para caminhos de código de verificação separados, com chaves separadas. - Mapeie as claims explicitamente: o PASETO reserva
iss,sub,aud,exp,nbf,iat,jtiekid, com datas ISO 8601; o JWT usa segundos em NumericDate. - Substitua o JWKS pela sua própria distribuição de chaves (strings PASERK, gerenciamento de configuração ou um serviço de chaves), incluindo a rotação pelo
kidno rodapé do PASETO. - Mantenha o JWT nas bordas: muitos sistemas emitem JWTs para partes externas e PASETO internamente.
- Se você ficar com o JWT, aplique a RFC 8725 agora: fixe os algoritmos, valide
typ,isseaud, use segredos de HMAC de 256 bits ou chaves assimétricas e rejeitenone.