This is one of those choices where the trendy answer is not usually the right one. JWTs are everywhere in tutorials, so AI-built apps default to them, frequently stored in localStorage, which is the least safe option. For a typical web app with a single backend, a session cookie is simpler, gives you instant revocation, and avoids the most common auth vulnerability. Here is how the two really compare.
The core difference: stateful vs stateless
A session cookie is stateful. The server stores the session and the cookie carries only an id pointing to it. To log someone out, you delete the server session; the cookie is now worthless.
A JWT is stateless. The token itself holds the user's claims, signed so the server trusts it without a database lookup. That is elegant, but it means the token is valid until it expires, and revoking it early takes extra work. See what a JWT is for the mechanics.
Want to see exactly what Prbl flags? Watch it scan a demo app, no repo or account needed.
See a live scan →The mistake that makes JWTs unsafe
The security gap is almost never the token type; it is where the token is stored. AI-built apps put the JWT in localStorage, where any script on the page can read it, so a single cross-site scripting bug hands over the session. A session cookie marked httpOnly cannot be read by script at all. The fix, if you use JWTs, is to store them the same way: in an httpOnly cookie, not localStorage. The full reasoning is in where to store a JWT.
How to choose
- Single web app, one backend: use a session cookie. It is simpler, revocation is free, and an httpOnly cookie sidesteps the localStorage trap. Add sameSite plus a CSRF token and you are covered.
- Multiple services verifying the same token, or an API with bearer-token clients: JWTs earn their keep, because stateless verification is exactly what you need. Still keep browser tokens in an httpOnly cookie.
- You need instant revocation: lean toward session cookies, or accept the extra machinery (short expiry plus refresh, or a denylist) that revocable JWTs require.
Whichever you pick, the cookie flags matter: httpOnly, secure, and sameSite. The fix for a cookie missing those applies to both approaches, and cookie auth needs CSRF protection.
Frequently asked questions
What is the actual difference between the two?
A session cookie is stateful: the server stores the session and the cookie just carries an id that points to it. A JWT is stateless: the token itself contains the user's claims, signed so the server can trust it without a lookup. The practical consequence is revocation. You can delete a server session instantly; a JWT stays valid until it expires unless you build extra machinery to revoke it.
Which is more secure?
Neither is inherently more secure; it depends on how you store and handle them. The most common real-world mistake is putting a JWT in localStorage, where any script can read it, so one XSS bug steals the session. An httpOnly session cookie cannot be read by script at all. If you use JWTs, keep them in an httpOnly cookie too, not localStorage, and you remove that gap.
When are JWTs actually the right choice?
When you genuinely need stateless auth: multiple services that must verify a token without sharing a session store, or an API consumed by clients where a bearer token is the natural fit. In those cases the stateless property is a feature. For a single web app with one backend, a session cookie is usually simpler and gives you easy revocation for free, which is worth a lot.
Can I revoke a JWT?
Not directly, which is the catch. A signed JWT is valid until it expires, so to revoke early you need extra infrastructure: a short expiry with refresh tokens, or a denylist of revoked tokens that you check on each request, which reintroduces the server-side state JWTs were meant to avoid. If instant revocation matters to you, that friction is a reason to prefer session cookies.
Check how your app handles sessions
The riskiest part is not which you chose but how it is stored and flagged. A scan flags a token exposed to the browser and a session cookie missing its protections. Run a free scan and see how your app is handling auth.