Think of sessions as a coat check. You hand over the coat, you get a small ticket, the attendant keeps the real thing behind the counter. JWTs are the opposite. They are a stamped passport you carry yourself, and every border guard reads the stamps instead of calling your home office.
Both get you through the door. The difference is who holds the truth, and how fast you can cancel it.
Short answer, revocation need decides #
Use server sessions for a traditional web app rendered by your server. The server stores session state, the browser holds an opaque ID, and logout or compromise means deleting one row. Revocation is immediate and easy to reason about.
Use JWTs when clients call stateless APIs across services, edge runtimes, or mobile apps where a session lookup on every request is costly or impractical. The token carries signed claims the API can verify without a database round trip.
Decision rule: need instant revoke and simple server control, pick sessions. Need stateless verification across boundaries, pick short lived JWTs plus refresh token rotation. Most mature apps end hybrid.
How do sessions and JWTs work? #
Session auth keeps authority on the server. The cookie presents an ID. The server checks users, sessions, and accounts tables, then decides. The browser token is meaningless by itself, which is the point. Steal the ticket and you only hold a pointer. Cancel the pointer and the theft ends.
JWT auth moves a bounded set of assertions to the client: header, payload claims, and signature. As stated in RFC 7519 (https://datatracker.ietf.org/doc/html/rfc7519), a JWT is a compact claims representation meant to be transferred between parties, and the claims carry the assertions. The API validates signature, issuer, audience, and expiry, then trusts the claims inside for routing and coarse authorization. Fine grained data access still belongs in the database.
This is where teams go wrong. They treat a valid signature as proof of permission. It is not. It is proof of issuance. Permission needs a fresh check against current state, either in app logic or in the database itself.
Better Auth style apps typically favor session rows for web flows, which fits D1 backed users and sessions tables well. JWTs enter when APIs, edge functions, or third party clients need verifiable tokens without session affinity.
How do sessions and JWTs compare? #
| Criterion | Server sessions | JWTs |
|---|---|---|
| Revocation | Immediate, delete session | Needs denylist, version, or short lifetime |
| Scaling reads | Session lookup per request | Verify signature, no lookup |
| Edge fit | Requires session store access | Strong fit for edge verification |
| Claim freshness | Always current from DB | Stale until token expires |
| Token size | Tiny cookie ID | Larger, grows with claims |
| Best fit | Server rendered web apps | APIs, mobile, cross service calls |
Neither eliminates authorization logic. Both still need server side checks before returning data.
Which rules keep auth safe? #
Keep JWT claims small #
Include subject, issuer, audience, issued at, expiry, and only stable scopes needed for routing. Validate all of them on every request. As stated in RFC 8725 on JWT best practices (https://datatracker.ietf.org/doc/html/rfc8725), implementations must validate the signature with the expected algorithm and reject tokens that violate assumptions. Large or sensitive claims leak through logs, caches, and browser storage. Never put secrets, passwords, or fast changing permissions inside.
// Verify everything, trust nothing implicit
const payload = await verifyJwt(token, {
issuer: 'https://auth.yourapp.com',
audience: 'api.yourapp.com',
clockTolerance: 30
});
if (payload.exp <= Date.now() / 1000) throw new Error('expired');Use short lifetimes plus refresh token rotation #
Issue short lived access tokens and longer refresh tokens that rotate on each use. Reuse of an old refresh token signals possible theft and should revoke the chain. Store refresh state server side even in JWT architectures. As stated in the OWASP JWT cheat sheet (https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_Cheat_Sheet.html), JWTs suggested for stateless sessions need a managed invalidation answer such as a denylist or short expiration, or the stateless benefit collapses.
Plan revocation explicitly #
Sessions revoke by deletion. JWTs need expiry plus a revocation path: token version per user, denylist for compromised tokens, or forced reauthentication on sensitive actions. As stated in the OWASP session management cheat sheet (https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html), session IDs must be regenerated at privilege changes and invalidated at logout, and the same termination discipline applies to token chains. Stateless never means unrevocable.
Secure transport and storage #
Use HttpOnly, Secure, SameSite cookies for web sessions and web JWTs where possible. Avoid localStorage for long lived credentials, since any script in the origin can read them. Scope cookies to the correct domain and enforce HTTPS in production, including preview safe host handling. The OWASP ASVS session requirements (https://asvs.dev/v4.0.3/V3-Session-management/) treat unique, unpredictable, invalidatable sessions as baseline, not bonus.
// Cookie shape for web session IDs and web JWTs
cookies.set('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/'
});Enforce row level security #
Application checks can drift. Where the platform supports it, add row level security policies so the database independently restricts rows to their owner. Sessions and JWT derived identities both benefit from this second layer. A confused handler can still only return rows the policy allows.
Try it: JWT Decoder
When should you use sessions and when should you use JWTs? #
Default to sessions for server rendered web apps where immediate logout, account suspension, and simple audits matter. The operational model is boring in the best way.
Choose JWTs when architecture demands stateless verification: edge APIs, mobile clients, or service to service calls. Keep them short lived, validate claims strictly, rotate refresh tokens, and retain server side revocation records.
Pick from revocation and topology needs, not from which pattern sounds more modern. The coat check and the passport both work. Just know which one you handed your users before someone loses theirs.
What are the trade-offs? #
Revocation need decides: sessions for server rendered apps, JWTs for stateless edge, mobile, and service calls.
| Where sessions win | Where JWTs win |
|---|---|
| Instant logout and suspension by deleting one row | Signature checks with no lookup across edge and APIs |
| Simple audits tied to server tables | Mobile and service clients that cannot hit a session store per call |
| Opaque browser IDs with HttpOnly Secure SameSite cookies | Short lifetimes plus rotation for scoped access |
Pick sessions when logout must bite immediately and row rules guard data. Pick JWTs when verification must happen without a store lookup.
Who this is for (and who should skip it) #
This comparison helps web builders choosing between revocable server sessions and stateless JWTs. If you need logout and suspension that take effect right away, the revocation test decides.
Skip JWTs for primary web sessions when you need instant revocation or row level rules tied to session rows. Use short lived tokens for APIs and keep web auth in sessions that you can delete in one place.
One limit to know. JWTs stay valid until expiry, so short lifetimes plus rotation plus a denylist or version check are required. Sessions revoke in one row delete but need store access on every request, which strains edge use.
- Best for developers picking server sessions for web apps and short-lived JWTs for APIs.
- Best for startups needing instant revocation on logout or compromise.
- Best for freelancers wiring auth storage with rotation and row-level checks for clients.
What we learned building this #
BYOB uses server sessions for web auth and short lived tokens for APIs, with session rows in D1 and handle in HttpOnly Secure SameSite cookies, as laid out in the BYOB authentication guide. The session layer exposes login checks and redirects so server routes can verify the session and apply row level rules. That matches the post guidance of sessions for web and JWTs for APIs done carefully, and live docs at https://byob.studio/docs/authentication return 200 describing Better Auth plus D1.