JWT vs PASETO is a trade between flexibility and misuse resistance. JWT (RFC 7519, usually a JWS) lets the token name its algorithm from a long registry, which allowed classic bugs such as alg: none and RS256-to-HS256 confusion. PASETO (Platform-Agnostic SEcurity TOkens) fixes the cryptography per protocol version, so a v4 public token is always Ed25519 and a v4 local token is always XChaCha20 with BLAKE2b.
At a glance
| JWT (JWS) | PASETO v4 | |
|---|---|---|
| Specification | RFC 7519 with RFC 7515/7518 (IETF standards) | Community specification (paseto-standard/paseto-spec); IETF drafts expired |
| Format | base64url(header).base64url(claims).base64url(signature) | v4.public. + base64url(message ‖ signature), optional .footer |
| Algorithm selection | alg header from the IANA registry | Fixed by version and purpose |
| Public-key signing | ES256, RS256, PS256, EdDSA/Ed25519 and more | v4.public: Ed25519 only (v3.public: ECDSA P-384) |
| Symmetric tokens | HS256 (MAC only, readable claims) or JWE | v4.local: XChaCha20 + BLAKE2b-MAC (encrypted and authenticated) |
none and algorithm confusion | Possible if the verifier misconfigured | Not expressible |
| Implicit assertions (authenticated, not transmitted) | No | Yes (v3 and v4) |
| Example size (measured below) | 227 characters | 280 characters |
| Key format | JWK and JWKS | PASERK (k4.public…, k4.local…) |
| Ecosystem | Every IdP, OAuth/OIDC, gateways, cloud services | Libraries in many languages; little IdP or gateway support |
Design differences
A JWT verifier sees a header that names the algorithm and must decide whether to trust it. RFC 7518 registers over a dozen signature and MAC algorithms plus none, and libraries historically dispatched on the header, which produced the 2015 wave of bugs: unsigned tokens accepted, and RSA public keys used as HMAC secrets (see HS256 vs RS256). RFC 8725 fixes this with configuration rules that every modern library supports, but each deployment must apply them.
PASETO replaces negotiation with versions. Each version fixes one primitive per purpose: v4 uses Ed25519 for public tokens and XChaCha20 encryption with a keyed BLAKE2b MAC for local tokens; v3 uses NIST algorithms (ECDSA P-384, AES-256-CTR with HMAC-SHA-384) for environments that need them. A verifier configured for v4 public tokens cannot be tricked into anything else, and keys are typed so a local key cannot be used as a public key. Pre-authentication encoding (PAE) binds the version, purpose, payload, footer and optional implicit assertions, such as a tenant identifier that both sides know but the token does not carry.
Two other differences matter in practice. PASETO's symmetric local tokens are encrypted, while JWT's symmetric HS256 tokens are only MACed and readable; the JWT equivalent of v4.local is a JWE. And PASETO has no counterpart to JWKS discovery, so key distribution is up to you.
Side by side
The script below, run on Node.js 26.10 with paseto 3.1.4 and jose 6.2.13, issues the same claims with one Ed25519 key in both formats.
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
The PASETO is slightly longer here because the library adds iat and uses ISO 8601 timestamps, while the JWT uses numeric dates; signature sizes are identical. Note the algorithms: ["EdDSA"] option on the JWT side: that one line is what PASETO makes unnecessary.
Ecosystem and interoperability
JWT is embedded in OAuth 2.0 (RFC 9068 access tokens), OpenID Connect ID tokens, DPoP, SD-JWT, FAPI, cloud workload identity and practically every API gateway and identity provider. Libraries exist for every language, with years of security review.
PASETO's specification is maintained on GitHub by the PASETO standard project; earlier IETF Internet-Drafts expired without becoming RFCs. Libraries exist for many languages, but quality and version coverage vary, gateways and identity providers rarely accept PASETO, and there is no standard discovery mechanism. Choosing PASETO therefore means owning the issuer, the verifiers and key distribution.
When to choose which
- OAuth or OpenID Connect, third-party consumers, API gateways: JWT, with an algorithm allowlist per key.
- Internal service tokens in a polyglot system you control: either; PASETO if every language has a maintained v4 library.
- Encrypted tokens stored by clients (session data in a cookie): PASETO
v4.localis simpler than JWE; with JWT, use JWE withdirorA256KWandA256GCM. - FIPS-constrained environments: JWT with ES256 or PS256, or PASETO v3; v4's XChaCha20 and BLAKE2b are not FIPS-approved.
- Teams new to token security: PASETO removes a class of configuration errors; a JWT library with safe defaults (such as
jose, which requires algorithms to match key types) narrows the gap.
Migration notes
- Do not mix formats behind one verifier: route by prefix (
v4.public.versuseyJ) to separate verification code paths with separate keys. - Map claims explicitly: PASETO reserves
iss,sub,aud,exp,nbf,iat,jtiandkidwith ISO 8601 dates; JWT uses NumericDate seconds. - Replace JWKS with your own key distribution (PASERK strings, configuration management or a key service), including rotation by
kidin the PASETO footer. - Keep JWT at the edges: many systems issue JWTs to external parties and PASETO internally.
- If you stay with JWT, apply RFC 8725 now: pin algorithms, validate
typ,issandaud, use 256-bit HMAC secrets or asymmetric keys, and rejectnone.