JWT vs Cookie: Why Comparing the Two Is Misleading
Tags: auth, javascript, cloudflareworker
Published: 2023-11-10 00:13:58

---
title: JWT vs Cookie: Why Comparing the Two Is Misleading
published: true
date: 2022-11-01 00:00:19 UTC
tags: Auth,JavaScript,CloudflareWorker
canonical_url: https://jerrynsh.com/all-to-know-about-auth-and-cookies/
---

 ![JWT vs Cookie: Why Comparing the Two Is Misleading](https://images.unsplash.com/photo-1558961363-fa8fdf82db35?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGNvb2tpZXxlbnwwfHx8fDE2NzUyNTMyOTE&ixlib=rb-4.0.3&q=80&w=2000)

There is a lot of confusion about cookies, sessions, token-based authentication, and JWT.

Today, I want to clarify what people mean when they talk about “_JWT vs Cookie_, “_Local Storage vs Cookies_”, “_Session vs token-based authentication_”, and “_Bearer token vs Cookie_” once and for all.

Here’s a hint — we should stop comparing JWT vs Cookies!

Along the line, I’ll go through what XSS and CSRF attacks are and how to prevent them using token-based authentication with JWT and CSRF tokens.

Let's begin!

## Anatomy

To start, it’s important to know the differences between some of these terminologies.

Without explicitly stating these, it would be unclear for us to compare things properly.

### Client

Our client application. In this context, we are specifically talking about our web browsers, e.g. Firefox, Brave, Chrome, etc.

### Server

Computers that are doing all the magic behind the curtains.

### Request/Response Headers

[HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers). Note that they are case-insensitive.

### Cookie

a.k.a “HTTP cookie”, “web cookie”, or “browser cookie”.

A small piece of information that a server sends back to the client.

Stored in the browser’s Cookies storage, cookies are typically used for authentication, personalization, and tracking.

A cookie is received in name-value pairs via the `Set-Cookie` response header in a request. With this, your cookie will automatically be kept in the browser’s Cookies storage (`document.cookie`).

![JWT vs Cookie: Why Comparing the Two Is Misleading](https://jerrynsh.com/content/images/2022/10/image-2.png)
_An example of how a Cookie is received. You should never publicly share your JWT! (this JWT is no longer in use)._

Cookies with `HttpOnly`, `Secure`, and `SameSite=Strict` flags are more secure.

For example, with the `HttpOnly` flag, the cookies are not accessible through JavaScript, thus making it immune to XSS attacks.

![JWT vs Cookie: Why Comparing the Two Is Misleading](https://jerrynsh.com/content/images/2022/10/image-3.png)
_With HttpOnly, the cookie is not shown_

[Read more on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and check out what other flags do (they are handy).

### XSS Attack

a.k.a “ **Cross** - **S** ite **S** cripting” attack.

For context, the Web Storage (e.g. Local Storage) is accessible through JavaScript on the same domain. Consequently, Web Storage is vulnerable to XSS attacks.

In short, XSS is a type of vulnerability where an attacker **injects JavaScript** that will run on your page.

Basic XSS attacks attempt to inject JavaScript through form inputs, where the attacker puts an `alert(localStorage.getItem('your-secret-token'))` into a form to see if it is run by the browser and can be viewed by other users.

If you still don’t get what XSS is, check out [this “XSS Explained” video](https://www.youtube.com/watch?v=EoaDgUgS6QA).

### CSRF Attack

a.k.a “ **C** ross- **S** ite **R** equest **F** orgery” attack.

Cookies are vulnerable to CSRF attacks. **No cookies = no CSRF attacks**.

As browsers automatically send Cookies with all requests, CSRF attacks make use of this to gain authenticated access to a trusted site. Need more? [Read CSRF in simple words](https://stackoverflow.com/a/33829607/10067850).

To protect your site against CSRF attacks while using Cookies (with `SameSite=None`), check out this [StackOverflow answer](https://stackoverflow.com/questions/34782493/difference-between-csrf-and-x-csrf-token/34783845#34783845). Next, read more about [CSRF prevention on Wikipedia](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Prevention) (Wikipedia is generally too dry to my liking, but this is really good!).

Still don’t get what CSRF is? Check out [this “CSRF Explained” video](https://www.youtube.com/watch?v=eWEgUcHPle0).

### Cookies Storage

a.k.a “Cookie Jar”, or “Cookies”. _Yup. I can already see why it is confusing for many._

Client-side storage where HTTP cookies are stored.

Here’s an important note — **browsers automatically send cookies** (no client-side code needed) along with every request via the `cookie` request header. This is exactly why Cookie (storage) is vulnerable to CSRF attacks.

![JWT vs Cookie: Why Comparing the Two Is Misleading](https://jerrynsh.com/content/images/2022/10/image-4.png)
_XSS attacks can be prevented when using Cookies storage if the cookie is set with the HttpOnly flag._

![JWT vs Cookie: Why Comparing the Two Is Misleading](https://jerrynsh.com/content/images/2022/10/image-5.png)
_To view Cookies: F12 → ‘Application’ → ‘Storage’ → ‘Cookies’_

### Web Storage

a.k.a “Local/Session Storage”.

Client-side storage. They are typically used to store data in key-value pairs.

**Vulnerable to XSS** attacks. Hence, not ideal for storing private/sensitive/authentication-related data.

![JWT vs Cookie: Why Comparing the Two Is Misleading](https://jerrynsh.com/content/images/2022/10/image-6.png)
_You can get any item on your Local Storage using JavaScript_

![JWT vs Cookie: Why Comparing the Two Is Misleading](https://jerrynsh.com/content/images/2022/10/image-7.png)
_To view Local Storage items: F12 → ‘Application’ → ‘Storage’ → ‘Local Storage’ / ’Session Storage’_

`sessionStorage` — data is persisted only for the duration of the page session

`localStorage` — data is persisted even when the browser is closed and reopened

[Read more on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API).

### Cookies (Storage) vs Web Storage
<!--kg-card-begin: markdown-->

| | Local/Session Storage | Cookies (Storage) |
| --- | --- | --- |
| JavaScript | Accessible through JavaScript on the same domain | Cookies, when used with the HttpOnly cookie flag, are not accessible through JavaScript |
| XSS attacks | Vulnerable to XSS attacks | Immune to XSS (with HttpOnly flag) |
| CSRF attacks | Immune to CSRF attacks | Vulnerable to CSRF attacks |
| Mitigation | Do not store private/sensitive/authentication-related data here | Make use of CSRF tokens or [other prevention methods](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Prevention) |

<!--kg-card-end: markdown-->
## JWT

a.k.a “JSON Web Tokens”.

Commonly used for [authentication and authorization](https://www.okta.com/identity-101/authentication-vs-authorization/#authentication-vs-authorization-2).

JWT is an open standard ([RFC 7519](https://tools.ietf.org/html/rfc7519)). Meaning all JWTs are tokens.

Typically, JWT is stored in Local Storage or Cookies (storage).

Remember, **JWT is**  **not encrypted** by any means. Rather, it is encoded in Base64. Try decoding any JWT on [jwt.io](https://jwt.io/).

### So, why use JWT?

Often used with token-based authentication, horizontal scaling is easier when using JWT.

Why? The verification of JWT does not require any communication between the servers and databases. In other words, the authentication can be **stateless**.

[Read more on Auth0](https://auth0.com/docs/secure/tokens/json-web-tokens).

### Stop comparing JWT & Cookie

Neither JWT nor Cookie are authentication mechanisms on their own.

JWT is simply a [token format](https://www.rfc-editor.org/rfc/rfc7519).

A cookie is an [HTTP state management mechanism](https://www.rfc-editor.org/rfc/rfc6265) really.

As demonstrated, a web cookie can contain JWT and can be stored within your browser’s Cookies storage.

So, we need to stop comparing JWT vs Cookie.

## Session-based vs Token-based Authentication

Rather, the right question to ask is “What is the difference between token-based authentication and session-based authentication?”

<!--kg-card-begin: markdown-->

| Token-based | Session-based |
| --- | --- |
| Stateless | Stateful |
| The authentication state is NOT stored anywhere on the server-side | The authentication state is stored on the server side (DB) |
| Easier to scale horizontally | [Harder to scale horizontally](https://www.authgear.com/post/session-vs-token-authentication#:~:text=in%20recent%20times.-,Limited%20Scalability,-Since%20Cookies%20are) |
| Commonly uses JWT for authentication | Commonly uses Session ID |
| Typically sent to the server via an HTTP Request `Authorization` Header (e.g. `Bearer <token>)`. Can use `Cookie` too | Usually sent to the server in the `Cookie` request header |
| Harder to revoke a user session | Able to revoke user session with ease |

<!--kg-card-end: markdown-->
## Cookie vs Bearer Tokens

Now, we know how cookies work. Let’s take a stab at the term “Bearer tokens”. Let’s assume we’ll use JWT as our authentication token from hereon.

What people call a “Bearer token” is a string (e.g. JWT) that goes into the `Authorization` header of any HTTP request. Unlike a browser cookie, it is not automatically stored anywhere, thus making this **CSRF impossible**.

```
GET <http://www.example.com>
Authorization: Bearer my_bearer_token_value // HTTP Request Header

```

To make use of a “Bearer token”, we’ll need to explicitly store the JWT somewhere in our client (Cookies storage or Local Storage) and add that JWT to our HTTP `Authorization` header while making requests.

If your cookie (e.g. with a JWT) is set with the `HttpOnly` flag, retrieving your token from the client side would be impossible with JavaScript.

### “Wait, how about we use Local Storage then?”

Remember, using Local Storage makes our JWT vulnerable to XSS. As a result, you’ll often hear people advise strongly against storing JWT in Local Storage.

At this point, it may sound like using Cookie to store JWT is our only option. But remember, this makes our website vulnerable to CSRF attacks!

## CSRF Prevention

### Same-site Cookies

[Same-site cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) can effectively prevent CSRF attacks. Though, it comes with other caveats. [Read more here](https://security.stackexchange.com/questions/121971/will-same-site-cookies-be-sufficient-protection-against-csrf-and-xss/121986#121986).

What follows assumes that we're not going to use SameSite cookies.

### Common CSRF prevention methods

Leaving JWT behind for a bit, these 2 are some of the most common CSRF prevention methods:

- [Synchronizer token pattern](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Synchronizer_token_pattern)
- [Cookie-to-header-token pattern](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token)

Check out this [StackOverflow answer](https://stackoverflow.com/questions/34782493/difference-between-csrf-and-x-csrf-token/34783845#34783845) for a quick summary of the 2 approaches above.

Cool. But now comes the question — how can we do this with JWT?

### The Modified "Cookie-to-header token" Approach

Honestly, I’m not too sure if this approach has a proper name. I found this approach from this wonderful talk about [100% Stateless with JWT (JSON Web Token) by Hubert Sablonnière](https://youtu.be/67mezK3NzpU). I highly, highly recommend you to check it out. It’s worth the hour.

In short, the modified approach (in my opinion) looks similar to the original [Cookie-to-header token approach](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token) except with a few tweaks:

- the anti-CSRF token is returned in a separate response header (e.g. `X-CSRF-Token`) instead of the `Set-Cookie` response header
- we sign and set a JWT on the `Set-Cookie` response header

![JWT vs Cookie: Why Comparing the Two Is Misleading](https://jerrynsh.com/content/images/2022/10/image-8.png)
_This implementation can be found on this Cloudflare Worker demo and GitHub._

**Explanation:**

1. The user logs in, the server would sign a JWT with `csrfToken` as part of the JWT claim (for verification in Step 6).

```
{
  "email": "your@email.com",
  "exp": 1666798498,
  "csrfToken": "1449bd3e-41c2-45cb-a538-73c7ad80ca2c",
  "iat": 1666794898
}

```

The generated `csrfToken` should be unpredictable and unique per-user session.

2. The JWT would then be stringified into a cookie which will be set into the `Set-Cookie` response header. The randomly generated `csrfToken` on the other hand will be set in the `X-CSRF-Token` response header.

3. With the `Set-Cookie` header present in the response header, our browser would automatically store the JWT in the Cookies (storage). The `csrfToken` present in the `X-CSRF-Token` header will be extracted and set in the browser’s Local Storage.

4. When a request (e.g. GET /hello) is triggered, our browser will fetch the `csrfToken` from the Local Storage.

5. The JWT from the Cookies (storage) and the `csrfToken` retrieved from the Local Storage will be sent to the server in the request header.

6. The server will verify the JWT and check  `csrfToken` from the request header against the `csrfToken` claim inside the JWT to verify if the CSRF Token is valid.

## Demo

The screenshots in this post are taken from [this Cloudflare Worker demo](https://worker-auth.jerrynsh.workers.dev/) I made. The source code of this demo can be found on GitHub.

[

GitHub - ngshiheng/worker-auth: To demonstrate and implement a PoC to protect your site against XSS & CSRF attacks.

To demonstrate and implement a PoC to protect your site against XSS &amp; CSRF attacks. - GitHub - ngshiheng/worker-auth: To demonstrate and implement a PoC to protect your site against XSS &amp; C...

 ![JWT vs Cookie: Why Comparing the Two Is Misleading](https://github.com/fluidicon.png)GitHubngshiheng

 ![JWT vs Cookie: Why Comparing the Two Is Misleading](https://opengraph.githubassets.com/05f635bb7d357df2cec403267207b2a31ae018ac922945efdd454f3e9229f17f/ngshiheng/worker-auth)
](https://github.com/ngshiheng/worker-auth)

To test a CSRF attack, check out this [little tool](https://littletools.app/form?eyJtZXRob2QiOiJQT1NUIiwiYWN0aW9uIjoiaHR0cHM6Ly93b3JrZXItYXV0aC5qZXJyeW5zaC53b3JrZXJzLmRldi9oZWxsbyIsImZpZWxkcyI6W119) (credits: [Shrikant](https://sharats.me/)).

I created a [branch name `csrf-attack-demo`](https://github.com/ngshiheng/worker-auth/commit/a781f97d95053bae182ca0841bb6d030d86d3cd1#commitcomment-97975597) where you could run that locally to simulate a CSRF attack on your site.

## Conclusion

In essence, as long as authentication isn't automatic (such as in browsers with Cookies), you don't have to worry about CSRF attacks.

For example, if your application attaches authentication credentials via a `Authorization` header, then CSRF isn't possible as the browser can't automatically authenticate the request.

Lastly, we need to stop advocating that any one of our comparisons is better than another. That is not how it works. Rather, we should think about the tradeoffs that we are making.

## Reference

I owe my thanks to all of the following references:

- [100% Stateless with JWT (JSON Web Token) by Hubert Sablonnière](https://www.youtube.com/watch?v=67mezK3NzpU)
- [How to Store Session Tokens in a Browser (and the impacts of each)](https://blog.ropnop.com/storing-tokens-in-browser/)