HomeBlog → Auth Token Comparison

JWT vs Session Cookies vs PASETO vs Biscuit — Auth Token Comparison (2026)

📅 Updated April 2026 ⏱ 15 min read 🛠 Developer guide
SG
By Saurabh Goyal · Independent Software Developer
Builds JSON Web Tools · GitHub · About the author

"Just use JWT" became the default authentication advice for web applications in the late 2010s, and the consequences are still being felt. JWT is genuinely useful for some scenarios — and an actively bad choice for others. Meanwhile, alternatives that addressed JWT's footguns have matured: PASETO removed the algorithm-confusion problem at the protocol level, Biscuit added offline attenuation, and server-side session cookies — the option JWT was supposed to replace — remain the simplest secure default for the vast majority of web applications. This article compares the four most relevant token approaches in 2026: JWT, server-side session cookies, PASETO, and Biscuit. The recommendation at the end is opinionated and grounded in the security literature.

Decode a JWT to follow along

Paste any JWT into the decoder to see header, payload, and signature. Examples in this article are decodable inline.

Open JWT Decoder →

The Comparison at a Glance

PropertyJWTServer-side sessionPASETOBiscuit
Stateless (no server lookup)YesNoYesYes
Easy revocationNo (or stateful)YesNo (or stateful)Partial
Algorithm-confusion attacks possibleYes (alg header)N/ANo (versioned)No
Confidentiality of claimsNo (just base64)Yes (server-only)v*.local: YesNo
Offline attenuation / delegationNoNoNoYes
Storage in browserCookie / localStorageHttpOnly cookie (best)Cookie / localStorageCookie / localStorage
Library maturity (Node)ExcellentExcellentGoodLimited
Cross-language supportExcellentN/A (server-internal)GoodLimited
RFC / standardRFC 7519De factoPASETO specBiscuit spec

Server-Side Session Cookies

The traditional approach: when a user logs in, the server creates a session record (in Redis, in a database, or in memory), generates an opaque random session ID, and sets that ID in an HttpOnly cookie. On every request, the server looks up the session record by ID.

// Express + express-session
import session from "express-session";
app.use(session({
  secret: process.env.SESSION_SECRET,
  cookie: { httpOnly: true, secure: true, sameSite: "lax", maxAge: 86400000 },
  store: new RedisStore({ client: redis }),
  resave: false,
  saveUninitialized: false,
}));

Strengths: Trivial revocation (delete the session row). All session data lives server-side, so claims can't be tampered with or read by clients. The cookie is opaque — leaking it doesn't expose user data. HttpOnly + Secure + SameSite cookies are the safest place to store credentials in a browser.

Weaknesses: Requires a session store (Redis or database). Doesn't work across different domains or for stateless microservices. Doesn't work for native mobile clients without extra work (cookie management is awkward outside the browser).

JWT (JSON Web Token)

JWT is a self-contained token: the user's claims (id, role, expiry) are encoded into the token itself, signed by the server. The server validates the signature on every request without consulting a session store.

// Three base64url parts joined by dots
header.payload.signature

// Example payload (decoded):
{
  "sub": "user_123",
  "role": "admin",
  "iat": 1714780000,
  "exp": 1714783600
}

Strengths: Stateless. Validation needs only the public key (RS256/ES256/EdDSA) or shared secret (HS256). Works naturally across services, domains, and native clients. Standard format with libraries in every language.

Weaknesses (security):

If you must use JWT, do this

PASETO (Platform-Agnostic Security Tokens)

PASETO is JWT's successor, designed by security researchers (notably Scott Arciszewski) to remove JWT's cryptographic footguns. The core idea: tokens have a version that fixes the algorithm. There is no alg header to manipulate.

// PASETO v4.public token (Ed25519 signed, claims visible like JWT)
v4.public.eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6IjIwMjYtMDUtMDNUMTI6MDA6MDBaIn0.aBcDeFg...

// PASETO v4.local (XChaCha20-Poly1305 encrypted, claims confidential)
v4.local.RnB1RQg...

The format is similar to JWT (header.payload.signature) but with critical differences:

Strengths: Eliminates the most common JWT vulnerabilities at the protocol level. Local tokens give you payload confidentiality if you need claims hidden from clients. Spec is opinionated and discourages footguns.

Weaknesses: Smaller ecosystem than JWT. Library quality varies. Same fundamental revocation problem as JWT. Most identity providers (Auth0, Cognito, Okta) speak JWT, not PASETO — interop may force you back to JWT.

Biscuit

Biscuit is a 2021-era token format with a unique feature: offline attenuation. A token holder can derive a more restricted token without contacting the server. The new token cannot grant more permissions than the original — only less.

This solves a real problem: delegation. If your user wants to grant a third-party tool read-only access to a single project, the user can attenuate their existing token offline rather than going through an OAuth dance.

Biscuit also has a Datalog-like policy language baked into the token, so authorization decisions can be made at the API gateway without consulting the application.

Strengths: Offline attenuation is genuinely novel. Useful for capability-style auth, delegated access, microservice mesh authorization. Ed25519 by default — no algorithm-choice footgun.

Weaknesses: Smallest ecosystem of the four — primarily Rust, with bindings to other languages. Limited documentation outside the spec. Most teams don't actually need offline attenuation; if you don't, the complexity isn't worth it.

Decision Tree

  1. Is this a single-domain web application with a server you control? → Server-side session cookies. The simplest secure choice.
  2. Is this a microservices architecture where services can't share a session store? → JWT or PASETO. Prefer PASETO if the team can adopt it; JWT if you need broad ecosystem compatibility (especially with identity providers).
  3. Are you integrating with Auth0, Cognito, Firebase Auth, Clerk, or another identity provider? → JWT. The provider speaks JWT; using anything else means re-implementing auth.
  4. Do you need delegation / offline attenuation as a first-class feature? → Biscuit, or build a custom token format. Most teams do not need this.
  5. Do you need claims confidentiality (the client should not see the contents)? → PASETO v*.local, or a server-side session, or encrypt the JWT payload separately (uncommon).
  6. Is performance a real concern (10k+ req/sec)? → JWT or PASETO with cached public-key validation. Server-side sessions add a Redis round-trip per request.

The Honest Recommendation for 2026

Storage Pitfalls in the Browser

Whatever token format you choose, the storage decision in the browser is critical:

StorageXSS-readable?CSRF-vulnerable?Recommended?
localStorageYes (any script)NoNo (avoid for credentials)
sessionStorageYesNoNo (avoid for credentials)
Cookie (HttpOnly + Secure + SameSite=Strict/Lax)NoMitigated by SameSiteYes (best for browsers)
In-memory JS variableYes (page-level)NoOK for short-lived in-page state

Putting a JWT in localStorage so you can send it as a Bearer header sounds clean but is a regression in security from a session cookie. If your app runs in a browser, you almost always want HttpOnly cookies — at which point much of the JWT story is just adding cryptographic signatures to data the server could have stored in Redis.

Common Misconceptions

"JWT is encrypted"

Almost always false. The default JWT (JWS) is signed, not encrypted. Anyone who has the token can base64-decode it and read every claim. JWE provides encrypted JWT but is rare and complex; PASETO v*.local is the cleaner option if you need confidentiality.

"JWT is faster than sessions"

Only sometimes. JWT validation is a signature check (microseconds with ECDSA). Session validation is a Redis lookup (sub-millisecond on a local network). For a single request, the difference is rounding error. For 100k req/sec on a global edge with no Redis nearby, JWT wins. For a typical web app, the performance difference is irrelevant.

"JWT solves single sign-on"

Not by itself. SSO is solved by OAuth 2.0 and OIDC, which often use JWT as the access token format but also include flows for issuance, consent, and revocation. JWT alone is a token format, not an SSO protocol.

"JWT can't be stolen because it's signed"

Wrong. Signing prevents tampering, not theft. A stolen JWT is valid until it expires. This is exactly why short expiry + refresh tokens is essential, and why server-side sessions (which can be revoked instantly) remain attractive.

Practical Tooling

Whichever token format you settle on, you'll spend time inspecting and debugging tokens:

Decode a JWT in your browser

Paste any JWT to see the header, payload claims, and signature breakdown. Tokens never leave your browser.

Open JWT Decoder →

Frequently Asked Questions

Should I use JWT or session cookies?+

For most web applications, server-side session cookies are the simpler, more secure default. They support easy revocation and integrate naturally with framework session middleware. Use JWT for genuine stateless requirements like microservices that cannot share a session store.

Is JWT secure?+

JWT can be secure when used correctly: short expiry, asymmetric signatures (RS256/ES256/EdDSA, not HS256), no sensitive data in the payload, and proper validation of every claim. Most JWT vulnerabilities involve algorithm confusion, weak keys, or trusting unverified claims.

What is PASETO and why is it better than JWT?+

PASETO is an alternative to JWT designed to remove cryptographic foot-guns. Instead of letting the token specify its algorithm (which led to algorithm-confusion attacks against JWT), PASETO has fixed versions: each version specifies exactly one algorithm. There is no alg:none and no algorithm-substitution attack.

Can JWT be revoked?+

Not without compromising statelessness. Common approaches: maintain a server-side blocklist (defeats most performance benefits), or keep access tokens very short-lived (5–15 minutes) with longer-lived refresh tokens stored server-side. Server-side sessions remain simpler when revocation is critical.

What is Biscuit auth?+

Biscuit is a 2021-era token format that supports offline attenuation: a holder can derive a more restricted token from it without contacting the server. Useful for delegation patterns. Adoption is still limited compared to JWT and PASETO.