vM.

JWT vs Session Cookies: How to Choose for a Web App

Author
Vishal Maurya
Published on
Reading time
4 min read

Overview

You are starting a web app and every tutorial seems to say something different: use JWTs because they scale, or use sessions because they are simpler. The question is usually framed as a technology contest, but it is really about where authentication state lives and how quickly you need to change it.

This guide compares the two models and gives a conservative default for most browser-based applications.

1. What You Are Actually Choosing Between

A server-side session stores state on the server, in a database or a store such as Redis. The browser holds an opaque, random session ID in a cookie. On each request the server looks up that ID and finds the user, permissions, and expiry.

A JWT is a signed token that carries claims, such as a user ID and an expiry time, inside itself. The server verifies the signature and trusts the claims without looking anything up.

Note that these are not exclusive categories. A JWT can be delivered in a cookie, and a session ID can be sent in a header. The real distinction is stateful (server remembers) versus stateless (token carries the facts).

2. Revocation Is the Deciding Factor

With a session, logging a user out, banning an account, or forcing a password-reset logout means deleting a row or key. The change takes effect on the next request.

With a self-contained JWT, the token stays valid until it expires unless you add server-side state to track revoked tokens. That means a stolen token keeps working for its remaining lifetime. If you need immediate logout, account suspension, or role changes that apply right away, sessions give you that by default and JWTs make you rebuild part of it.

3. Scaling Is Less Different Than It Sounds

The usual argument for JWTs is that servers do not need a shared session store. That is true, but a session lookup against Redis or an indexed database table is a small cost for most applications. Many apps would be limited by other things long before session lookups become a problem.

Statelessness matters more when several independent services need to verify identity without calling a central auth service, or when you cross organizational boundaries. In a single application with one backend, the scaling benefit is often modest.

4. Where Each Option Tends to Win

Server-side sessions are a strong default when:

  • One backend serves a browser frontend on the same site.
  • You need reliable logout, device management, or "sign out everywhere".
  • Permissions change often and should apply immediately.

JWTs are a reasonable fit when:

  • Multiple services or third parties must verify identity independently.
  • You are issuing short-lived tokens in an OAuth 2.0 or OpenID Connect flow.
  • Clients are not browsers, such as mobile apps or service-to-service calls.

If you use JWTs, keep them short-lived and pair them with a revocation or refresh strategy. Related posts on refresh tokens and revocation cover those details.

Whichever you choose, the cookie attributes matter. This Express example uses the express-session package with a store you configure separately.

import session from 'express-session'

app.use(
  session({
    name: 'sid',
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    // store: configure a persistent store such as Redis for production
    cookie: {
      httpOnly: true,
      secure: true,
      sameSite: 'lax',
      maxAge: 1000 * 60 * 60 * 8,
    },
  })
)

HttpOnly keeps the cookie away from JavaScript, Secure restricts it to HTTPS, and SameSite reduces cross-site request risk. SameSite is not a complete CSRF defense by itself, so add CSRF protection for state-changing requests when your framework recommends it. Also rotate the session ID on login to avoid session fixation.

6. Mistakes Common to Both

  • Storing a JWT in localStorage, which exposes it to any XSS bug.
  • Using long-lived tokens with no way to revoke them.
  • Putting sensitive data in JWT claims. The payload is encoded, not encrypted, unless you use JWE.
  • Accepting any signing algorithm instead of pinning the one you expect.
  • Skipping exp, issuer, and audience validation.

Conclusion

For a typical web app with a single backend and a browser client, server-side sessions with a hardened cookie are usually the simpler and safer starting point. Reach for JWTs when you have a real need for cross-service verification, and plan for revocation before you ship.

If you are deciding on an auth design or want an existing implementation reviewed, I can help assess the tradeoffs for your architecture. Contact me.

Additional Resources

  • OWASP Session Management Cheat Sheet
  • OWASP JSON Web Token Cheat Sheet
  • RFC 7519: JSON Web Token