← all posts

JWT vs sessions: there is no universal answer

·2 min read ·Security · Backend · Authentication

JWT vs sessions is one of those debates where both sides are right, both sides are wrong, and the actual answer depends entirely on your situation. Here's how I think about it.

Sessions: The Sane Default for Web Apps

Session-based auth stores state on the server. The browser holds a session ID in an httpOnly cookie. On each request, the server looks up the session to verify the user.

Advantages:

  • Instant revocation: delete the session and the user is logged out everywhere
  • No token storage concerns—httpOnly cookies are inaccessible to JavaScript
  • CSRF protection is well-understood and built into frameworks

For a standard web application where your frontend and backend share a domain, sessions are the right default. Laravel makes this trivially easy.

JWT: Right for APIs, Wrong When Misused

JWT is stateless—the server doesn't store anything. The token itself contains the claims. This makes it attractive for:

  • APIs consumed by mobile apps that can't use cookies
  • Microservices that need to pass identity between services
  • Cross-domain authentication

The problem is what developers get wrong:

// Don't do this:
localStorage.setItem('token', jwt); // Vulnerable to XSS

// If you must use JWT in a browser, use httpOnly cookies:
// Let the server set it via Set-Cookie header

You also can't revoke a JWT before it expires. If a user changes their password or you suspect compromise, a valid JWT stays valid until expiry. Short TTLs (15 minutes) with refresh token rotation are the mitigation—but that adds complexity.

My Rule

Web app with standard server-rendered or SPA frontend on same domain: sessions.

API consumed by mobile apps, third parties, or cross-domain clients: JWT with short expiry + refresh token rotation.

Never put a JWT in localStorage in a browser. Never make a JWT with a one-year expiry. These are not opinions—they're security mistakes.