The token format that removes the footguns
Most JWT vulnerabilities aren't flaws in the crypto — they're flaws in the *flexibility*. JWT lets the token declare its own algorithm, offers a none option, and supports a menu of algorithms of varying strength. That flexibility is exactly what alg: none and algorithm-confusion attacks exploit. PASETO (Platform-Agnostic SEcurity TOkens) takes the opposite stance: it bakes safe cryptographic choices into the format so the dangerous options simply don't exist.
If you're choosing a stateless token format in 2026, PASETO is the "secure by default" alternative worth knowing.
The core idea: versioned protocols, not free choice
A JWT header says "here's the algorithm, please use it." A PASETO token starts with a version and a purpose that together *determine* the cryptography — the token can't negotiate it. A PASETO looks like:
v4.public.<payload>.<footer>
v4.local.<payload>.<footer>
│ │ │ │
│ │ │ └─ optional footer (not encrypted, authenticated)
│ │ └─ Base64url claims
│ └─ purpose: "local" (encrypted) or "public" (signed)
└─ version: v4 (the current recommended version)- version (
v4) fixes the exact algorithms — there's noalgfield to tamper with. Because the algorithm isn't attacker-controllable,alg: noneand algorithm confusion are impossible by construction. - purpose is either:
- - `local` — *symmetric encryption*. The claims are encrypted and authenticated with a shared secret key. Use when the same party issues and reads the token and the payload must be confidential.
- - `public` — *asymmetric signature*. The claims are signed with a private key and verified with a public key (like RS256/ES256), but *not* encrypted. Use when many parties need to verify but only one can issue.
What PASETO fixes, concretely
| JWT problem | PASETO answer |
|---|---|
alg: none bypass | No alg field exists; the version pins the algorithm. |
| RS256↔HS256 confusion | local and public are distinct purposes; you can't cross them. |
| Weak-algorithm choices | Each version hard-codes modern crypto (v4 uses Ed25519 / XChaCha20). |
| "Is the payload secret?" ambiguity | local is encrypted; public is explicitly not. |
The claims themselves are still JSON, so PASETO keeps JWT's ergonomics — standard registered claims like exp, iss, and aud work the same way, and you still validate them. You lose *nothing* semantically; you lose only the dangerous knobs.
The footer and implicit assertions
PASETO's footer is authenticated but not encrypted — a good place for a key identifier so verifiers know which key to use (analogous to JWT's kid). Newer versions also support implicit assertions: extra data bound into the signature/encryption but not stored in the token, so a token is only valid in the context it was issued for.
When to choose PASETO — and when JWT is fine
Reach for PASETO when:
- You're starting fresh and want secure defaults with no configuration to get wrong.
- You need encrypted claims (local) and don't want to hand-roll JWE.
- Your team has been bitten by JWT validation mistakes before.
Stick with JWT when: - You need the enormous ecosystem — OIDC, JWKS, gateways, and identity providers overwhelmingly speak JWT. - You're integrating with existing systems that issue or expect JWTs. - Your JWT usage is already locked down (pinned algorithm allowlist, strong keys, full claim validation) — a correctly-used JWT is secure; PASETO just makes "correctly" the only option.
The honest summary: JWT is secure when used perfectly and dangerous when used carelessly; PASETO narrows the gap between those two by removing the careless path. For an existing, well-configured JWT stack the upgrade is optional; for a greenfield service it's a sensible default.
For the JWT side of this comparison see JWT tokens explained, JWT vulnerabilities, and where to store tokens.