Authentication with Cookies

Sudeepta GiriSudeepta Giri
6 min read

Cookies in web development are small pieces of data that a website sends and stores on the user's computer through the browser. They act like a memory system for websites, helping them identify and remember users across sessions.

Think of cookies as little notes your browser keeps for a website, so the next time you visit, the site recognizes you.


Why Do We Use Cookies?

  1. Session Management

    • Cookies allow websites to identify users and keep them logged in as they move between pages.

    • Example: You log in to Gmail once, and it remembers you when you switch to Inbox or Drive.

  2. Personalization

    • Websites can store your preferences, such as theme (dark/light mode), language, or shopping cart items.
  3. Tracking

    • Cookies can track browsing behavior across websites.

    • This is often used for analytics and targeted ads.

  4. Security

    • Special cookies like Secure and HttpOnly help protect sensitive data by ensuring tokens are sent only over HTTPS and are not accessible to JavaScript.

💡 In this series, we’ll focus mainly on point 4 (Security) because it’s crucial when talking about authentication.


Why Not LocalStorage?

Both Cookies and LocalStorage store data on the client side, but they work differently:

  • Cookies are automatically sent with every request to the server (via the browser).
    👉 This means you don’t need to attach them in API calls manually.

  • LocalStorage requires extra steps: you must explicitly add tokens to the Authorization header when making fetch/axios requests.

⚡ This difference becomes super important when working with frameworks like Next.js, where server-side rendering and API routes make cookie-based authentication a more natural fit.


How Authentication with Cookies Works (Step by Step)

  1. User Logs In

    • User enters username and password.

    • Server verifies credentials.

  2. Server Sets a Cookie

    • If credentials are correct, the server creates a session or issues a JWT token.

    • This token/session ID is stored inside a cookie and sent to the browser.

  3. Browser Stores Cookie

    • The cookie can be HttpOnly, Secure, and set with an expiry.

    • Browser automatically attaches this cookie to all future requests to the same domain.

  4. Accessing Protected Routes

    • When the user tries to access a protected resource, the server checks the cookie.

If the cookie is valid, access is granted. Otherwise, the user is redirected to login.

Signup:-

SignIn:-

User Details Endpoint:-

You don’t need to explicitly set the cookieheader in the browser. It’s automatically set by the browser in every request


Example with Express.js (Node.js)

Here’s a simple implementation using cookies for authentication:

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

const app = express();

// Middleware to parse cookies from incoming requests
app.use(cookieParser());
app.use(express.json());

// Allow cross-origin requests (CORS) so frontend (localhost:5173) can talk to backend (localhost:3000)
// credentials: true → allows cookies to be sent with requests
app.use(cors({
    credentials: true,
    origin: "http://localhost:5173"
}));

// Your JWT secret (in real projects, keep it in .env file)
const JWT_SECRET = "super-secret-key";


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

    // Normally: validate email & password against DB (skipped here)
    // If valid → create a JWT with userId inside payload
    const token = jwt.sign({ id: 1 }, JWT_SECRET);

    // Send JWT back to client inside a cookie
    // Browser will automatically store this cookie
    res.cookie("token", token, {
        httpOnly: true,  // protects from JS access (XSS safe)
        secure: false,   // set to true in production (HTTPS)
        sameSite: "lax"  // helps protect against CSRF
    });

    res.send("Login successful!");
});

// PROTECTED ROUTE
app.get("/user", (req, res) => {
    // Read & verify JWT from cookies
    const token = req.cookies.token;
    const decoded = jwt.verify(token, JWT_SECRET) as JwtPayload;

    // In real apps: fetch user details from DB using decoded.id
    res.send({
        userId: decoded.id
    });
});

// LOGOUT ROUTE
app.post("/logout", (req, res) => {
    // Remove the cookie by clearing it
    res.clearCookie("token");
    //res.cookie("auth", ""); //or set your token to something else both do the same thing
    res.json({ message: "Logged out successfully" });
});

// Start server
app.listen(3000, () => console.log("Server running on http://localhost:3000"));

Key Properties of Cookies for Authentication

  • HttpOnly → Prevents JavaScript from accessing cookies (helps prevent XSS).

  • Secure → Ensures cookies are sent only over HTTPS.

  • SameSite → Helps prevent CSRF attacks.

    P.S. - Everything is Explained below


Types of Cookies

  1. Persistent Cookies 🗂️

    • Stored even after closing the browser.

    • Example: “Remember me” functionality on login.

  2. Session Cookies

    • Deleted once the browser/tab is closed.

    • Common for temporary sessions.

  3. Secure Cookies 🔐

    • Sent only over HTTPS, preventing data leakage in plain-text connections.

  • HttpOnly:

    • Cannot be accessed by client-side JavaScript.

    • Protects against XSS attacks (hackers can’t steal your cookies via injected JS).

  • Domain & Path:

    • Controls which domains/paths the cookie is valid for.
  • SameSite:

    • Prevents cookies from being sent on cross-origin requests, which helps fight CSRF attacks.

    • Modes:

      • Strict: Cookies only sent for same-site requests.

      • Lax: Cookies sent for safe requests (GET, top-level navigation).

      • None: Cookies sent for all requests (must be Secure).

SameSite: None

Like a guard who always shows your ID, no matter how you entered, as long as the road is safe (HTTPS)

SameSite: Strict

Like a guard who shows your ID only if you walked in from the same building.

But there is a problem

SanmSite: Lax

Like a guard who shows your ID if you enter through the front door, but not if someone sneaks you in.


CSRF Attacks and Cookies

Cross-Site Request Forgery (CSRF) is a web security vulnerability that tricks an authenticated user into unknowingly performing unwanted actions on a web application where they're currently logged in. Unlike XSS attacks that exploit user trust in a website, CSRF attacks exploit the website's trust in the authenticated user.

How CSRF Attacks Work

CSRF attacks leverage the fact that browsers automatically include credentials (like session cookies) with every request to a website, regardless of where the request originates. Here's the attack flow:

  1. User Authentication: Victim is logged into a legitimate website (e.g., online banking)

  2. Malicious Content: Attacker tricks the victim into visiting a malicious page or clicking a crafted link

  3. Forged Request: The malicious page automatically sends a request to the legitimate website

  4. Automatic Credentials: Browser includes the victim's session cookies with the forged request

  5. Request Execution: The legitimate website processes the request as if it came from the authenticated user

Key Attack Requirements

  • The target website must be vulnerable to CSRF (lack proper protection)

  • The victim must be authenticated on the target website

  • The attacker must know the structure of the target website's requests

  • The victim must be tricked into triggering the malicious request

Cookies made CSRF attacks very common. Why?

  • Since the browser automatically sends cookies, malicious sites could trick users into making unintended requests to another website where they were logged in.

💡 This is why the SameSite attribute was introduced: to give developers control over when cookies should be included in requests.


Tags

#Cookies #WebSecurity #Authentication #JWT #CSRF #WebDevelopment #NextJS #FullStackDevelopment #SecureCoding #JavaScript #NodeJS #WebAppSecurity

0
Subscribe to my newsletter

Read articles from Sudeepta Giri directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sudeepta Giri
Sudeepta Giri

I’m Sudeepta Giri, a Full Stack Software Engineer with hands-on experience as a Jr. Software Engineer Intern at EPAM Systems, where I built scalable MEAN stack applications, microservices, and optimized frontend performance. I’ve led Agile teams, developed impactful projects like a Rent-Car platform, Mental Health app, and an AI Interview platform, and achieved recognition as a Smart India Hackathon 2022 winner. Passionate about web development, cloud, and problem-solving, I aim to create user-focused and scalable digital solutions.