JWT Authentication: LocalStorage vs HttpOnly Cookies

javascript dev.to

Authentication sounds simple… until you actually have to implement it.

At first, it feels easy: user logs in, backend gives a token, frontend stores it somewhere, and boom — done.
Then reality enters the chat:

  • Where should I store the token?
  • LocalStorage or cookies?
  • Why is my cookie not being set?
  • Why does it work in Postman but not in the browser?
  • Why am I debugging CORS at 2 AM?

If you’ve been there, welcome. This blog will explain client-side authentication vs server-side authentication, how LocalStorage auth works, how cookie-based auth works, and how to properly set it up in both backend and frontend.

No unnecessary theory. Just practical understanding.


Table of Contents

  1. The Basic Authentication Flow
  2. Client-Side Auth Using LocalStorage
  3. Server-Side Auth Using Cookies
  4. Backend Setup for Cookie Authentication
  5. Frontend Access with Cookies
  6. LocalStorage vs Cookies: Which One is Better?
  7. Common Mistakes Developers Make
  8. Final Thoughts

1. The Basic Authentication Flow

Most authentication systems follow this flow:

  1. User enters email + password
  2. Frontend sends credentials to backend
  3. Backend verifies the user
  4. Backend generates a token (usually JWT)
  5. Token is stored somewhere
  6. Future requests use that token for authorization

The real question is:

Where should Step 5 happen?

That’s where LocalStorage vs Cookies begins.


2. Client-Side Auth Using LocalStorage

This is the “easy and fast” approach most developers start with.

Flow:

  • Backend sends JWT token in response body
  • Frontend stores token in localStorage
  • Every API request manually sends token in headers

Example:

Backend Response

{"token":"your_jwt_token_here"}
Enter fullscreen mode Exit fullscreen mode

Frontend Store

localStorage.setItem("token", response.data.token);
Enter fullscreen mode Exit fullscreen mode

Sending Token

axios.get("/profile", {
  headers: {
    Authorization: `Bearer ${localStorage.getItem("token")}`
  }
});
Enter fullscreen mode Exit fullscreen mode

Why Developers Like It

  • Very easy to implement
  • Easy to debug
  • Works quickly for prototypes
  • No cookie/CORS headaches

The Problem

localStorage is vulnerable to XSS attacks.

If malicious JavaScript enters your app, it can access:

localStorage.getItem("token")
Enter fullscreen mode Exit fullscreen mode

…and congratulations, your auth token is now on a vacation it never planned.

That’s why production apps usually avoid storing sensitive auth tokens there.


3. Server-Side Auth Using Cookies

This is the more secure and professional approach.

Instead of giving the token directly to frontend JavaScript:

  • Backend stores token inside an HttpOnly Cookie
  • Browser automatically sends it with requests
  • Frontend cannot access it using JavaScript

This means:

document.cookie
Enter fullscreen mode Exit fullscreen mode

won’t expose your auth token if it’s HttpOnly.

That’s a big security win.


4. Backend Setup for Cookie Authentication

Let’s use Node.js + Express.


Install Packages

npm install express cookie-parser jsonwebtoken cors
Enter fullscreen mode Exit fullscreen mode

Basic Server Setup

const express = require("express");
const cookieParser = require("cookie-parser");
const cors = require("cors");
const jwt = require("jsonwebtoken");

const app = express();

app.use(express.json());
app.use(cookieParser());

app.use(cors({
  origin: "http://localhost:3000",
  credentials: true
}));
Enter fullscreen mode Exit fullscreen mode

Login Route

app.post("/login", (req, res) => {
  const { email, password } = req.body;

  // Assume user is valid
  const token = jwt.sign(
    { email },
    "your_secret_key",
    { expiresIn: "1h" }
  );

  res.cookie("token", token, {
    httpOnly: true,
    secure: false, // true in production (HTTPS)
    sameSite: "lax",
    maxAge: 60 * 60 * 1000
  });

  res.json({
    message: "Login successful"
  });
});
Enter fullscreen mode Exit fullscreen mode

Now the browser stores the cookie automatically.

No frontend localStorage.setItem() needed.

Beautiful.


5. Frontend Access with Cookies

This is where many developers suffer.

The secret:

You MUST use withCredentials: true

Without this, cookies will behave like your gym motivation — gone.


Axios Example

axios.post(
  "http://localhost:5000/login",
  {
    email,
    password
  },
  {
    withCredentials: true
  }
);
Enter fullscreen mode Exit fullscreen mode

Protected Route Example

axios.get(
  "http://localhost:5000/profile",
  {
    withCredentials: true
  }
);
Enter fullscreen mode Exit fullscreen mode

That’s it.

The browser sends cookies automatically.

No manual headers.

No token handling.

Less headache.


6. LocalStorage vs Cookies: Which One is Better?

Feature LocalStorage HttpOnly Cookie
Easy to implement ✅ Yes Slightly harder
Accessible in JS ✅ Yes ❌ No
XSS Safe ❌ No ✅ Better
CSRF Risk ✅ Safe ⚠ Needs protection
Automatic sending ❌ No ✅ Yes
Production Ready ⚠ Depends ✅ Preferred

My Recommendation

  • Small project / prototype → LocalStorage is okay
  • Production app → Prefer HttpOnly cookies

Security always wins.

Even if it makes debugging slightly more annoying.


7. Common Mistakes Developers Make

Mistake 1: Forgetting withCredentials

Most common issue.

Most painful issue.

Most “why is this not working?” issue.


Mistake 2: Wrong CORS Config

You must allow:

credentials: true
Enter fullscreen mode Exit fullscreen mode

and exact frontend origin.

Not *

Never *

Browsers hate that.


Mistake 3: secure: true in Localhost

This breaks cookies locally.

Use:

secure: false
Enter fullscreen mode Exit fullscreen mode

for development.


Mistake 4: Trying to Read HttpOnly Cookie in JS

You can’t.

That’s literally the point.


8. Final Thoughts

Authentication is one of those things that looks easy until you build it yourself.

LocalStorage works.
Cookies work better.

Understanding the trade-offs helps you choose the right approach for your project instead of blindly copying random Stack Overflow answers from 2017.

If you’re building serious production applications:

HttpOnly cookies + proper backend validation is the way to go.

Your future self will thank you.

Your security team will thank you.

And most importantly…

you’ll finally stop debugging CORS at 2 AM.

Source: dev.to

arrow_back Back to Tutorials