HTTP is stateless — each request is independent. Sessions fake state by storing data server-side and sending the client a session ID in a cookie. On each request, the client sends the cookie, and the server looks up the session. Simple, but relies on cookies working correctly.
4 min read
HTTP is stateless. The server doesn't remember anything about previous requests:
Request 1: GET / (browser: "hello server")
Response: 200 OK (server: "hello browser")
Request 2: GET /profile (browser: "show me my profile")
Response: 200 OK (server: "wait, who are you?")
The server has no memory of Request 1. It doesn't know the browser is logged-in alice. Every request is treated as coming from an anonymous user.
Sessions fake statefulness: the server remembers who is who, and tells the browser a secret identifier to prove its identity.
POST /login with email + password.session_id = generate_random_token()
sessions[session_id] = {
'user_id': alice.id,
'email': 'alice@example.com',
'created_at': now,
'expires_at': now + 24_hours
}Set-Cookie: session_id=<session_id>; Path=/; HttpOnly; Secure; SameSite=Strict
GET /profile
Cookie: session_id=<session_id>
session_id = request.cookies['session_id']
session = sessions.get(session_id)
if session:
user_id = session['user_id']
return get_user_profile(user_id)
else:
return 401 # session expired or invalidPOST /logout.del sessions[session_id].A cookie is a small file the browser stores. On every request to the server, the browser automatically includes cookies (if they match the domain/path).
Set-Cookie: session_id=abc123; Domain=myapp.com; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=86400
Flags mean:
A well-configured session cookie:
Set-Cookie: session_id=<random_token>; HttpOnly; Secure; SameSite=Strict
In-memory (simplest for single-server apps):
sessions = {} # in-memory store
# Create session
sessions[session_id] = {'user_id': alice.id, 'expires_at': now + 24_hours}
# Check session
if session_id in sessions:
user_id = sessions[session_id]['user_id']Problems: if the server restarts, all sessions are lost. If the app scales to multiple servers, only one server knows about the session.
Database (for multi-server apps):
# Create session
db.sessions.insert({'session_id': session_id, 'user_id': alice.id, 'expires_at': now + 24_hours})
# Check session
session = db.sessions.find_one({'session_id': session_id, 'expires_at': {'$gt': now}})
if session:
user_id = session['user_id']Database sessions survive server restarts and work across multiple servers.
Sessions are stateful: the server must maintain a database of sessions.
JWT (JSON Web Token) is an alternative: the client stores encrypted data, and the server verifies the encryption signature:
Claim (client has this data): {"user_id": alice.id, "expires_at": 2025-08-25}
Server signs it: JWT = encode_and_sign(claim, secret_key)
Client stores the JWT and sends it back
Server verifies: does the signature match? If yes, trust the data inside.
Sessions and JWTs are different approaches; each has trade-offs covered in a future lesson. For now: sessions store data server-side and send a reference to the client; JWTs store data client-side and send the whole thing.
Missing HttpOnly flag
# WRONG — JavaScript can read this cookie
response.set_cookie('session_id', token) # no HttpOnly
# CORRECT
response.set_cookie('session_id', token, httponly=True, secure=True)Without HttpOnly, an XSS vulnerability (injected script) can steal the session cookie.
Missing SameSite flag
# WRONG — sent on cross-site requests (CSRF risk)
response.set_cookie('session_id', token) # no SameSite
# CORRECT
response.set_cookie('session_id', token, samesite='Strict')Without SameSite, an attacker can trick a logged-in user into making unwanted requests.
Not expiring sessions
# WRONG — sessions live forever
response.set_cookie('session_id', token) # no expiration
# CORRECT
response.set_cookie('session_id', token, max_age=86400) # 1 dayWithout expiration, a stolen session ID works forever. Expiration forces re-authentication periodically.
Storing sensitive data in cookies
Cookies are sent with every request and visible in the browser. Don't store credit cards, passwords, or PII in cookies.
# WRONG
response.set_cookie('payment_method', 'visa-1234') # card visible in cookies
# CORRECT
response.set_cookie('session_id', random_token) # token is meaningless without server lookup
# (server stores payment method in database, keyed by session_id)Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why are sessions needed in a stateless HTTP protocol?
2. What does the HttpOnly cookie flag do?
3. Why should session cookies have an expiration time?
4. When should you use database-backed session storage instead of in-memory?
Auth, Authorization & Multi-Tenancy