DEV Community

Cover image for Cookies vs Sessions vs JWT: What's the Difference?
Tanu Priya
Tanu Priya

Posted on

Cookies vs Sessions vs JWT: What's the Difference?

When you build a login system, you'll quickly run into three terms:

Cookies, sessions, and JWTs.

They are often mentioned together, which makes it easy to assume that they are three different ways of doing the exact same thing.

They're not.

A cookie is mainly a way for a browser to store and send data.

A session is server-side state that can be used to remember a logged-in user.

A JWT is a signed token that can carry information about an authenticated user.

The confusion usually starts because these things can also be used together.

For example:

Browser
   ↓
Cookie
   ↓
Session ID
   ↓
Server-side Session
Enter fullscreen mode Exit fullscreen mode

Or:

Browser
   ↓
Cookie
   ↓
JWT
Enter fullscreen mode Exit fullscreen mode

So instead of asking:

"Which one is better?"

it's more useful to understand what each one actually does and then decide which combination fits your application.

First, What Problem Are We Solving?

Let's start with a normal login.

A user sends:

POST /api/login
Enter fullscreen mode Exit fullscreen mode

with:

{
  "email": "alex@example.com",
  "password": "mypassword"
}
Enter fullscreen mode Exit fullscreen mode

The backend verifies the credentials.

Let's say they are correct.

The server now knows:

User ID: 42
Enter fullscreen mode Exit fullscreen mode

But what happens when the user makes another request?

GET /api/profile
Enter fullscreen mode Exit fullscreen mode

HTTP doesn't automatically remember that the previous request was a successful login.

The application needs a way to recognize the user again.

That's the problem these authentication mechanisms help solve.


Cookies: How Does the Browser Remember Something?

Let's start with cookies because they are the easiest to misunderstand.

A cookie is not an authentication system by itself.

It's a small piece of data that a browser can store and send back to a website.

A server can send:

Set-Cookie: session_id=abc123
Enter fullscreen mode Exit fullscreen mode

The browser stores it.

Later, when making another request to the relevant site, the browser can send:

Cookie: session_id=abc123
Enter fullscreen mode Exit fullscreen mode

That's the basic idea.

You can think of a cookie as a container for a value that the browser knows how to send back to the server.

That value could be:

session_id
authentication-related data
preferences
language settings
analytics identifiers
Enter fullscreen mode Exit fullscreen mode

So:

Cookie ≠ Session
Cookie ≠ JWT
Enter fullscreen mode Exit fullscreen mode

A cookie is a transport/storage mechanism.

What you put in it determines how it's being used.


Sessions: Keep the Login State on the Server

Now let's look at sessions.

After a successful login, the server can create a session:

Session ID: abc123
User ID: 42
Enter fullscreen mode Exit fullscreen mode

The actual session information stays on the server.

Conceptually:

abc123 → User 42
Enter fullscreen mode Exit fullscreen mode

The browser only needs to keep the session identifier.

That's where cookies often come in.

The server sends:

Set-Cookie: session_id=abc123
Enter fullscreen mode Exit fullscreen mode

The browser stores it.

On a later request:

GET /api/profile
Cookie: session_id=abc123
Enter fullscreen mode Exit fullscreen mode

the server reads the session ID and looks up the corresponding session.

The flow is:

Login
  ↓
Verify credentials
  ↓
Create session
  ↓
Store session on server
  ↓
Send session ID in cookie
  ↓
Browser stores cookie
  ↓
Browser sends cookie later
  ↓
Server finds session
  ↓
User identified
Enter fullscreen mode Exit fullscreen mode

This is why you will often see cookies and sessions used together.

The cookie carries the identifier.

The session holds the state.


A Simple Example

Imagine a server has this session store:

abc123 → User 42
xyz789 → User 91
Enter fullscreen mode Exit fullscreen mode

Alex logs in and receives:

session_id=abc123
Enter fullscreen mode Exit fullscreen mode

When Alex requests:

GET /api/profile
Cookie: session_id=abc123
Enter fullscreen mode Exit fullscreen mode

the server can do:

abc123
  ↓
Session Store
  ↓
User 42
  ↓
Fetch profile
Enter fullscreen mode Exit fullscreen mode

The browser never needs to know what is stored inside the server-side session.

That's one of the useful properties of this approach.


So Where Does JWT Come In?

JWT stands for JSON Web Token.

Instead of giving the client a random session ID that points to server-side authentication state, the server can issue a signed token containing claims.

A simplified payload might look like:

{
  "sub": "42",
  "role": "user",
  "exp": 1760003600
}
Enter fullscreen mode Exit fullscreen mode

The server signs the token.

The resulting JWT looks roughly like:

Header.Payload.Signature
Enter fullscreen mode Exit fullscreen mode

The client can then send it with future API requests.

A common format is:

Authorization: Bearer eyJ...
Enter fullscreen mode Exit fullscreen mode

The backend receives the token and verifies its signature and relevant claims.

Conceptually:

Login
  ↓
Verify credentials
  ↓
Create JWT
  ↓
Sign JWT
  ↓
Send JWT to client
  ↓
Client sends JWT
  ↓
Server verifies JWT
  ↓
Identify user
Enter fullscreen mode Exit fullscreen mode

That's the basic JWT model.


The Important Difference

At this point, the three terms should start looking different.

What it does
Cookie Stores and sends data through the browser
Session Keeps authentication state on the server
JWT Carries signed claims that a server can verify

This is why comparing them as if they were direct alternatives can be confusing.

They're not really at the same layer.

For example, you can have:

Cookie + Session
Enter fullscreen mode Exit fullscreen mode

or:

Cookie + JWT
Enter fullscreen mode Exit fullscreen mode

or:

Authorization Header + JWT
Enter fullscreen mode Exit fullscreen mode

Those are all different architectures.


Cookie + Session

This is a common pattern for traditional web applications.

The architecture looks like:

             Browser
                |
             Cookie
                |
          Session ID
                |
                ↓
        Server-side Session
                |
             User ID
Enter fullscreen mode Exit fullscreen mode

For example:

Cookie:
session_id=abc123
Enter fullscreen mode Exit fullscreen mode

Server:

abc123 → User 42
Enter fullscreen mode Exit fullscreen mode

The browser carries the identifier.

The server owns the actual session state.

One practical advantage is that the server can invalidate the session directly.

For example:

Logout
  ↓
Invalidate session
  ↓
Session ID no longer works
Enter fullscreen mode Exit fullscreen mode

This can make server-side session management convenient.


Cookie + JWT

A JWT doesn't have to be stored in JavaScript-accessible browser storage.

It can also be placed in a cookie.

For example:

Set-Cookie: access_token=eyJ...; HttpOnly; Secure
Enter fullscreen mode Exit fullscreen mode

The browser then sends the cookie with appropriate requests.

The architecture becomes:

Browser
   ↓
Cookie
   ↓
JWT
   ↓
Server verifies JWT
Enter fullscreen mode Exit fullscreen mode

Here the cookie is handling browser storage/transmission while the JWT is the authentication credential.

Again:

Cookie ≠ JWT
Enter fullscreen mode Exit fullscreen mode

They are doing different jobs.


JWT in the Authorization Header

Another common pattern is to send the JWT explicitly:

GET /api/profile
Authorization: Bearer eyJ...
Enter fullscreen mode Exit fullscreen mode

This is common when working with APIs where the client is responsible for attaching the token.

The server receives it and performs verification.

Request
   ↓
Authorization header
   ↓
JWT
   ↓
Verify token
   ↓
Identify user
   ↓
Process request
Enter fullscreen mode Exit fullscreen mode

This pattern is common in applications where the frontend, mobile app, or another client communicates with a backend API.

But having an API does not automatically mean JWT is required.

That's an important distinction.


What Does "Stateless JWT" Mean?

One reason JWTs are popular is that they can support a stateless authentication model.

With a traditional session:

Session ID
    ↓
Server
    ↓
Session Store
    ↓
User
Enter fullscreen mode Exit fullscreen mode

The server needs access to the session state.

With a self-contained JWT:

JWT
 ↓
Verify signature
 ↓
Read claims
 ↓
Identify subject
Enter fullscreen mode Exit fullscreen mode

The server can potentially authenticate the request without looking up a traditional session record.

This can be useful when an application has multiple backend servers:

                 Load Balancer
                 /     |     \
                /      |      \
          Server A  Server B  Server C
                \      |      /
                 \     |     /
                 Verify JWT
Enter fullscreen mode Exit fullscreen mode

Each server can verify the token using the appropriate key.

However, calling JWT authentication "stateless" doesn't mean the entire authentication system has zero state.

Applications may still need state for:

Refresh tokens
Revocation
Device sessions
Security events
User sessions
Enter fullscreen mode Exit fullscreen mode

So stateless JWT authentication is a design choice, not a guarantee that the whole system becomes state-free.


What About Security?

This is where the choice becomes more interesting.

For browser applications, cookies have security-related attributes such as:

HttpOnly
Secure
SameSite
Enter fullscreen mode Exit fullscreen mode

For example:

Set-Cookie: session_id=abc123;
HttpOnly;
Secure;
SameSite=Lax
Enter fullscreen mode Exit fullscreen mode

HttpOnly prevents ordinary JavaScript from reading the cookie directly.

Secure restricts the cookie to HTTPS connections.

SameSite controls when the browser sends the cookie in cross-site situations.

These options are useful, but they don't magically make authentication secure.

The application still needs to consider things like:

HTTPS
CSRF
XSS
Token theft
Session fixation
Password security
Session expiration
Authorization
Enter fullscreen mode Exit fullscreen mode

The correct security setup depends on the application.


What About localStorage?

You may have seen JWTs stored like this:

localStorage.setItem("token", token);
Enter fullscreen mode Exit fullscreen mode

It is convenient.

But there's an important trade-off.

JavaScript running on the page can read values stored in localStorage.

So if an attacker manages to execute malicious JavaScript through an XSS vulnerability, a token stored there may be accessible to that script.

This doesn't mean:

localStorage = always insecure
Enter fullscreen mode Exit fullscreen mode

or:

cookies = automatically secure
Enter fullscreen mode Exit fullscreen mode

Security is more complicated than that.

It means you should understand what each storage mechanism exposes and choose based on your application's threat model.


What Happens During Logout?

This is one of the places where sessions and JWTs behave differently.

With a server-side session:

Logout
  ↓
Invalidate session
  ↓
Session ID becomes useless
Enter fullscreen mode Exit fullscreen mode

The server can immediately stop recognizing that session.

With a self-contained access JWT, the server may continue accepting the token until it expires unless the application has an additional revocation mechanism.

That's one reason JWT-based systems often use short-lived access tokens.

For example:

Access token
   ↓
15-minute lifetime
Enter fullscreen mode Exit fullscreen mode

A refresh mechanism can then be used to obtain another access token when necessary.

A common flow is:

Login
  ↓
Access Token + Refresh Token
  ↓
Use Access Token
  ↓
Access Token expires
  ↓
Use Refresh Token
  ↓
Get new Access Token
Enter fullscreen mode Exit fullscreen mode

Refresh-token handling needs its own security design.


Scaling Sessions

Now imagine your application grows.

Initially:

Users
  ↓
Server
  ↓
Database
Enter fullscreen mode Exit fullscreen mode

Later:

             Load Balancer
                  ↓
          ┌───────┼───────┐
          ↓       ↓       ↓
       Server A Server B Server C
Enter fullscreen mode Exit fullscreen mode

With server-side sessions, you need to decide where those sessions live.

For example:

Server A ──┐
Server B ──┼──→ Shared Session Store
Server C ──┘
Enter fullscreen mode Exit fullscreen mode

A shared store allows different servers to find the same session.

This adds infrastructure, but it is a perfectly reasonable architecture.

With JWTs, each server can potentially validate the token independently:

Server A → Verify JWT
Server B → Verify JWT
Server C → Verify JWT
Enter fullscreen mode Exit fullscreen mode

That can simplify some scaling scenarios.

But it doesn't eliminate the other problems involved in distributed authentication.

You still have to think about:

Key management
Token expiration
Refresh tokens
Revocation
Authorization
Enter fullscreen mode Exit fullscreen mode

So Which One Should You Use?

This is the part where generic tutorials often go wrong.

They say:

"Use JWT because it's modern."

That's not a useful architectural decision.

Start with the application.

A traditional web application

If most of your application is server-rendered and the server already owns the user's session state, a session-based approach can be a straightforward choice:

Browser
  ↓
Secure Cookie
  ↓
Session ID
  ↓
Server Session
Enter fullscreen mode Exit fullscreen mode

A frontend + backend API

If you have separate clients communicating with an API, token-based authentication may fit naturally:

Web App
Mobile App
Desktop App
      ↓
     API
      ↓
Authentication
Enter fullscreen mode Exit fullscreen mode

JWT can be useful here, but it isn't mandatory.

Cookies and sessions can also be used with APIs.

A distributed system

If several services need to verify the same authentication information, signed tokens can be convenient:

             API Gateway
                  ↓
       ┌──────────┼──────────┐
       ↓          ↓          ↓
   Service A  Service B  Service C
       ↓          ↓          ↓
       └──── Verify identity ──┘
Enter fullscreen mode Exit fullscreen mode

Again, the architecture determines whether that benefit actually matters.


The Questions I Would Ask Before Choosing

Instead of starting with:

"Should I use JWT?"

I'd ask:

Who are the clients?

Is the application browser-based?

Do we need server-side session state?

How should logout work?

Do we need immediate credential revocation?

How long should authentication credentials live?

How will refresh work?

Where will credentials be stored?

Do multiple services need to verify the identity?

What threats does the application need to handle?
Enter fullscreen mode Exit fullscreen mode

These questions tell you much more than simply choosing a technology because it's popular.


The Mental Model

If you remember only one thing from this article, remember this:

Cookie
→ How the browser stores and sends a value

Session
→ Authentication state maintained by the server

JWT
→ A signed token containing claims
Enter fullscreen mode Exit fullscreen mode

And they can be combined:

Cookie + Session
Enter fullscreen mode Exit fullscreen mode

or:

Cookie + JWT
Enter fullscreen mode Exit fullscreen mode

or:

Authorization Header + JWT
Enter fullscreen mode Exit fullscreen mode

They're not mutually exclusive.

That's the part that usually clears up the confusion.

Final Takeaway

Cookies, sessions, and JWTs are often discussed together, but they solve different problems.

A cookie is a mechanism for storing and sending data through the browser.

A session is server-side state that can be used to remember an authenticated user.

A JWT is a signed token that carries claims which a server can verify.

The right architecture depends on what you're building.

A small server-rendered application might be perfectly comfortable with cookies and server-side sessions.

A system serving multiple types of clients might benefit from token-based authentication.

A distributed system might find signed tokens useful for passing verifiable identity information between services.

None of those choices makes one technology universally better than the others.

The better question isn't:

"JWT or sessions?"

It's:

"Where should authentication state live, how should the client carry it, and how should the server verify and revoke it?"

Once you start thinking in those terms, cookies, sessions, and JWTs stop looking like competing technologies and start looking like different pieces of an authentication architecture.

Top comments (0)