How One Line of JavaScript Steals Tokens from localStorage

A Practical XSS Demonstration with React and Express

Modern web applications love localStorage. It's simple, persistent, and easy to use. Many Single Page Applications store JWTs there without a second thought.

But here's the uncomfortable truth:

If your app ever suffers an XSS vulnerability, storing tokens in localStorage turns a bug into a full account takeover.

In this article, I'll show you a small React + Express proof of concept that demonstrates — clearly and practically — why HTTP-only cookies are fundamentally safer than localStorage for authentication tokens.

The Question That Matters

Security discussions often get abstract. So instead of asking "Which is best practice?", let's ask:

What can an attacker actually do?

Assume the worst:

  • An attacker can execute JavaScript in your app (XSS)
  • Their goal is to steal authentication tokens

Now let's test both approaches.

Case 1: Token Stored in localStorage

The application logs in successfully and stores the JWT in localStorage.

When the attacker injects JavaScript, they run:

localStorage.getItem("token")

The result?

XSS stole token: JWT_SECRET_TOKEN

That's it. The token is stolen, sent elsewhere, and reused.

XSS + localStorage = instant account takeover.

Case 2: Token Stored in HTTP-Only Cookie

Now we repeat the same attack, but the token is stored in a cookie with the HttpOnly flag.

The attacker tries:

document.cookie

The result?

Nothing.

The browser blocks access completely. The token is never exposed to JavaScript.

This protection is not a framework feature. It's enforced by the browser itself, as defined in RFC 6265.

"But What About CSRF?"

Yes, cookies introduce CSRF risks. But here's the key difference:

  • XSS allows full account takeover
  • CSRF is limited and well-mitigated

With SameSite, CSRF tokens, and origin checks, CSRF becomes manageable. Token theft via XSS does not.

The Takeaway

HTTP-only cookies don't prevent XSS. They prevent XSS from becoming catastrophic.

If your app handles real users, real money, or real data, storing auth tokens in localStorage is a risk you don't need to take.