← Back to list

Authentication using Cookies πŸͺ

β€œBhai login toh ho gaya… but secure hai kya?”

Yash Patil Β· 2026-05-27 12:29 Β· 0 claps Β· 5.2 min read
#cookies #jwt-authentication #nextjs #reactjs #web-development
Open on Medium β†—
Wiki topics: 🌐 · Web Development

Authentication using Cookies πŸͺ

β€œBhai login toh ho gaya… but secure hai kya?”

Imagine this πŸ‘‡

You enter a college fest.

At the gate:

  • You show your ID card once
  • The organizer puts a stamp on your hand
  • Now wherever you go inside the fest β€” gaming zone, food court, concert β€” security already knows you’re authenticated.

You don’t keep showing your ID card every 2 minutes.

That stamp? That’s basically what cookies do on the web.

What is Authentication?

Authentication is the process of verifying who you are.

This usually happens when users:

  • Signup
  • Signin
  • Use Google/GitHub login (SSO β€” Single Sign On)

Without authentication:

  • Anyone could access anyone’s account
  • Websites wouldn’t know who is making requests

The old common approach

JWT + LocalStorage

Most beginners start authentication like this:

localStorage.setItem("token", jwtToken)

Then every request manually sends:

headers: {
   Authorization: `Bearer ${token}`
}

Works? βœ… But has problems too πŸ˜…

Especially when applications become bigger.

Why LocalStorage becomes problematic

1. You manually handle everything

Every request:

fetch(url, {
   headers: {
      Authorization: token
   }
})j

Har jagah token bhejo 😭

You forget once β†’ request fails.

2. Vulnerable to XSS attacks

If malicious JS somehow runs on your page:

const token = localStorage.getItem("token");

Boom πŸ’€

Attacker now has your auth token.

3. Doesn’t work nicely with Next.js SSR

This is where things become super important.

Why LocalStorage is problematic in Next.js

In traditional React apps:

  • Everything runs in browser

But in Next.js:

  • Components can run on the server
  • Pages can render before browser even loads

And guess what?

localStorage

does NOT exist on the server.

So this breaks:

const token = localStorage.getItem("token");

Error:

ReferenceError: localStorage is not defined

Real-world analogy 🧠

Imagine:

  • LocalStorage = your personal diary inside your room
  • Browser/server = security guards outside

Server cannot enter your room to read the diary.

But cookies?

Cookies are automatically attached with requests by the browser itself.

Meaning:

  • Server can access them easily
  • SSR works beautifully
  • Authentication becomes smoother

This is why frameworks like:

  • Next.js
  • Remix
  • Nuxt.js

prefer cookie-based authentication.

Authentication using Cookies

What are Cookies?

Cookies are small pieces of data stored by the browser.

They are designed to:

  • Remember users
  • Maintain sessions
  • Store auth information
  • Improve security

Very similar to LocalStorage…

…but with one SUPERPOWER ⚑

Cookies are automatically sent with requests

You DON’T need this:

Authorization: Bearer token

Browser khud hi sambhaal leta hai 😌

Flow of Cookie Authentication

User logs in
       ↓
Server verifies credentials
       ↓
Server sends cookie
       ↓
Browser stores cookie
       ↓
Browser automatically sends cookie
with every request
       ↓
Server identifies user

Why Cookies are better for Authentication

1. Automatic handling

Browser automatically attaches cookies.

Less headache. Cleaner code.

2. Better security

Cookies support:

  • HttpOnly
  • Secure
  • SameSite

These are HUGE security advantages.

3. Better with SSR frameworks

Works naturally with:

  • Next.js
  • Server Components
  • Middleware
  • API routes

4. Expiry support

Cookies can expire automatically.

Token expires after 7 days

Simple.

Types of Cookies

Persistent Cookies

Stay even after browser closes.

Example:

  • β€œRemember me”

Session Cookies

Destroyed when browser closes.

Example:

  • Banking websites

Secure Cookies

Only sent over HTTPS.

No HTTP = No cookie.

Security level πŸ“ˆ

Important Cookie Properties

These properties are what make cookies powerful.

HttpOnly

HttpOnly = JavaScript cannot access cookie

This means:

document.cookie

cannot read it.

So even if malicious JS enters your website:

  • Token remains protected

Massive win against XSS attacks.

SameSite

Controls whether cookies are sent on cross-origin requests.

This was introduced mainly to reduce:

  • CSRF attacks
  • Cross-site abuse

There are 3 modes:

1. SameSite: Strict

Only same-site requests allowed.

Most secure.

BUT…

Problem πŸ˜…

If user clicks your website from another website:

  • Cookie may not be sent

User experience becomes annoying.

2. SameSite: Lax

Balanced mode βš–οΈ

Cookies sent:

  • On top-level navigation
  • Mostly GET requests

This is why many apps use:

SameSite=Lax

Best balance of:

  • Security
  • Usability

3. SameSite: None

Cookies sent everywhere.

BUT requires:

Secure=true

Useful for:

  • Different frontend/backend domains

Example:

  • frontend.com
  • api.backend.com

Domains

You can also define:

Which domains can access cookie

Useful in:

  • Subdomains
  • Multi-service apps

Example:

app.example.com
api.example.com

CSRF Attacks 😨

Cross Site Request Forgery.

One of the biggest reasons cookie auth became controversial.

Real-life example

Imagine you are logged into:

bank.com

Now you visit:

evilwebsite.com

That evil site secretly sends:

<form action="https://bank.com/transfer-money">

Since browser automatically sends cookies…

πŸ’€ Bank thinks YOU made the request.

Money gone.

Daya… kuch toh gadbad hai.

Solution? SameSite

This is exactly why:

  • SameSite
  • CSRF protection

became important.

Authentication using Cookies : Now let’s build it πŸ”₯

Backend Setup (Express + TypeScript)

Initialize project

npm init -y
npx tsc --init

Update tsconfig

{
   "rootDir": "./src",
   "outDir": "./dist"
}

Install dependencies

npm install express cookie-parser cors jsonwebtoken
npm install -D typescript @types/express @types/cookie-parser @types/jsonwebtoken

Create Express App

import express from "express";
import cookieParser from "cookie-parser";
import cors from "cors";
import jwt, { JwtPayload } from "jsonwebtoken";

const app = express();
app.use(cookieParser());
app.use(express.json());
app.use(cors({
    credentials: true,
    origin: "http://localhost:5173"
}));

Why credentials: true matters

Without this:

Browser will NOT send cookies

Even if cookie exists.

This line is extremely important.

Signin Endpoint

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

// Validate user from DB

    const token = jwt.sign({
        id: 1
    }, JWT_SECRET);
    res.cookie("token", token, {
        httpOnly: true,
        secure: false,
        sameSite: "lax"
    });
    res.send("Logged in!");
});

Important part

res.cookie(...)

This sends cookie to browser.

Browser automatically stores it.

No manual work.

Protected Route

app.get("/user", (req, res) => {
const token = req.cookies.token;
    const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload;
    res.send({
        userId: decoded.id
    })
});

Notice something beautiful? ✨

No:

Authorization headers

No:

localStorage.getItem()

Browser automatically handled everything.

Logout Route

app.post("/logout", (req, res) => {
res.clearCookie("token");
    res.json({
        message: "Logged out!"
    })
});

Cleaner than replacing token manually.

Listen on Port

app.listen(3000);

Frontend in React

Signin Page

import { useState } from "react"
import axios from "axios"
export const Signin = () => {
    const [username, setUsername] = useState("")
    const [password, setPassword] = useState("")
    return <div>
        <input
            type="text"
            placeholder="username"
            onChange={(e) => {
                setUsername(e.target.value);
            }}
        />
        <input
            type="password"
            placeholder="password"
            onChange={(e) => {
                setPassword(e.target.value);
            }}
        />
        <button onClick={async () => {
            await axios.post("http://localhost:3000/signin", {
                username,
                password
            }, {
                withCredentials: true
            });
            alert("Logged in")
        }}>
            Submit
        </button>
    </div>
}

SUPER IMPORTANT ⚠️

withCredentials: true

Without this:

  • Cookies won’t be sent
  • Authentication breaks

Most beginners miss this line 😭

User Page

import axios from "axios";
import { useEffect, useState } from "react"
export const User = () => {
    const [userData, setUserData] = useState();
    useEffect(() => {
        axios.get("http://localhost:3000/user", {
            withCredentials: true
        })
        .then(res => {
            setUserData(res.data);
        })
    }, []);
    return <div>
        You're id is {userData?.userId}
        <br /><br />
        <button onClick={() => {
            axios.post("http://localhost:3000/logout", {}, {
                withCredentials: true,
            })
        }}>
            Logout
        </button>
    </div>
}

Authentication Flow Diagram

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ User Login β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
      ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Backend validates  β”‚
β”‚ credentials        β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Server sends       β”‚
β”‚ cookie             β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Browser stores     β”‚
β”‚ cookie             β”‚
β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Every future       β”‚
β”‚ request carries    β”‚
β”‚ cookie automaticallyβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Final Thoughts

Cookie-based authentication feels slightly confusing initially…

But once it clicks 🀌

…it becomes one of the cleanest authentication systems for modern web apps.

Especially in frameworks like:

  • Next.js
  • SSR applications
  • Fullstack apps

Cookies solve many real-world problems elegantly.

The key takeaway:

JWT is not the problem.
WHERE you store the JWT matters.
  • JWT + LocalStorage β†’ Easier initially, weaker security
  • JWT + HttpOnly Cookies β†’ Cleaner + safer + SSR friendly

And in real production apps…

Most companies prefer:

  • Cookies
  • Session-based auth
  • HttpOnly tokens

for exactly these reasons.

Tiny Homework πŸ‘€

Try implementing:

  • Access Token
  • Refresh Token
  • HttpOnly cookies
  • Protected routes

And suddenly…

you’ll realize authentication is not β€œjust login/signup”.

It’s an entire security system sitting behind your app πŸ”₯

webdevelopment #javascript #typescript #reactjs #nextjs #nodejs #expressjs #authentication #websecurity #cookies #jwt #frontenddevelopment #backenddevelopment #fullstackdevelopment #softwareengineering #coding #programming #developers #100DaysOfCode #tech #devcommunity #httpOnly #csrf #webdev #reactdeveloper #nextjsdeveloper


메타데이터
post_id
b105efd0a944
slug
authentication-using-cookies-b105efd0a944
url
https://medium.com/@yasharesofficial/authentication-using-cookies-b105efd0a944
canonical_url
https://medium.com/@yasharesofficial/authentication-using-cookies-b105efd0a944
author_url
https://medium.com/@yasharesofficial
status
ok
fetched_at
2026-06-09 15:37:30