---
title: "HttpOnly cookie vs LocalStorage: where to store auth tokens"
description: "LocalStorage exposes auth tokens to any script on the page, XSS included. Why HttpOnly cookies close that gap, with the attack path and a Nuxt 3 example."
url: "https://volcanicminds.com/en/insights/cookie-vs-localstorage-security-guide"
lang: "en"
type: "second_level_page"
updated: "2026-09-04"
alternate: "https://volcanicminds.com/insights/cookie-vs-localstorage-security-guide"
---

# Not just AI in 2026: Cookie vs LocalStorage

## **The Ultimate Token Security Guide**

Given the current landscape, imagine having just launched your new AI-powered SaaS. The UI is snappy, models respond in milliseconds (kudos!), and investors are thrilled. But while the team is celebrating, a three-line script is silently sending that single access token to an anonymous server.

No alarms ring. No firewall blocks the request. Why? Because that token was stored in **LocalStorage**.

This is a classic **Web Security** issue. If you think "sanitizing inputs" is enough to keep you safe, this article might help you understand why it's not.

## Convenience is the Enemy of Security

Why do we still see tutorials recommending **_localStorage.setItem('accessToken', token**)_? The answer is simple: it’s incredibly convenient and fast to implement. No CORS headaches, no domain issues, it just works.

But in a **Zero Trust Architecture**, convenience comes at a steep price. **LocalStorage** has (unfortunately) a fatal design flaw regarding sensitive data: it is accessible by _any_ JavaScript code running on your page. This includes:

- Your Web Application (e.g. Vue/Nuxt, React/Next, Angular, ..).
- Third-party libraries (analytics, chat widgets, social pixels).
- Any malicious code injected via **XSS (Cross-Site Scripting)**.

If an attacker manages to execute JS on your page (XSS), reading LocalStorage is as trivial as reading a variable.

## HttpOnly: the real Firewall

The correct solution isn't to try and "lock down" LocalStorage, but to use storage that JavaScript _cannot_ read: **HttpOnly Cookies**.
When a cookie is flagged as HttpOnly, the browser hides it from the JavaScript engine (document.cookie won't show it) but continues to send it automatically with every request to the origin server. This drastically reduces the attack surface: even with an XSS vulnerability, the attacker cannot steal the token so easily.

### The Strategy: Cookie Strict Mode

Although **Volcanic Backend** natively supports various authentication modes (including classic Bearer Tokens via Header, useful for mobile apps or server-to-server calls), for Enterprise Web Apps we **strictly enforce Cookie mode**.

This configuration shifts persistence responsibility from the (vulnerable) client code to the (protected) browser, leveraging two fundamental barriers:

1. **HttpOnly**: The token is not accessible via JavaScript. Not even by _your_ code.
1. **Secure & SameSite**: The browser forces the token to be sent only over encrypted connections and only to the origin domain, preventing CSRF.

In this scenario, LocalStorage remains empty (or used only for UI preferences), and authentication flows transparently and securely.

## Conceptual implementation in Nuxt 3

Let's look at how to implement this pattern leveraging Nuxt 3 composables and the Nitro server engine.

### 1. Secure Login (Server Side)

In a **Volcanic Backend** project, cookie management happens in the controller, leveraging the Fastify-based architecture.
First, we define the route in src/api/auth/routes.ts:

```javascript
// src/api/auth/routes.ts
export default {
  config: {
    controller: "controller",
    enable: true,
    tags: ["Auth"],
  },
  routes: [
    {
      method: "POST",
      path: "/login",
      handler: "auth.login",
      config: {
        description: "Secure Login with HttpOnly Cookie",
        response: {
          200: { type: "object", properties: { user: { type: "object" } } },
        },
      },
    },
  ],
};
```

And then implement the logic in the controller src/api/auth/controller/auth.ts:

```javascript
// src/api/auth/controller/auth.ts
import { FastifyReply, FastifyRequest } from "@volcanicminds/backend";
export async function login(req: FastifyRequest, reply: FastifyReply) {
  const { username, password } = req.body as any;
  // ... validate user ...
  // const user = await authService.validate(username, password)
  const token = generateAccessToken(user.id);
  // Save the token in a secure cookie
  reply.setCookie("token", token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "strict",
    path: "/",
    maxAge: 60 * 60 * 24, // 1 day
  });
  // The client doesn't need to receive the token in the body!
  return { success: true, user: { id: user.id, username: user.username } };
}
```

### 2. Session Management (Client Side)

On the frontend, **we don't need to do anything with the token**. We don't save it, we don't read it. When calling an API with $fetch (or useFetch), the browser will automatically attach the cookie.

```javascript
// composables/useAuth.ts
export const useAuth = () => {
  const user = useState("user", () => null);
  const login = async (credentials: any) => {
    // The response will automatically set the cookie
    const data = await $fetch("/api/auth/login", {
      method: "POST",
      body: credentials,
    });
    // We only update the user state for the UI
    user.value = data.user;
  };
  return { user, login };
};
```

This approach ensures that the secret (the access token) is **invisible** to JavaScript code. Even if a malicious script tried to read document.cookie, it would find an empty string (thanks to HttpOnly). And without the token, it cannot impersonate the user outside the victim's browser.

## We Don't Just Use Libraries, We Design Architectures

At Volcanic Minds, we develop complex platforms (Enterprise ERP & Dashboards). For us, security isn't a feature; it's the starting point. We don't just use libraries; we design resilient architectures, which is why we built the **[Volcanic Backen**d](https://github.com/volcanicminds/volcanic-backend).

An open-source framework that natively integrates these security best practices, allowing teams to focus on business logic knowing they are resting on solid foundations.

## Conclusion

Security has always (and even more so in 2026) been about more than just HTTPS or complex passwords. It's about data architecture on the client and between client and server. Moving tokens from LocalStorage to HttpOnly Cookies is a **zero-cost** investment that drastically raises the bar that potential attackers must overcome.

### 
Are you sure your Web App is XSS-proof?

If you have doubts about your platform's security, at Volcanic Minds we offer specific architectural audits to identify and close these gaps before it's too late.

- [Contact us for a Security Review](https://volcanicminds.com/en/contacts)

## Questions this raises

What you ask once the pattern is clear and has to be applied

### Is localStorage ever acceptable for an access token?

For a token that grants access to real data, no. The exception people reach for is the mobile app or the server-to-server call, where there is no browser and no XSS surface: there a Bearer token in a header is fine. Inside a browser, any script on the page can read localStorage, and that includes third-party libraries you did not audit.

### We already store tokens in localStorage. What do we do first?

Move the token to an HttpOnly cookie and invalidate the existing sessions, in that order. Rotating the tokens without changing where they are stored only resets the clock on the same exposure. The frontend change is usually smaller than expected, because once the cookie is sent automatically the client stops managing the token at all.

### Do HttpOnly cookies protect against CSRF as well?

Not on their own: HttpOnly closes the XSS read, not the cross-site request. That is what SameSite is for, set to strict or lax depending on the flows you need, alongside the usual origin checks on state-changing requests. The two attacks are different and need both flags.
