document.body.classList.toggle('menu-open', show); // Add 'menu-open' class to body.

JWT Security Part 5: JWT storage – cookies vs local

Jan Masters
Written by Jan Masters
December 12, 2024
Tags – ,

Welcome back, token tamers and security savants! Today, we’re diving into the great JWT storage debate: cookies vs local storage.

JWT storage: the contenders

In the left corner, following the strict rules of RFC 6265, the longtime web staple: HTTP Cookies! And in the right corner, with 10 MiB of pure storage muscle, the modern client-side challenger local storage!
Let’s break down these contenders and see how they stack up in the JWT storage arena.

Local storage: the client-side contender

Local storage is simple; it’s spacious, and it’s always there when you need it. No questions asked.

How it works

				
					```javascript
// Storing a JWT
localStorage.setItem('jwt', 'your.jwt.here');
// Retrieving a JWT
const token = localStorage.getItem('jwt');
```



				
			

Pros:

  • Capacity: 5 MiB for local storage and 5MiB for session storage per origin.
  • More than enough for most JWT needs, but not infinite!
  • Ease of use: simple API, easy to access from JavaScript.
  • Persistence: stays put even after the browser is closed.
  • CSRF proof: local storage is not typically susceptible to CSRF unless something has gone very wrong.


Note: while 5 MiB might sound like “a lot of JWTs,” remember that this is shared with all other local storage data for your domain. Budget wisely!

Cons:

  • XSS vulnerability: if an attacker can run JavaScript on your site, your tokens are up for grabs.
  • No automatic expiration: tokens stick around until you manually remove them.

Cookies: the HTTP heavyweight

How it works

Cookies are the old guard of web storage. They’ve been around the block, seen some things, and picked up a few tricks along the way.

				
					```javascript
// Storing a JWT in a cookie (server-side)
res.cookie('jwt', 'your.jwt.here', {
  httpOnly: true,
  secure: true,
  sameSite: 'strict'
});
// Retrieving a JWT from a cookie (server-side)
const token = req.cookies.jwt;```


				
			

Pros:

  1. HttpOnly flag: can be made inaccessible to JavaScript, mitigating XSS risks.
  2. Secure flag: can be set to only transmit over HTTPS.
  3. Automatic transmission: sent automatically with every HTTP request to your domain.

 

Cons:

  1. Size limit: generally limited to 4KB.
  2. CSRF vulnerability: if not properly protected, can be vulnerable to Cross-Site Request Forgery.

JWT storage security smackdown

When it comes to security, cookies have an edge…but only if you use them correctly.

XSS (Cross-Site Scripting) protection

Local storage:

				
					```javascript
// If this runs, say goodbye to your tokens
const stolenToken = localStorage.getItem('jwt');
sendToEvilServer(stolenToken);
```

				
			

Cookies:

				
					```javascript
// With HttpOnly cookies, this returns undefined
const failedAttempt = document.cookie;
console.log("Nice try, hacker:", failedAttempt);
```


				
			

HttpOnly cookies are like a bouncer for your tokens, keeping them safe from grabby JavaScript hands.

CSRF (Cross-Site Request Forgery) protection

Cookies are vulnerable to CSRF attacks, but modern defenses exist:

				
					```javascript
// Server-side CSRF token generation
app.use(csrf());

// Client-side CSRF token usage
fetch('/api/data', {
  method: 'POST',
  headers: {
	'CSRF-Token': csrfToken
  },
  body: JSON.stringify(data)
});
```

				
			

Local storage isn’t vulnerable to CSRF, as you have to manually include the token in requests. It’s like having to consciously decide to wear your VIP wristband every time you want to use it.

The verdict

So when it comes to JWT storage, which one should you use? Well, as with many things in tech, it depends.

Use cookies if:

  • You’re more worried about XSS than CSRF.
  • You’re okay with implementing additional CSRF protections.
  • Your JWTs are modest in size.


Use local storage if:

  • You’re building a Single Page Application (SPA).
  • You’re confident in your XSS protections.
  • You need to store larger tokens.

JWT storage best practices, regardless of choice

  1. Always use HTTPS: whether you’re using cookies or local storage, encrypt that traffic!
  2. Implement proper CORS policies: don’t let just anyone talk to your server.
  3. Use short-lived tokens: the shorter the lifespan, the smaller the window for misuse.
  4. Sanitise and validate user inputs: the best way to prevent most web app attacks is to stop it at the source.
  5. Security flags and headers: always ensure that you consistently and properly set HTTP security headers and cookie flags.

The hybrid approach

For the “I want it all” types, consider this: use short-lived JWTs in HttpOnly cookies for authentication, and store non-sensitive session data in local storage.

				
					```javascript
// Server-side
res.cookie('jwt', shortLivedToken, { httpOnly: true, secure: true });

// Client-side
localStorage.setItem('userName', 'Bruce Wayne');
```


				
			

In conclusion

Whether you choose cookies or local storage, remember that security is an ongoing process. Your choice of JWT storage method is just one piece of the security puzzle.

Stay tuned for our next post, where we’ll explore JWTs in API-centric applications.

Like what you see? Share with a friend!

Jan Masters

This article is written by

Jan Masters

Cyber Security Engineer

In cyber security, there are no magic spells – just knowledge, experience, luck, and a touch of wizardry to turn challenges into solutions.

Cyber Security Engineer Jan – our resident Cyber Wizard (@FlyingPhishy on X, if you’re asking) – is a jack-of-all-trades consultant specialising in infrastructure, cloud, and R&D. He delivers high-quality, holistic penetration tests and drives innovation from within, ensuring our penetration testing services are modern and break the mould.