Angular 01.09.2026 · 2 min read

Where Not to Store a JWT in Angular (and What to Do Instead)

localStorage is the most common and the worst place for a token. The pattern we use when an identity leak is not an option: token in memory only, refresh through an httpOnly cookie.

Almost every Angular tutorial puts the JWT in localStorage and moves on. It works, it is simple, and it hands the token on a plate to any script that ever runs on your domain. On a system where an identity leak was not acceptable, we had to do it differently.

Why localStorage Is Not a Vault

localStorage is readable from any JavaScript on the same domain. One XSS hole — in your code, in a dependency, in a marketing script someone added through the admin — and the attacker has the token and everything it unlocks. The same goes for sessionStorage and for a plain cookie without the httpOnly flag.

The Pattern: Token in Memory, Refresh in a Cookie

The access token lives only in memory — a field on the auth service, nowhere on disk. An interceptor attaches it to every request. For a long-lived session the server issues a refresh token as an httpOnly, Secure, SameSite cookie: the browser sends it on its own, and JavaScript can neither read nor steal it.

When the user reloads the page, memory is empty. On startup the app calls the refresh endpoint; the cookie travels by itself, the server returns a fresh access token, and the user stays signed in without noticing a thing. A short expiry on the access token limits the damage if it ever does leak.

What Changes in the App

Three things need care. First, initial load has one extra call, so the route guard must wait for its result instead of bouncing the user to the login page immediately. Second, on a 401 the interceptor attempts a refresh once and replays the original request — without ever looping. Third, logout has to invalidate the cookie on the server, because the client cannot delete it.

The Cost, and When It Pays Off

The pattern is not free: the server has to manage refresh tokens and be able to revoke them, and every browser tab refreshes its session on its own. For an internal tool with three users it may be overkill. For anything holding other people’s personal data it is not a question of whether it pays off — only of how much you are prepared to explain why you didn’t.

A token JavaScript cannot read is a token it cannot steal — everything else is hope.