Skip to main content

Command Palette

Search for a command to run...

Sessions, Cookies, and JWT: Which Authentication Should You Use?

Updated
9 min read

When I started building my first full-stack project, I hit a wall almost immediately — how does a server know who I am after I log in?

I kept running into terms like sessions, cookies, and JWT tokens, and nobody seemed to explain them together in a way that made sense. So in this article, that's exactly what I want to do.

By the end, you'll understand what each one is, how they differ, and — most importantly — which one to use and when.


The Core Problem: HTTP is Stateless

Before anything else, you need to understand why authentication mechanisms even need to exist.

HTTP — the protocol your browser uses to talk to servers — is stateless. Every request is independent. The server has no memory of you.

So when you log in and then visit your dashboard, the server has no idea it's still you. From its perspective, it's just getting a random request from somewhere on the internet.

Authentication systems solve this: they give the server a way to recognize you across multiple requests.


What Are Sessions?

A session is a way for the server to remember information about a user across requests.

Here's how it works:

  1. You log in with your username and password.
  2. The server verifies your credentials and creates a session — essentially a record that says "User #42 is logged in" — and stores it in memory or a database.
  3. The server generates a unique Session ID (a random string) and sends it back to your browser.
  4. On every subsequent request, your browser sends that Session ID back to the server.
  5. The server looks it up, finds your session data, and knows who you are.
Browser                          Server
  |                                |
  |--- POST /login --------------->|
  |                                | (verifies credentials)
  |                                | (creates session in DB/memory)
  |<-- Set-Cookie: sid=abc123 -----|
  |                                |
  |--- GET /dashboard (sid=abc123)>|
  |                                | (looks up session → "User #42")
  |<-- 200 OK: Dashboard data -----|

The key point: the server holds all the data. The browser only holds a reference (the Session ID) to that data.


What Are Cookies?

Cookies are the delivery mechanism — they're how the Session ID (or any small piece of data) travels back and forth between the browser and server.

A cookie is just a small key-value pair stored in the browser:

sid=abc123xyz; HttpOnly; Secure; SameSite=Strict

When the server sends a Set-Cookie header, the browser saves it. From that point on, the browser automatically attaches that cookie to every request it makes to that domain — you don't have to do anything manually.

Think of a cookie as a wristband at an event. The venue (server) gives it to you at entry (login). Every time you pass a checkpoint (make a request), you show the wristband instead of re-verifying your identity.

Cookies are not just for sessions. They can store any small piece of data — preferences, tracking info, etc. But in the context of authentication, they're most commonly used to carry the Session ID.


What Are JWT Tokens?

JWT stands for JSON Web Token. It's an open standard for securely transmitting information between parties as a compact, self-contained string.

A JWT looks like this:

eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyLCJyb2xlIjoiYWRtaW4ifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

It has three parts separated by dots:

HEADER . PAYLOAD . SIGNATURE
Part What it contains
Header Algorithm used to sign the token (e.g., HS256)
Payload Your actual data — user ID, role, expiry time, etc.
Signature A hash that proves the token hasn't been tampered with

The payload is just Base64 encoded, not encrypted — so don't store sensitive data like passwords in it. The signature is what makes it trustworthy. It's generated using a secret key that only your server knows.

Here's what a decoded JWT payload looks like:

{
  "userId": 42,
  "role": "admin",
  "iat": 1716000000,
  "exp": 1716086400
}

When you log in, the server creates this token, signs it, and sends it to you. You store it (in localStorage or a cookie) and attach it to every future request — usually in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

The server receives the token, verifies the signature, reads the payload, and knows who you are — without looking anything up in a database.


Stateful vs Stateless Authentication

This is the fundamental difference between sessions and JWT, and it's worth understanding clearly.

Stateful Authentication (Sessions)

The server stores state — it keeps a record of every active session.

User logs in → Server creates session → Stores it in DB → Gives you an ID
Next request → You send ID → Server looks it up → Finds your data

The server must maintain and query this session store on every request. The session ID alone is meaningless without the server-side record.

Stateless Authentication (JWT)

The server stores nothing — all the information is embedded inside the token itself.

User logs in → Server creates JWT with user data → Signs it → Sends it to you
Next request → You send JWT → Server verifies signature → Reads data directly

No database lookup. No shared state. The token is self-contained.

This is the core trade-off: stateful = server remembers you, stateless = you carry your own proof of identity.


Session-Based Auth vs JWT: Full Comparison

Session-Based Auth JWT-Based Auth
Where state lives Server (DB or memory) Client (the token itself)
Server lookup per request Yes — checks session store No — just verifies signature
Scalability Harder — all servers need access to the same session store Easier — any server can verify the token independently
Revocation Easy — delete the session from the store Hard — tokens are valid until they expire
Token size Small (just an ID) Larger (carries actual data)
Storage on client Cookie (automatic) localStorage, cookie, or memory
Best for Traditional web apps, monoliths APIs, microservices, mobile apps
Logout reliability Instant and guaranteed Not instant (token remains valid until expiry)

The Revocation Problem with JWT

This deserves a separate mention because it catches a lot of developers off guard.

With sessions, logging out is simple — you delete the session from the server. The Session ID becomes worthless immediately.

With JWT, you cannot invalidate a token early. Once issued, it's valid until it expires. If a user logs out, their token still technically works until the expiry time passes.

Common workarounds:

  • Short expiry times (e.g., 15 minutes) + a refresh token mechanism
  • Maintaining a token blacklist on the server (but now you're adding state back, somewhat defeating the purpose) This isn't a dealbreaker — it just means JWT logout is a design consideration, not a solved problem out of the box.

When to Use Sessions

Sessions are a solid choice when:

  • You're building a traditional web application where the backend renders HTML (like an Express + EJS or Django app).
  • You need instant logout — for example, banking applications, admin panels, or anything security-sensitive where revoking access immediately matters.
  • Your app runs on a single server or a small cluster where sharing a session store (like Redis) is straightforward.
  • Your users are primarily on web browsers (cookies work seamlessly here).

When to Use JWT

JWT makes more sense when:

  • You're building a REST API consumed by a mobile app, single-page application (React, Vue, etc.), or third-party clients.
  • You're working with a microservices architecture where multiple independent services need to verify a user's identity without calling a central session store.
  • You want to avoid server-side state entirely for scalability — horizontal scaling becomes trivial because any instance can verify any token.
  • You need cross-domain authentication — JWT works easily across different domains, while cookies have restrictions.

A Practical Decision Framework

If you're staring at a new project and wondering which to pick:

Is your app a traditional server-rendered web app?
  → Use Sessions
 
Is your app a REST API or consumed by mobile clients?
  → Use JWT
 
Do you need instant, guaranteed logout?
  → Use Sessions
 
Are you building microservices or need horizontal scaling?
  → Use JWT
 
Do you need cross-domain auth (e.g., SSO across subdomains)?
  → Use JWT

And honestly — for many projects, neither is wrong. Both are used in production at massive scale. The decision comes down to your architecture, not which one is "better."


Quick Recap

  • Cookies — a browser mechanism that stores small data and automatically sends it with every request. Used to carry Session IDs (or tokens).
  • Sessions — server-side records of who is logged in. Stateful. Great for traditional apps and when you need reliable logout.
  • JWT — self-contained tokens that carry user data and are verified by signature. Stateless. Great for APIs, microservices, and mobile apps.
  • Stateful means the server holds your session. Stateless means the token holds everything.
  • The biggest JWT trade-off: you can't easily revoke a token before it expires.

Understanding authentication at this level — not just how to implement it but why each approach exists — is what separates developers who follow tutorials from developers who make architecture decisions.