Headers, Cookies & Sessions

Headers, Cookies & Sessions

HTTP is stateless — by design, the server has no memory of who you are between requests. Each request arrives as if from a stranger. Cookies, sessions, and tokens solve this problem. Headers carry the metadata that makes everything work together.


1. HTTP Headers — The Metadata Layer

Headers are key-value pairs exchanged between the client and server. They appear in both requests and responses, each on its own line in the format Header-Name: value.

Request Headers:
  Host: api.nandhoo.com
  Accept: application/json
  Authorization: Bearer eyJhbG...
  Cookie: sessionId=abc123; theme=dark

Response Headers:
  Content-Type: application/json
  Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax
  Cache-Control: private, max-age=0
  X-Rate-Limit-Remaining: 94

Anatomy of a Header

Header-Name: value [; directive=value; flag]
     │           │         │
     │           │         └── Directives add extra instructions
     │           └──────────── The value
     └──────────────────────── Case-insensitive name (convention: Title-Case)

2. Common Request Headers

HeaderExampleWhat it does
Hostapi.nandhoo.comRequired in HTTP/1.1 — specifies the server (allows virtual hosting)
Acceptapplication/jsonWhat formats the client will accept
Accept-Languageen-GB,en;q=0.9Preferred language(s) for the response
Accept-Encodinggzip, brCompression algorithms the client supports
AuthorizationBearer eyJ...Authentication credentials
Cookieid=abc123; prefs=darkCookies to send to the server
User-AgentMozilla/5.0 ...Browser and OS identification
Refererhttps://google.comThe previous page (note: intentionally misspelled in spec)
Originhttps://nandhoo.comThe origin of a cross-origin request (CORS)
Content-Typeapplication/jsonFormat of the request body
Content-Length128Size of the request body in bytes
If-None-Match"etag-value"Conditional request — skip if ETag unchanged
Cache-Controlno-cacheCaching directives for the request

3. Common Response Headers

HeaderExampleWhat it does
Content-Typeapplication/json; charset=utf-8Format of response body
Content-Length284Body size in bytes
Content-EncodinggzipCompression used on the body
Set-Cookieid=abc; HttpOnly; SecureInstructs browser to store a cookie
Cache-Controlpublic, max-age=3600Caching rules for the response
ETag"a1b2c3"Resource version fingerprint
Location/api/users/99Redirect URL or newly created resource URL
WWW-AuthenticateBearer realm="api"Tells client how to authenticate (sent with 401)
Retry-After60Seconds to wait before retrying (sent with 429 or 503)
X-Rate-Limit-Limit100Max requests per window
X-Rate-Limit-Remaining78Requests left in current window
Strict-Transport-Securitymax-age=31536000Force HTTPS for 1 year

4. Custom Headers

Applications often use custom headers prefixed with X- (historical convention) or just descriptive names:

X-Request-Id: 7e4c9f21-ab12-4d3e-bcd1
X-Correlation-Id: parent-trace-abc456
X-App-Version: 2.1.4
X-Feature-Flags: dark-mode,beta-editor

Custom headers are useful for:

  • Tracing requests across microservices
  • Passing feature flags
  • API versioning
  • A/B testing signals

5. What are Cookies?

A cookie is a small piece of text data (up to ~4KB) that the server asks the browser to store and send back on every subsequent request to the same domain.

The Cookie Lifecycle

1. First Visit (no cookie yet)
   Client: GET /login HTTP/1.1
   Server: 200 OK
           Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Lax
           Set-Cookie: theme=dark; Path=/; Max-Age=2592000
           [HTML body]

2. Second Visit (browser sends cookie automatically)
   Client: GET /dashboard HTTP/1.1
           Cookie: sessionId=abc123; theme=dark
   Server: 200 OK
           [Dashboard HTML]

3. Cookie expires or user logs out
   Server: Set-Cookie: sessionId=; Max-Age=0  ← empty value + Max-Age=0 deletes the cookie

Cookie Attributes

AttributeExampleWhat it does
Name=ValuesessionId=abc123The cookie data
DomainDomain=.nandhoo.comWhich domain (and subdomains) receives this cookie
PathPath=/apiOnly sent for URLs starting with /api
Max-AgeMax-Age=3600Expiry in seconds from now (0 deletes it)
ExpiresExpires=Mon, 31 Mar 2025 GMTAbsolute expiry date (older alternative to Max-Age)
HttpOnlyHttpOnlyInaccessible to JavaScript (document.cookie cannot read it) — prevents XSS theft
SecureSecureOnly sent over HTTPS connections
SameSiteSameSite=LaxControls cross-site cookie sending — prevents CSRF

SameSite Values Explained

ValueBehaviourUse case
StrictNever sent cross-siteHighest security — for session cookies on banking/critical apps
LaxSent on top-level navigations (clicking a link), not on AJAX cross-site requestsGood default for most apps
NoneAlways sent cross-site (requires Secure)Third-party embeds (payment widgets, analytics)

6. Sessions — The Server's Memory

While cookies live in the browser, a session is data stored on the server linked to a cookie.

Session Flow

┌────────────────────────────────────────────────────────────────┐
│                       SERVER                                   │
│                                                                │
│  In-memory store (or database):                               │
│  {                                                             │
│    "abc123": {                                                  │
│      userId: 42,                                               │
│      username: "Nandhoo",                                     │
│      loginAt: "2025-03-31T07:38:00Z",                          │
│      cart: [{ courseId: 1, qty: 1 }]                           │
│    }                                                           │
│  }                                                             │
└────────────────────────────────────────────────────────────────┘
            ▲
            │  Server looks up "abc123"
            │
Browser sends: Cookie: sessionId=abc123

The cookie is the key (claim check). The session data stored server-side is the value (the coat).

Session vs Cookie Storage

AspectCookieSession
Where storedBrowserServer (memory, DB, Redis)
Size limit~4KBUnlimited (server memory)
SecurityCan be read if not HttpOnlyOnly server can access it
PerformanceNo server lookup neededRequires server/DB lookup per request
ScalabilitySimpleSticky sessions or shared session store needed

7. Tokens — Stateless Authentication

Modern apps often use tokens (especially JWTs) instead of sessions. Tokens contain all the user data inside them — the server doesn't need to store anything.

Traditional Session Flow:
  Client                Server              Session DB
    │── POST /login ───►│                       │
    │                   │── Look up user ──────►│
    │                   │◄── sessionId=abc ──────│
    │◄── Set-Cookie: sessionId=abc ─────────────│
    │── GET /profile ──►│                       │
    │   Cookie: abc     │── Look up session ───►│
    │                   │◄── userId=42 ──────────│
    │◄── 200 user data ─│                       │

JWT Token Flow:
  Client                Server
    │── POST /login ───►│
    │                   │ Verify password, create JWT
    │◄── 200 token ─────│  (no DB write!)
    │── GET /profile ──►│
    │   Bearer: token   │ Verify JWT signature, read claims
    │◄── 200 user data ─│  (no DB read!)

Anatomy of a JWT (JSON Web Token)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJDb2RuS2lkcyIsImlhdCI6MTcxMTg2NDI4MCwiZXhwIjoxNzExOTUwNjgwfQ.X5kLmQ3Rn8Ps7tH2MvJz4Yw9Dk1Nc6Ua0Fe5Bi3Go

Header (base64):        eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  → {"alg":"HS256","typ":"JWT"}

Payload (base64):       eyJzdWIiOiI0MiIsIm5hbWUiOiJDb2RuS2lkcyIsImlhdCI6MTcxMTg2NDI4MCwiZXhwIjoxNzExOTUwNjgwfQ
  → {"sub":"42","name":"Nandhoo","iat":1711864280,"exp":1711950680}

Signature:              X5kLmQ3Rn8Ps7tH2MvJz4Yw9Dk1Nc6Ua0Fe5Bi3Go
  → HMACSHA256(base64Header + "." + base64Payload, secretKey)

⚠️ Important: JWT payloads are base64-encoded, NOT encrypted. Do NOT put passwords or secrets in a JWT payload — anyone can decode it. The signature only proves it hasn't been tampered with.


8. Authentication Flows

Cookie-Session Based (Traditional)

// 1. Login — send credentials
const loginRes = await fetch('/api/auth/login', {
  method:  'POST',
  headers: { 'Content-Type': 'application/json' },
  body:    JSON.stringify({ email, password }),
  credentials: 'include'  // Required to receive and send cookies
});
// Server sets: Set-Cookie: sessionId=abc123; HttpOnly; Secure

// 2. Subsequent requests — cookie sent automatically
const profileRes = await fetch('/api/profile', {
  credentials: 'include'
});
// Browser automatically includes: Cookie: sessionId=abc123

// 3. Logout — delete the session
await fetch('/api/auth/logout', {
  method:      'POST',
  credentials: 'include'
});
// Server responds: Set-Cookie: sessionId=; Max-Age=0

Token-Based (Modern)

// 1. Login — receive token
const { token } = await fetch('/api/auth/login', {
  method:  'POST',
  headers: { 'Content-Type': 'application/json' },
  body:    JSON.stringify({ email, password })
}).then(r => r.json());

// Store token (preferably in memory, or httpOnly cookie —
// NOT localStorage for high-security apps)
localStorage.setItem('accessToken', token);

// 2. Subsequent requests — manually attach token
const profile = await fetch('/api/profile', {
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('accessToken')}`
  }
}).then(r => r.json());

9. Security: Protecting Cookies and Sessions

XSS (Cross-Site Scripting) Attack

Attacker injects JavaScript into your page to steal cookies.

Defence: Set HttpOnly on session cookies — JavaScript cannot access them even if injected.

Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax

CSRF (Cross-Site Request Forgery) Attack

Attacker tricks the user's browser into sending an authenticated request to your server from a malicious page.

Defence: Set SameSite=Lax or SameSite=Strict on session cookies.

Set-Cookie: sessionId=abc123; SameSite=Lax

Or use CSRF tokens — a random value embedded in forms and validated server-side.

Session Fixation

Attacker sets a known sessionId on the user before they log in, then hijacks the session.

Defence: Always regenerate the sessionId on login.

// After successful authentication on the server:
req.session.regenerate(() => {  // Creates a new sessionId
  req.session.userId = user.id;
  res.json({ success: true });
});

Short-Lived Tokens with Refresh Tokens

Access Token:  Short-lived (15 min) — used for API calls
Refresh Token: Long-lived (7 days) — used only to get new access tokens

Flow:
  User logs in → receives access token + refresh token
  Uses access token for API calls
  Access token expires → use refresh token to silently get new access token
  Refresh token expires → user must log in again

10. Reading and Setting Cookies in JavaScript

// Reading cookies (only non-HttpOnly cookies are visible)
console.log(document.cookie); // "theme=dark; language=en"

// Setting a cookie
document.cookie = 'theme=dark; Max-Age=2592000; SameSite=Lax';

// Deleting a cookie (set Max-Age to 0)
document.cookie = 'theme=; Max-Age=0';

// Parsing cookies into an object
function parseCookies() {
  return document.cookie
    .split('; ')
    .reduce((acc, pair) => {
      const [key, value] = pair.split('=');
      acc[decodeURIComponent(key)] = decodeURIComponent(value);
      return acc;
    }, {});
}

const cookies = parseCookies();
console.log(cookies.theme); // "dark"

11. Common Mistakes

MistakeProblemFix
Storing session data in cookiesVisible to client, can be forgedStore only the session ID in the cookie; keep data server-side
Session cookies without HttpOnlyJavaScript can steal the cookie via XSSAlways use HttpOnly for session cookies
Session cookies without SecureCookie can be stolen on HTTP connectionsAlways use Secure so cookies only travel over HTTPS
Not using SameSiteVulnerable to CSRF attacksSet SameSite=Lax or Strict
Storing JWTs in localStorageXSS attack can read localStorageStore in memory or use HttpOnly cookies
Never expiring sessionsStolen sessions are valid foreverSet a Max-Age and implement server-side session expiry
Sending sensitive data in the Referer headerPrevious URL leaks to third partiesUse Referrer-Policy: strict-origin-when-cross-origin

12. Mini Exercises

  1. Open DevTools → ApplicationCookies on any website you're logged into. What cookies exist? Do they have HttpOnly? Secure? SameSite?
  2. In your browser console, run document.cookie. Are there any cookies? Which ones are missing (hidden by HttpOnly)?
  3. Log into a website, then look at the Set-Cookie header in the Network tab during the login response. What attributes does the session cookie have?
  4. Try to delete a cookie using document.cookie = 'name=; Max-Age=0'. Then refresh and check if it's gone.
  5. Decode the payload of a JWT at jwt.io. What claims does it contain? What does exp mean?

13. Key Terms

TermMeaning
StatelessHTTP has no built-in memory of previous requests
CookieSmall text value stored by the browser, sent on every matching request
SessionServer-side data linked to a cookie key
JWTJSON Web Token — self-contained, signed token containing user claims
HttpOnlyCookie attribute preventing JavaScript from reading the cookie
SecureCookie attribute — only send over HTTPS
SameSiteCookie attribute controlling cross-site sending (prevents CSRF)
XSSCross-Site Scripting — injecting malicious JS into a page
CSRFCross-Site Request Forgery — tricking a browser into making authenticated requests
Refresh tokenLong-lived token used to obtain a new short-lived access token