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
| Header | Example | What it does |
|---|---|---|
Host | api.nandhoo.com | Required in HTTP/1.1 — specifies the server (allows virtual hosting) |
Accept | application/json | What formats the client will accept |
Accept-Language | en-GB,en;q=0.9 | Preferred language(s) for the response |
Accept-Encoding | gzip, br | Compression algorithms the client supports |
Authorization | Bearer eyJ... | Authentication credentials |
Cookie | id=abc123; prefs=dark | Cookies to send to the server |
User-Agent | Mozilla/5.0 ... | Browser and OS identification |
Referer | https://google.com | The previous page (note: intentionally misspelled in spec) |
Origin | https://nandhoo.com | The origin of a cross-origin request (CORS) |
Content-Type | application/json | Format of the request body |
Content-Length | 128 | Size of the request body in bytes |
If-None-Match | "etag-value" | Conditional request — skip if ETag unchanged |
Cache-Control | no-cache | Caching directives for the request |
3. Common Response Headers
| Header | Example | What it does |
|---|---|---|
Content-Type | application/json; charset=utf-8 | Format of response body |
Content-Length | 284 | Body size in bytes |
Content-Encoding | gzip | Compression used on the body |
Set-Cookie | id=abc; HttpOnly; Secure | Instructs browser to store a cookie |
Cache-Control | public, max-age=3600 | Caching rules for the response |
ETag | "a1b2c3" | Resource version fingerprint |
Location | /api/users/99 | Redirect URL or newly created resource URL |
WWW-Authenticate | Bearer realm="api" | Tells client how to authenticate (sent with 401) |
Retry-After | 60 | Seconds to wait before retrying (sent with 429 or 503) |
X-Rate-Limit-Limit | 100 | Max requests per window |
X-Rate-Limit-Remaining | 78 | Requests left in current window |
Strict-Transport-Security | max-age=31536000 | Force 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
| Attribute | Example | What it does |
|---|---|---|
Name=Value | sessionId=abc123 | The cookie data |
Domain | Domain=.nandhoo.com | Which domain (and subdomains) receives this cookie |
Path | Path=/api | Only sent for URLs starting with /api |
Max-Age | Max-Age=3600 | Expiry in seconds from now (0 deletes it) |
Expires | Expires=Mon, 31 Mar 2025 GMT | Absolute expiry date (older alternative to Max-Age) |
HttpOnly | HttpOnly | Inaccessible to JavaScript (document.cookie cannot read it) — prevents XSS theft |
Secure | Secure | Only sent over HTTPS connections |
SameSite | SameSite=Lax | Controls cross-site cookie sending — prevents CSRF |
SameSite Values Explained
| Value | Behaviour | Use case |
|---|---|---|
Strict | Never sent cross-site | Highest security — for session cookies on banking/critical apps |
Lax | Sent on top-level navigations (clicking a link), not on AJAX cross-site requests | Good default for most apps |
None | Always 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
| Aspect | Cookie | Session |
|---|---|---|
| Where stored | Browser | Server (memory, DB, Redis) |
| Size limit | ~4KB | Unlimited (server memory) |
| Security | Can be read if not HttpOnly | Only server can access it |
| Performance | No server lookup needed | Requires server/DB lookup per request |
| Scalability | Simple | Sticky 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
| Mistake | Problem | Fix |
|---|---|---|
| Storing session data in cookies | Visible to client, can be forged | Store only the session ID in the cookie; keep data server-side |
Session cookies without HttpOnly | JavaScript can steal the cookie via XSS | Always use HttpOnly for session cookies |
Session cookies without Secure | Cookie can be stolen on HTTP connections | Always use Secure so cookies only travel over HTTPS |
Not using SameSite | Vulnerable to CSRF attacks | Set SameSite=Lax or Strict |
Storing JWTs in localStorage | XSS attack can read localStorage | Store in memory or use HttpOnly cookies |
| Never expiring sessions | Stolen sessions are valid forever | Set a Max-Age and implement server-side session expiry |
Sending sensitive data in the Referer header | Previous URL leaks to third parties | Use Referrer-Policy: strict-origin-when-cross-origin |
12. Mini Exercises
- Open DevTools → Application → Cookies on any website you're logged into. What cookies exist? Do they have
HttpOnly?Secure?SameSite? - In your browser console, run
document.cookie. Are there any cookies? Which ones are missing (hidden byHttpOnly)? - Log into a website, then look at the
Set-Cookieheader in the Network tab during the login response. What attributes does the session cookie have? - Try to delete a cookie using
document.cookie = 'name=; Max-Age=0'. Then refresh and check if it's gone. - Decode the payload of a JWT at jwt.io. What claims does it contain? What does
expmean?
13. Key Terms
| Term | Meaning |
|---|---|
| Stateless | HTTP has no built-in memory of previous requests |
| Cookie | Small text value stored by the browser, sent on every matching request |
| Session | Server-side data linked to a cookie key |
| JWT | JSON Web Token — self-contained, signed token containing user claims |
| HttpOnly | Cookie attribute preventing JavaScript from reading the cookie |
| Secure | Cookie attribute — only send over HTTPS |
| SameSite | Cookie attribute controlling cross-site sending (prevents CSRF) |
| XSS | Cross-Site Scripting — injecting malicious JS into a page |
| CSRF | Cross-Site Request Forgery — tricking a browser into making authenticated requests |
| Refresh token | Long-lived token used to obtain a new short-lived access token |