Skip to content
security

PASETO Explained: The JWT Alternative That Removes the Footguns

Updated 2026-05-15 · 9 min read · By @guptadeepak

Key takeaways

  • PASETO (Platform-Agnostic Security Tokens) was designed in response to JWT's algorithm-confusion vulnerabilities. There is no alg field.
  • Each PASETO version pins exactly one cryptographic algorithm; v4 uses Ed25519 for public-key signing and XChaCha20 for symmetric encryption.
  • The footgun closure costs interop: PASETO has a smaller ecosystem than JWT and is not used by OAuth 2.0 or OIDC.
  • Use PASETO when you control both issuer and verifier, the cryptographic-library risk worries you, and you have no spec-imposed need for JWT.
  • Biscuit is a third option for capability-based authorization where token-attenuation matters; pick it when the use case is least-privilege delegation.

What PASETO actually is

A PASETO token looks like:

v4.public.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6IjIwMjYtMDUtMTVUMTI6MDA6MDArMDA6MDAifQ-signature

Three parts joined by dots:

  1. Version: v1, v2, v3, v4. Pins the cryptographic algorithm.
  2. Purpose: local (symmetric encryption — issuer and verifier share a key) or public (asymmetric signing — verifier has only the public key).
  3. Payload: base64url-encoded JSON for public; base64url-encoded ciphertext for local.

There is no header. There is no alg field. The version-purpose pair in the prefix is the cryptographic contract, and it is not negotiable.

Why no alg field

JWT's worst vulnerability class is alg-related: alg: none acceptance, HS256-vs-RS256 confused deputy, libraries accepting whatever the token declares. The pattern across all of them is that the token tells the verifier how to verify itself, and the verifier trusts the answer.

PASETO closes the door. The verifier is configured for exactly one version-purpose pair. If the configured pair is v4.public, then a token starting with v4.local is rejected without parsing. A token with v3.public is rejected. A token with v4.public proceeds to signature verification, which is Ed25519, period.

The interop cost: a deployment that wants to migrate from v2 to v4 needs to handle both versions during the transition. That is intentional — the spec wants the migration to be deliberate.

Versions and their algorithms

The version table, in plain language:

  • v1: RSA-PSS + AES-256-CTR + HMAC-SHA-384. Originally provided for environments that needed RSA. Deprecated.
  • v2: Ed25519 for signing; XChaCha20-Poly1305 for encryption. Still acceptable.
  • v3: ECDSA P-384 + AES-256-CTR + HMAC-SHA-384. Provided for NIST-FIPS-required environments (Ed25519 is not yet FIPS-approved everywhere).
  • v4: Ed25519 for signing; XChaCha20 (with BLAKE2b PAE for AEAD) for encryption. The modern default for greenfield deployments not bound by FIPS rules.

For a new deployment in 2026 outside FIPS environments, v4 is the right choice. For FIPS environments, v3. v1 should not be used. v2 is fine in maintenance mode.

Local vs public

Two purposes, mapped to the two cryptographic problems:

  • Local: symmetric encryption. Issuer and verifier share a key. The token is encrypted and authenticated — the verifier can read the payload, and the contents are confidential from anyone without the key. Use for session cookies your own backend issues and reads.
  • Public: asymmetric signing. The issuer holds a private key; the verifier holds the public key. The payload is plaintext (base64url-encoded JSON); the signature proves issuance. Use when the verifier is a different service from the issuer and you do not want to share a secret.

The two cover the same use-case space as JWS (signed JWT) and JWE (encrypted JWT) — the PASETO equivalents are simpler because each version pins the algorithm.

When PASETO is the right choice

The deployments where PASETO pays off:

  1. You control both issuer and verifier. Internal session cookies, signed bearer tokens for your own API tier, signed event-bus messages. JWT's algorithm-negotiation surface area is not buying you anything; PASETO removes the surface area.
  2. The cryptographic-library risk worries you. Every JWT library has had a CVE in the algorithm-handling code at some point. PASETO libraries cannot have that class of bug because there is no algorithm to handle.
  3. Your team has been bitten by JWT before. Once you have spent a weekend tracing an alg: none or HS256/RS256 confusion in your stack, the appeal of "the format does not let this happen" is high.
  4. Greenfield deployment with no JWT dependency. No existing OAuth / OIDC clients to interoperate with, no SDKs that expect JWT, no auditors who only know JWT.

When PASETO is the wrong choice

The cases where JWT is the right call despite its warts:

  1. OIDC ID tokens. The spec mandates JWT. You do not get to choose.
  2. OAuth 2.0 access tokens consumed by downstream services using off-the-shelf OAuth libraries. Those libraries expect JWT.
  3. You are integrating with vendors who only emit JWT. Auth0, Okta, AWS Cognito, Firebase, Stripe, Twilio — every major IdP or SaaS API that uses signed tokens uses JWT.
  4. Your auditors want a checklist. "We use JWT, the industry-standard format" is a short conversation. "We use PASETO, here's why it's better" is a long one.
  5. You need wide language/runtime support. JWT libraries exist for everything. PASETO libraries exist for the major languages (Node, Python, Go, .NET, Java, Rust, PHP) but the ecosystem is thinner — fewer integrations, fewer tutorials, fewer people who have shipped it.

Biscuit: the capability-based alternative

A third option worth knowing about: Biscuit is a token format designed for capability-based authorization with attenuation. The defining property: a token holder can derive a more-restrictive token from a parent token without contacting the issuer.

Concrete example: the issuer mints a token that authorizes read access to bucket://files/*. A service that needs to delegate access to only bucket://files/user-123/*.txt derives a new token from the parent, adding a constraint. The new token is independently verifiable, can be used by the downstream party, and never required a callback to the original issuer. The cryptographic mechanism is third-party caveats and Ed25519 signatures.

Biscuit is right when the use case is delegation with attenuation — issuing capabilities that downstream code can scope further. It is not a JWT or PASETO replacement for "this is who the user is" tokens; it is a different category. If your service-to-service authz design has talked about "I wish I could give the downstream service a token that is just the parts of mine they need," Biscuit is the spec to read.

Implementation guidance

  1. Pick a maintained library. paseto (Node), pyseto (Python), paseto.go (Go), paseto-rs (Rust). The implementations are simpler than JWT — there is less for bugs to hide in — but use the standard ones anyway.
  2. Pin to one version. Configure the verifier for exactly the version-purpose pair you use. v4.public for greenfield public signing; v4.local for greenfield symmetric encryption.
  3. Use public over the network, local for things that never leave your trust boundary. The default mistake is using local for a service-to-service token, which means every verifier needs the key.
  4. Key rotation is your responsibility. PASETO does not have a JWKS equivalent in the spec; ship a key-rotation mechanism (versioned key IDs in your payload, two-key acceptance during rotation, etc.).
  5. Do not migrate to PASETO just because. The JWT surface area is well-understood and the library defaults are safe now. PASETO buys you removal of a class of bugs that modern libraries already mitigate; the value depends on how much you trust the library.
  6. Keep the JWT skill on the team. Even if you ship PASETO internally, the rest of the ecosystem is JWT. Read JWT Explained and Session Management: JWTs vs Opaque Tokens for the full token-format picture.

FAQ

What does PASETO stand for?
Platform-Agnostic Security Tokens. The spec was published by Scott Arciszewski in 2018 as a response to the recurring JWT vulnerability classes — alg:none, HS256/RS256 confusion, kid injection. Each PASETO version pins one algorithm, so the verifier never has to negotiate cryptography with the token. The token's version is in its prefix (e.g. v4.public.) and the verifier rejects anything but the version it was built for.
Should I migrate from JWT to PASETO?
Not for OIDC ID tokens — the spec mandates JWT, you have no choice. Not for OAuth 2.0 access tokens that downstream services validate via OAuth library defaults — JWT is what those libraries expect. Consider PASETO for internal session cookies, signed bearer artifacts your own code mints and validates, and any use case where you control both ends and the library risk of JWT bothers you. The PASETO ecosystem is smaller; that is the price.
What's the difference between PASETO versions?
Versions pin algorithms. v1 used RSA + AES-CTR (deprecated). v2 uses Ed25519 + XChaCha20-Poly1305 (still acceptable). v3 uses ECDSA P-384 + AES-CTR + HMAC (intended for NIST-required environments). v4 uses Ed25519 + XChaCha20 (modern default). Each version has two purposes: local (symmetric encryption, the verifier and issuer share a key) and public (asymmetric signing, anyone with the public key can verify). The format is `v<n>.<purpose>.<payload>`.
Why is there no alg field?
Because the alg field is where most JWT bugs live. A token format that lets the token declare its own algorithm requires the verifier to negotiate, and negotiation is where confused-deputy bugs hide. PASETO removes the negotiation: the version-purpose pair in the token prefix fully specifies the cryptographic primitive. The verifier is configured for one version; anything else is rejected, no choices to get wrong.
How does PASETO compare to Biscuit?
PASETO and Biscuit solve different problems. PASETO is a signed-token format — same job as JWT, fewer footguns. Biscuit is a capability-based authorization token that supports attenuation: the holder can derive a more-restrictive token from a parent token without contacting the issuer. Biscuit is the right pick when the use case is delegating restricted authority — issuing a token that lets a downstream service do a subset of what the original allowed. For ordinary 'this is who the user is' tokens, PASETO is the JWT replacement.

Sources

  • PASETO RFC draft (Paragon Initiative Enterprises)
  • paseto.io documentation
  • Biscuit specification (biscuitsec.org)
Last reviewed 2026-05-15.