SE Fundamental

Cookies and Sessions in Web Development: Complete Guide

cookies-and-sessions-in-web-development img
Table of Contents

Imagine a waiter with no memory at all. You order a drink. He brings it. You ask for the bill, and he has no idea who you are, what you ordered, or whether you have paid. That is HTTP. Every single request arrives as a complete stranger.

Cookies and sessions are how the web gives that waiter a notebook. Get them right and people stay logged in safely. Get them wrong and you have an account-takeover bug. This guide shows you exactly how they work, with a step-by-step login you can click through, real code for PHP and Node, and the security flags that actually matter.

What you will learn (11 minute read):

✓ Why HTTP forgets you, and what that forces developers to build
✓ A clickable walkthrough of a real login, showing both the browser and the server
✓ Cookies vs sessions vs tokens, with a clear decision guide
✓ Working code: PHP, Node.js and plain JavaScript
✓ The four cookie flags that stop most attacks
✓ How to read cookies in DevTools and check your own site

The Problem: HTTP Has No Memory

HTTP is stateless. Each request stands completely alone. The server answers it and immediately forgets everything. Without help, this happens:

Browser Server POST /login  (hina / correct password) Server: “Welcome, Hina!” … the very next click … GET /dashboard Server: “Who are you? Please log in.” Browser Server

Without cookies you would have to log in again on every single page. Cookies are the fix.

Cookies and Sessions in One Picture

People mix these up constantly, so here is the distinction in one line: a cookie is a note the browser keeps; a session is a file the server keeps. They work together, because the cookie is what carries the key to the server’s file.

THE BROWSER (the user’s device) 🍪 Cookie sid=7f3a9b2c… just a key, no personal data Small (about 4 KB per cookie) The user can see and delete it Sent with every request Never put secrets here sends key looks up THE SERVER (your machine) 🗃 Session store 7f3a9b2c → { userId: 42, role: ‘admin’, cart: […] } the real data lives here As large as you need The user cannot touch it Safe for sensitive data

The cloakroom analogy. The cookie is the numbered ticket in your pocket. The session is your coat in the back room. The ticket is worthless to a thief who cannot reach the cloakroom, and losing the ticket does not give anyone your coat unless they also walk in with it. That is why we protect the ticket so carefully.

CLICK THROUGH IT

Watch a Real Login Happen

This is the part that makes everything else obvious. Press Start and step through a login. Watch the cookie appear in the browser, the session appear on the server, and the exact HTTP headers travelling between them.

💻 BROWSER · cookie jar
🗃 SERVER · session store

 

 

Six steps, from typing a password to logging out.

HTTP traffic
# Press "Start the login" above

The key insight from step 3: the cookie only ever holds sid=7f3a9b2c. The user’s name, role and permissions never leave the server. Even if someone reads the cookie, they learn nothing about the account, which is why this pattern has lasted thirty years.

Seeing Cookies on Any Website

You do not need to take my word for it. On any site, press F12, open the Application tab (Chrome and Edge) or Storage (Firefox), and click Cookies. You will see something like this:

DevTools  |  Application › Storage › Cookies › https://example.com● ● ●
NameValueExpiresHttpOnlySecureSameSite
sid7f3a9b2c4e…Session✓✓Lax
themedark2027-10-17✗✓Lax
langen2027-10-17✗✓Lax
_gaGA1.2.118…2028-04-17✗✓Lax

An illustration of the DevTools cookie panel. Note that the login cookie sid has HttpOnly ticked, while the harmless preference cookies do not need it.

A two-minute exercise. Open DevTools on a site where you are logged in, find the session cookie, delete it, and refresh. You are logged out instantly. That one cookie was your login. Now you understand why protecting it matters so much.

The Code

PHP sessions

PHP does nearly all of this for you. session_start() creates the session, sends the cookie, and loads the data back on later requests.

login.php
<?php
// Set the cookie rules BEFORE starting the session
session_set_cookie_params([
  'lifetime' => 0,          // 0 = until the browser closes
  'path'     => '/',
  'secure'   => true,       // HTTPS only
  'httponly' => true,       // JavaScript cannot read it
  'samesite' => 'Lax',      // blocks most CSRF attacks
]);
session_start();

if (password_verify($_POST['password'], $user['password_hash'])) {

  // Critical: new ID on login, to stop session fixation
  session_regenerate_id(true);

  $_SESSION['user_id']   = $user['id'];
  $_SESSION['role']      = $user['role'];
  $_SESSION['last_seen'] = time();

  header('Location: /dashboard.php');
  exit;
}
dashboard.php  —  checking the session
<?php
session_start();

// Not logged in? Send them away.
if (empty($_SESSION['user_id'])) {
  header('Location: /login.php');
  exit;
}

// Log out after 30 minutes of inactivity
if (time() - $_SESSION['last_seen'] > 1800) {
  session_unset();
  session_destroy();
  header('Location: /login.php?timeout=1');
  exit;
}
$_SESSION['last_seen'] = time();

echo 'Welcome back, user #' . $_SESSION['user_id'];

Logging out properly

logout.php
<?php
session_start();
$_SESSION = [];                 // 1. empty the data
setcookie(session_name(), '', time() - 3600, '/');  // 2. kill the cookie
session_destroy();            // 3. delete it on the server
header('Location: /');

Deleting the cookie alone is not logging out. If the session still exists on the server, anyone holding a copy of that old session ID can keep using it. Always destroy the server side too, in that order.

Node.js with Express

server.js
const express = require('express');
const session = require('express-session');
const app = express();

app.use(session({
  secret: process.env.SESSION_SECRET,   // never hard-code this
  name: 'sid',                         // avoid the default name
  resave: false,
  saveUninitialized: false,           // no cookie until it is needed
  cookie: {
    httpOnly: true,
    secure: true,                      // HTTPS only
    sameSite: 'lax',
    maxAge: 1000 * 60 * 30            // 30 minutes
  }
  // In production use a real store (Redis, a database),
  // not the default in-memory one.
}));

app.post('/login', async (req, res) => {
  const user = await checkPassword(req.body);
  if (!user) return res.status(401).json({ error: 'Invalid login' });

  req.session.regenerate(() => {        // new ID on login
    req.session.userId = user.id;
    res.json({ ok: true });
  });
});

app.post('/logout', (req, res) => {
  req.session.destroy(() => {
    res.clearCookie('sid');
    res.json({ ok: true });
  });
});

Plain cookies in JavaScript

For harmless things like a theme preference, you can set a cookie in the browser. Remember that JavaScript can never read an HttpOnly cookie, which is exactly the point.

app.js
// Write a cookie that lasts a year
document.cookie = 'theme=dark; Max-Age=31536000; Path=/; Secure; SameSite=Lax';

// Read one back
function getCookie(name) {
  return document.cookie
    .split('; ')
    .find(row => row.startsWith(name + '='))
    ?.split('=')[1];
}

// Delete one: set it to a date in the past
document.cookie = 'theme=; Max-Age=0; Path=/';

// For data the server never needs, localStorage is simpler
localStorage.setItem('theme', 'dark');

The Four Flags That Stop Most Attacks

A cookie without these is a cookie waiting to be stolen. This is the single most valuable table in this article.

FlagWhat it doesWhat it prevents
HttpOnlyJavaScript cannot read the cookie at allA script injected into your page stealing the login cookie (XSS)
SecureOnly sent over HTTPSSomeone on the same Wi-Fi reading it in plain text
SameSiteControls whether it is sent from other sitesAnother site making requests as the logged-in user (CSRF)
Max-Age
or Expires
Sets when the cookie diesA login that stays valid forever on a shared computer
What a good session cookie looks like on the wire
Set-Cookie: sid=7f3a9b2c4e8d1f60; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1800

Choosing a SameSite value

ValueBehaviourUse for
LaxSent when someone clicks a link to your site, not on background cross-site requestsThe sensible default for login cookies
StrictNever sent from another site at all, even a normal linkBanking and admin panels, where the user arriving logged-out once is acceptable
NoneAlways sent, and requires SecureOnly when your cookie genuinely must work inside another site, such as an embedded widget

Cookies, Sessions and Tokens Compared

CookieSessionJWT token
Stored whereThe user’s browserYour server (memory, Redis, database)The browser, often in a cookie
Size limitAbout 4 KBWhatever your server allowsSmall, but bigger than a session ID
Can the user read itYes, unless HttpOnlyNoYes, it is only encoded, not encrypted
Safe for secretsNoYesNo, never put private data in the payload
Instant logoutDelete the cookieYes, delete it server-sideHard: a token stays valid until it expires
Scales across serversn/aNeeds a shared store such as RedisEasy, nothing to look up
Best forPreferences, the session ID itselfNormal website loginsAPIs, mobile apps, services talking to services

Which should you pick? For an ordinary website or a WordPress project, use server-side sessions with a secure cookie. It is simpler, you can log people out instantly, and nothing sensitive leaves your server. Reach for JWTs when a mobile app or another service needs to authenticate, not because they sound more modern.

Where to Store What

🍪 CookieAnything the server needs on every request: the session ID above all. Small, and automatically attached to each request.
🗃 Server sessionAnything sensitive or large: user ID, role, permissions, shopping cart, a multi-step form in progress.
💾 localStorageThings only the browser cares about and that can survive a restart: theme, dismissed banners, a saved draft. Never a login token.
⏳ sessionStorageBrowser-only data for one tab, cleared when it closes: a wizard’s current step, a scroll position.

Never store these in a cookie or in localStorage: passwords, password hashes, credit card numbers, API secrets, or a plain flag like isAdmin=true. Anyone can edit those in DevTools in five seconds. Authorisation decisions belong on the server, every time.

Cookies and the Law

Rules such as the GDPR in Europe treat non-essential cookies as personal data. The practical version for a developer:

No consent neededStrictly necessary cookies: your login session, a shopping cart, a security token. The site cannot work without them.
Consent needed firstAnalytics, advertising, and anything that tracks people across sites. Do not load those scripts until the user agrees.
Refusing must be easy“Reject all” should be as easy to find as “Accept all”. A banner with only an accept button is not consent.
Say what you setList your cookies and their purpose in your privacy policy, and let people change their mind later.

A compliant banner: rejecting is one click, just like accepting (illustration).

Mistakes That Cause Real Security Bugs

MistakeWhy it is dangerousFix
No HttpOnly on the session cookieAny injected script can read the login cookie and impersonate the userSet HttpOnly on every authentication cookie
Not regenerating the session ID at loginAn attacker who planted a known session ID beforehand is logged in as the victim (session fixation)session_regenerate_id(true), or req.session.regenerate()
Storing roles in a cookierole=admin can simply be edited by the userKeep the role in the server session and check it there
Sessions that never expireA stolen cookie works forever, and shared computers stay logged inSet an idle timeout, and an absolute maximum lifetime
Logout that only clears the cookieThe session survives on the server and can be reusedDestroy the server session as well
Cookies without Secure on an HTTPS siteThe cookie can leak over an accidental HTTP requestAlways add Secure, and redirect HTTP to HTTPS
In-memory sessions in productionEveryone is logged out on every restart, and nothing works behind a load balancerUse Redis, a database, or your framework’s recommended store

Common Questions

Are cookies dangerous or spyware?

A cookie is just a small piece of text your browser stores and sends back. It cannot run code or read your files. The privacy concern is not the technology but what some companies store in it: identifiers used to follow people across many websites. Your own login cookie is simply a ticket number.

What is the difference between a session cookie and a persistent cookie?

A session cookie has no expiry date and disappears when the browser closes. A persistent cookie has Max-Age or Expires set and survives restarts. Login cookies are usually session cookies unless the user ticks “remember me”.

Why do I get logged out even though I did nothing?

Usually an idle timeout, the server restarting with in-memory sessions, or the cookie being blocked. In development, a common cause is Secure: true on a cookie while testing over plain HTTP, so the browser silently refuses to store it.

Should I use localStorage for my login token?

It is not recommended. Anything in localStorage is readable by any JavaScript on the page, so one injected script exposes the token. An HttpOnly cookie cannot be read by scripts at all, which is why it remains the safer default for browser logins.

How big can a cookie be, and how many can I set?

Roughly 4 KB per cookie, and browsers typically allow somewhere around 50 per domain, with limits varying. Remember that every cookie is sent with every request to that domain, so a pile of large cookies slows your whole site down.

What are third-party cookies and should I worry about them?

They are cookies set by a different domain from the one in the address bar, mostly used for advertising. Browsers have been restricting them for years, so do not build anything important on top of them. First-party cookies, the kind your own site sets, continue to work normally.

Read next: The Role of APIs in Modern Web Development covers how tokens and sessions work when the client is not a browser.

Final Thoughts

Cookies and sessions exist because HTTP forgets you the moment it answers. Remember the cloakroom: the cookie is the ticket, the session is the coat. Keep the ticket small, mark it HttpOnly, Secure and SameSite, give it an expiry, and keep everything that matters on the server.

Do this now: open DevTools on your own project, look at your session cookie, and check those three flags. Most sites are missing at least one. Which one were you missing? Tell me in the comments. I read every one.

If this made cookies finally click for you, share it with someone still guessing.

Share this article

Muhammad Azeem

Written by

Muhammad Azeem

Articles by Muhammad Azeem on azeemtechstudio.com.

Stay Updated

Get the latest coding tutorials, web development tips, AI tools, and technology articles delivered to your inbox.

No spam — we only use your email to send new articles.

Join the Discussion

Your email address will not be published. Required fields are marked *