WritingJWT vs Session Cookies: A Security Comparison for Web Applications — Clixo
6 min readjwt, session-management, authentication, cookies, web-security

JWT vs Session Cookies: A Security Comparison for Web Applications

Compare JWT and session cookies for web app authentication security — storage risks, XSS exposure, revocation, CSRF, and when to use each approach.

The choice between JWTs and session cookies is one of the most argued topics in web authentication, and much of the argument conflates convenience with security. Both approaches can be implemented securely. Both can be implemented insecurely. The difference is not which you choose — it is what the tradeoffs of each choice actually mean for your threat model.

This comparison focuses specifically on the security properties of each approach, not on scalability or developer ergonomics.

JWT vs Session Cookies: The Core Security Properties

How each approach stores credentials

A session cookie stores a random, opaque identifier (the session ID) on the client. The server maps that identifier to session data — typically in a database, Redis, or another store. The session cookie has no inherent meaning outside the server that issued it.

A JWT (JSON Web Token) stores the actual claims — user ID, roles, permissions, expiry — in a signed (and optionally encrypted) token on the client. The server validates the signature but does not need to look up anything server-side to verify a valid token.

This architectural difference drives most of the security tradeoffs.

Where tokens are stored

Session cookies are stored as HttpOnly cookies, which means JavaScript cannot access them. This is a meaningful XSS defense: an XSS attacker who injects script into your page cannot read an HttpOnly cookie, so stealing the session token through script injection is not possible by default.

JWTs are frequently stored in localStorage or sessionStorage for ease of use in single-page applications. Both are fully accessible to JavaScript. Any XSS vulnerability in the application gives an attacker direct access to the JWT, which they can then exfiltrate and use from any origin until the token expires.

The alternative — storing JWTs in HttpOnly cookies — is possible and recovers the XSS protection. But it also reintroduces CSRF risk, which requires the same mitigations (SameSite cookies, CSRF tokens) as session cookie approaches.

Token revocation

Session cookies can be revoked instantly. The server deletes the session record, and the session ID the client holds is immediately invalid. Forced logout, account suspension, and credential rotation work reliably.

JWTs cannot be revoked without additional infrastructure. Once issued, a JWT is valid until its expiry time (exp claim). If a user's account is compromised, if they log out on one device, or if their permissions change, tokens already in circulation remain valid until they expire.

Workarounds exist — a server-side token blocklist or a short-lived JWT paired with a longer-lived refresh token mechanism — but both add complexity and partially undermine the stateless property that makes JWTs attractive in the first place. If you maintain a blocklist to enable revocation, you have effectively reintroduced server-side state.

CSRF risk

Session cookies are automatically attached to same-site and (depending on SameSite settings) cross-site requests by the browser. This automatic behavior is what makes CSRF possible. Session cookie-based applications require explicit CSRF protection via SameSite cookie attributes, synchronizer tokens, or fetch metadata policies.

JWTs stored in localStorage are immune to CSRF in their default form. Cross-site requests from an attacker-controlled page cannot read localStorage values, so the token cannot be attached. However, if an XSS vulnerability exists, the XSS can read and attach the token — trading CSRF risk for XSS risk.

JWTs stored in HttpOnly cookies carry the same CSRF risk as session cookies and require the same mitigations.

Algorithm confusion and signature validation

JWTs carry an alg header that specifies the signing algorithm. Early JWT libraries trusted this header unconditionally, allowing attackers to switch the algorithm to none and present unsigned tokens as valid. Modern libraries reject this, but only if you explicitly configure them to require a specific algorithm rather than accepting whatever the token claims.

When implementing JWT validation:

  • Specify the expected algorithm explicitly in your validation configuration; do not trust the alg header
  • Use asymmetric algorithms (RS256, ES256) in distributed systems where multiple services validate tokens, so private signing keys are held by one service and only public keys are distributed
  • Validate the iss, aud, and exp claims on every request

Sensitive data in the payload

JWTs are signed, not encrypted by default. The payload is base64-encoded and readable by anyone who intercepts or obtains the token. Do not store sensitive values — PII, permissions that should not be visible to the user, internal IDs that expose your data model — in the JWT payload unless the token is also encrypted (JWE).

When to Use Session Cookies vs JWTs

Use session cookies when:

  • Your application is a traditional server-rendered application or an SPA with a backend-for-frontend pattern
  • You need reliable forced logout and immediate revocation
  • Your users log in once and maintain a persistent session
  • You want the simplest, most mature authentication pattern with the least moving parts

JWTs are well-suited when:

  • You are issuing short-lived tokens (minutes to an hour) for stateless service-to-service authentication
  • You are building a distributed system where multiple independent services need to validate tokens without a shared session store
  • You need to pass verified claims across trust boundaries without a network call

Avoid JWTs when:

  • The token is long-lived (hours or days) and you cannot implement reliable revocation
  • You are storing them in localStorage in an application that also renders user-supplied HTML
  • You are using them as a drop-in replacement for sessions in a monolithic application without a clear need for stateless verification

The Security-Practical Recommendation

For most web applications — particularly SPAs with a dedicated backend — the most secure and operationally simple pattern is a backend-for-frontend that issues HttpOnly; Secure; SameSite=Lax session cookies, with SameSite providing the primary CSRF protection. Use JWTs internally for service-to-service calls with short expiry windows.

Neither approach is inherently superior. The right choice depends on your architecture, your revocation requirements, and your XSS posture.

Need help designing an authentication architecture that fits your application's actual threat model? Start a build.