Auth tokens: Stop storing them in local storage

I have seen the pattern with teams I’ve worked with. They built a well coded backend API, and to lock it down, they decide to use the Authorization header. It sounds good, read every where, probably seen few example implementations, right? The frontend grabs a user’s token, saves it somewhere, and sends it onto every single API request.

It works. It’s easy. But if you are building your apps this way, we need to talk. Storing tokens on the frontend and manually passing them around opens up some massive security gaps that are deceptively easy to avoid.

Let’s look at why this common pattern trips people up, and how we can fix it.

The Problem with the Frontend Storage Strategy

When you force your frontend team to manage the authentication token, you usually end up dropping it into localStorage or a standard browser cookie.

That’s because it’s easy, seen and works well. The choice comes with three major headaches:

  • Malicious code attacks: If a hacker manages to run a script on your website called a Cross-Site Scripting (XSS) attack—they can read your local/session storage instantly. They can use the same token and overtake your account.
  • DIY expiration logic: Local storage is dumb. It doesn’t know or care when a token expires. That means you have to write extra code to keep track of time and clear the token out when it gets old.
  • Extra frontend maintenance: You have to write custom code to intercept every single API call, grab the token, and stuff it into the header. It’s just more boilerplate code that can break.

The Fix: Let the Browser Do the Heavy Lifting

The best way is to store your tokens in HttpOnly Cookies. Let the browser handle authentication natively. Shifting to this approach changes the game in a few big ways:

1. Token are not exposed to browser

When your backend sets a cookie with the HttpOnly flag, browsers simply cannot read it. Even if a hacker successfully pulls off an XSS attack, their scripts cannot read your token.

To double down on safety, you can add the SameSite=Lax flag. This ensures the browser won’t send the cookie along if a user clicks a malicious link on a completely different website, effectively shutting down most basic Cross-Site Request Forgery (CSRF) attacks.

2. Zero Frontend Code Required

Once the backend sets it, the browser will attach that cookie to every single request you make to that domain. You don’t have to write a single line of frontend code to pass headers around.

3. Expiration is Built Right In

Cookies have expiry date. Your backend can tell the cookie exactly when to expire, and the browser will automatically delete it when the time runs out. No custom JavaScript delete/expiry management needed.

## Additional Security Steps

Moving to secure cookies is a great first step, but if you want to build a truly robust system, you should add these four rules to your blueprint:

Split Your System Into Two Tokens
  • Access Token: This is your short-lived pass. Give it a life span of about 15 minutes and store it in an HttpOnly cookie.
  • Refresh Token: This is used solely to grab a new access token when the old one dies. Give it a longer life (like 7 days), store it in a separate HttpOnly cookie, and lock it down so it can only be sent to a specific path like “/auth/refresh”

I would go one step ahead and every single time access token is renewed, destroy the old refresh token and give them a brand-new one. If a hacker has old refresh token, it becomes invalid.

Keep Secrets Out of JSON Web Tokens (JWTs)

A lot of people think JWTs are highly encrypted. They aren’t. They are just encoded into a string that looks messy, but anyone can paste a JWT into a website (or do a base64 decode) and read the contents in two seconds. Never store passwords, emails, or sensitive Personally Identifiable Information (PII) data inside a JWT payload.

Use Public and Private Keys

If you use a shared secret key (HS256) to sign your tokens, every single micro-service in your system needs to know that exact secret to verify the user. If one service gets hacked, the whole kingdom falls.

Instead, use asymmetric encryption (RS256). Your main auth server uses a private key to write the token, and your other services use a completely safe, public key just to read and verify it.

Lock Up Your Keys

Never use environment variables to store the keys (unless it is not very important or sensitive application). Externalise them by storing in dedicated Secrets Manager. These days more developers using meta frameworks which blurs line between frontend and backend, it is easy to leak among many other reasons.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.