August 24, 2026
localStorage, sessionStorage and cookies: The Auth Token Decision Most Tutorials Get Wrong
Most tutorials teach localStorage first. Here is what they leave out.

By Tanitoluwa Adenuga
4 min read
Most tutorials teach localStorage first. Some mention sessionStorage as the safer upgrade. Very few talk about what actually happens when your token is readable by any script on the page.
I know this because I went through all three the wrong way before I understood what the right choice actually was.
And the first thing I had to unlearn was something I had believed for a long time, that cookie based authentication and token based authentication were two different things. Like you had to choose one or the other.
A cookie is not a type of authentication. It is just a place to put your token.
Token answers: what proves you're authenticated?
Cookie answers: where does that token live?
You can store a JWT in a cookie. In fact, that is exactly what you should do. "Cookie based auth" just means your token lives in a cookie instead of localStorage or sessionStorage. The two ideas don't compete. They work together.
Once that clicked, everything else made sense.
The Three Places Your Token Can Live
Before I understood cookies, I went through the other two first.
localStorage was my default for a long time. Not because I had thought carefully about it. Just because everyone seemed to be using it. It was simple, it persisted across tabs and browser closes, and the API was straightforward. localStorage.setItem("token", value) and you were done. I didn't question it much.
Then I learned about sessionStorage and thought I had upgraded. The key difference that appealed to me was that once the tab closes, the stored token is gone and that felt safer.
What I did not fully appreciate yet is that both localStorage and sessionStorage share the same fundamental weakness: any JavaScript running on your page can read them. That includes your code, but also injected scripts, compromised dependencies, and malicious browser extensions. If an attacker finds a way to run script on your page, your token is readable.
That is known as XSS, Cross Site Scripting. It is one of the most common ways frontend applications get compromised, and most developers don't think about it until something goes wrong.
sessionStorage's shorter lifetime reduces the window slightly. But it doesn't close it.
Cookies felt like a completely different world. And in one specific way, they are. When you set a cookie with the HttpOnly flag, JavaScript cannot read it at all. Not your code, not injected scripts, not anything running in the browser. The browser sends it automatically with every matching request, but it is invisible to JavaScript entirely.
That is the trade off that matters. You give up the ability to read your own token from the frontend. In exchange, you make it impossible to steal via XSS.
For an auth token, that is a trade worth making every time.
๐ช Cookie Attributes That Actually Matter
A cookie set by the backend is not just a string that appears in your browser. The attributes attached to it by the server are what determine whether it is actually secure, and whether your frontend requests even work at all.
These are the ones that matter for authentication :
HttpOnly
This is the most important one. An HttpOnly cookie cannot be read by JavaScript. Not even by your own frontend code. document.cookie won't show it. No script on the page can touch it.
This is exactly what protects you from XSS token theft. If the token is in an HttpOnly cookie, an attacker who injects script into your page gets nothing. The token is invisible to them.
The trade off is that your frontend can not read the token either. You can not decode it to get the user's role or check expiry on the client side. Instead, keep a small non-sensitive user profile in sessionStorage for the UI. Then treat a 401 response from the server as the real source of truth for whether someone is authenticated.
Secure
A Secure cookie is only sent over HTTPS. Over plain HTTP, the browser silently drops it. No error, no warning. It just doesn't get sent.
This is the one that caught me in the real world. Everything worked perfectly on localhost because browsers treat http://localhost as a secure context. The moment the app hit an HTTP test server, the cookie stopped being sent entirely. No obvious error. Just requests failing as if the user wasn't logged in.
The fix is straightforward: serve every environment over HTTPS. Not just production. Every environment.
SameSite
This one controls whether the cookie is sent on cross-site requests. It is your main defence against CSRF, which stands for Cross-Site Request Forgery.
Three options
Strict: cookie only sent on same-site navigation. Safest, but can cause issues when users arrive from external links.
Lax: sent on top level navigations. Good default for most apps. None: sent on all requests including cross-site. Requires
Secure. Needed when your frontend and API are on different domains.
๐ What I Do Now
This is where I land now on every new project:
For auth tokens, always cookies.
The backend sets them server-side with HttpOnly; Secure; SameSite=Lax as the baseline. As the frontend engineer, your job is to make sure your requests include credentials and that you are not trying to read or manipulate the cookie yourself. If your frontend and API are on different origins, work with your backend team to switch to SameSite=None; Secure and configure CORS properly.
Never touch the token from the frontend. Do not try to read, decode, or check it. Send your requests with credentials: "include" and let the browser handle it automatically:
fetch("/api/protected-route", { credentials: "include" });
If the server returns a 401, clear your local UI state and redirect to login. That's your source of truth, not anything stored client-side.
For non-sensitive UI state, sessionStorage or localStorage is fine.
Things like the user's name, email, display role. Nothing sensitive, just what you need to render the UI.
๐ If I Could Go Back
I would tell myself this:
localStorage, sessionStorage, and cookies are not three ways to do the same thing. They answer different questions entirely.
localStorage and sessionStorage ask: how long should this data stick around?
Cookies ask: should JavaScript be allowed to touch this at all?
For auth tokens, that second question is the only one that matters.
HttpOnly cookies don't care how long your session lasts or how many tabs you have open. They just make sure that if someone finds a way to run malicious script on your page, they leave empty handed.
Switching between localStorage and sessionStorage, I thought I made a better security choice, turns out I was just rearranging the same problem.
The real answer was cookies. I just had not worked with them yet.
So the next time someone asks you where to store an auth token, you already know.
And you've got this โค๏ธ