Cookies and Sessions: Web Authentication Fundamentals
Cookies and sessions are the foundation of web authentication — they let websites remember who you are across HTTP requests in a stateless protocol.
What You’ll Learn
By the end of this tutorial, you’ll understand how cookies work (Set-Cookie, domain/path/secure/HttpOnly/SameSite attributes), server-side session management, session IDs, token-based authentication vs sessions, and CSRF protection. Prerequisites: HTTP Protocol basics and Python fundamentals.
Why It Matters
Without cookies and sessions, every page load would require a login. Every “Remember me” checkbox, every shopping cart, every authenticated API call — all built on cookies and sessions.
Real-World Use
When you log into Gmail, Google sets a session cookie. Every subsequent request includes this cookie, telling the server “this request is from the logged-in user” — without re-entering your password.
Cookie Flow
sequenceDiagram participant Browser participant Server Browser->>Server: POST /login (username + password) Server->>Server: Verify credentials Server-->>Browser: 200 OK + Set-Cookie: session_id=abc123; HttpOnly; Secure Note over Browser: Stores cookie Browser->>Server: GET /dashboard (Cookie: session_id=abc123) Server->>Server: Look up session Server-->>Browser: 200 OK (Dashboard HTML)
How Cookies Work
HTTP is stateless — the server doesn’t remember who made each request. Cookies solve this by letting the server store small pieces of data (max 4KB) in the browser.
# Simple cookie setting with Python http.server
from http.server import HTTPServer, BaseHTTPRequestHandler
from datetime import datetime, timedelta
import uuid
class CookieHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/set-cookie':
# Set a session cookie
session_id = str(uuid.uuid4())
self.send_response(200)
self.send_header('Set-Cookie',
f'session_id={session_id}; HttpOnly; Path=/; Max-Age=3600')
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(b'Cookie set!')
elif self.path == '/check-cookie':
cookies = self.headers.get('Cookie', 'No cookies')
self.send_response(200)
self.send_header('Content-Type', 'text/plain')
self.end_headers()
self.wfile.write(f'Received cookies: {cookies}'.encode())
# Server would run on localhost:8000
print("Cookie server ready on port 8000")Cookie Attributes
Each cookie can have attributes that control its behavior:
import uuid
def create_session_cookie():
session_id = str(uuid.uuid4())
cookie_parts = [
f'session_id={session_id}',
'Domain=.example.com', # Available to all subdomains
'Path=/', # Available to entire site
'Max-Age=86400', # Expires in 1 day
'HttpOnly', # Not accessible via JavaScript
'Secure', # Only sent over HTTPS
'SameSite=Lax', # CSRF protection
]
return '; '.join(cookie_parts)
print("Cookie string:")
print(create_session_cookie())Expected output:
Cookie string:
session_id=550e8400-e29b-41d4-a716-446655440000; Domain=.example.com; Path=/; Max-Age=86400; HttpOnly; Secure; SameSite=LaxImportant Attributes Explained
| Attribute | Purpose | Example |
|---|---|---|
| HttpOnly | Prevents JavaScript access — stops XSS attacks from stealing cookies | HttpOnly |
| Secure | Only sends cookie over HTTPS — prevents network sniffing | Secure |
| SameSite | Controls when cookies are sent cross-site — prevents CSRF | SameSite=Lax |
| Domain | Which domains can receive the cookie | Domain=.example.com |
| Path | Which URL path the cookie applies to | Path=/ |
| Max-Age / Expires | Cookie lifetime | Max-Age=3600 |
Server-Side Sessions
Server-side sessions store user data on the server, with only a session ID in the cookie:
import uuid
import json
from datetime import datetime
class SessionStore:
def __init__(self):
self.sessions = {}
def create_session(self, user_data):
session_id = str(uuid.uuid4())
self.sessions[session_id] = {
'user': user_data,
'created_at': datetime.now().isoformat(),
'last_access': datetime.now().isoformat(),
}
return session_id
def get_session(self, session_id):
session = self.sessions.get(session_id)
if session:
session['last_access'] = datetime.now().isoformat()
return session
def destroy_session(self, session_id):
return self.sessions.pop(session_id, None)
# Usage
store = SessionStore()
# Login
sid = store.create_session({'user_id': 42, 'username': 'alice'})
print(f"Session cookie: session_id={sid}")
# Subsequent request
session = store.get_session(sid)
print(f"Session data: {session['user']}")Expected output:
Session cookie: session_id=550e8400-e29b-41d4-a716-446655440000
Session data: {'user_id': 42, 'username': 'alice'}Token-Based Auth vs Sessions
| Aspect | Server Sessions | Token-Based (JWT) |
|---|---|---|
| Storage | Server-side (memory/DB/Redis) | Client-side (browser stores token) |
| Scalability | Need shared session store across servers | Stateless — any server can verify the token |
| Revocation | Immediate — delete session from store | Hard — must maintain a blocklist |
| Payload | Session ID only (data on server) | User data encoded in the token |
| Implementation | Simpler for traditional web apps | Better for APIs and microservices |
| Expiry | Server-controlled | Token-embedded (can’t change until expiry) |
# JWT example (conceptual)
import jwt
import datetime
SECRET_KEY = 'your-secret-key'
def create_jwt(user_id, username):
payload = {
'user_id': user_id,
'username': username,
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1),
'iat': datetime.datetime.utcnow()
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
return token
def verify_jwt(token):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
# Create and verify a token
token = create_jwt(42, 'alice')
print(f"JWT Token: {token[:50]}...")
payload = verify_jwt(token)
print(f"Verified: user_id={payload['user_id']}, username={payload['username']}")Expected output:
JWT Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Verified: user_id=42, username=aliceCSRF Protection
Cross-Site Request Forgery (CSRF) tricks authenticated users into performing unwanted actions. SameSite cookies and CSRF tokens prevent this.
import secrets
from datetime import datetime
class CSRFProtection:
def __init__(self):
self.tokens = {}
def generate_token(self, session_id):
token = secrets.token_hex(32)
self.tokens[session_id] = {
'token': token,
'created_at': datetime.now().isoformat()
}
return token
def validate_token(self, session_id, token):
stored = self.tokens.get(session_id)
if stored and stored['token'] == token:
del self.tokens[session_id]
return True
return False
csrf = CSRFProtection()
session_id = 'abc123'
# Generate token (put in form)
form_token = csrf.generate_token(session_id)
print(f"CSRF token (in form): {form_token[:16]}...")
# Validate on form submission
is_valid = csrf.validate_token(session_id, form_token)
print(f"Token valid: {is_valid}")Expected output:
CSRF token (in form): a1b2c3d4e5f6g7h8...
Token valid: TrueCommon Cookies & Sessions Errors
1. Not Using HttpOnly
Cookies accessible via JavaScript (document.cookie) can be stolen by XSS attacks. Always set HttpOnly for session cookies.
2. Missing Secure Flag on HTTPS Sites
Session cookies sent over unencrypted HTTP can be intercepted by anyone on the same network. Add Secure to all session cookies.
3. Wrong SameSite Configuration
SameSite=None requires Secure. SameSite=Lax prevents most CSRF but allows top-level navigation. Choose based on your needs.
4. Session Fixation
An attacker sets a user’s session ID to a known value. Always regenerate the session ID after login.
5. Not Setting Domain Correctly
Setting Domain=example.com makes the cookie available to all subdomains. A compromised subdomain can steal cookies intended for the main domain.
Practice Questions
1. Why do we need cookies in HTTP? HTTP is stateless — each request is independent. Cookies let servers maintain state across requests (e.g., “user is logged in”).
2. What does the HttpOnly flag do? Prevents JavaScript from accessing the cookie via document.cookie. Protects against XSS-based cookie theft.
3. How do server-side sessions differ from JWT tokens? Sessions store data server-side with a session ID cookie. JWTs encode data in the token itself. Sessions can be revoked instantly; JWTs can’t.
4. What attacks does SameSite protect against? CSRF (Cross-Site Request Forgery) — prevents malicious sites from making authenticated requests on behalf of the user.
5. Challenge: Build an authentication system Create a Flask app with login, session management, and protected routes. Include CSRF protection and proper cookie attributes.
FAQ
Try It Yourself
Mini Project: Session-Based Authentication
Build a complete login/logout system with Flask: user registration, password hashing (bcrypt), session management, protected routes, and CSRF protection on forms. Security angle: Doda Browser uses secure cookie storage with HttpOnly, Secure, and SameSite flags for all authentication cookies — preventing session hijacking even if an attacker finds an XSS vulnerability.
What’s Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
What’s Next
Congratulations on completing this Cookies and Sessions tutorial! Here’s where to go from here:
- Practice daily — Inspect cookies in your browser’s DevTools
- Build a project — Create a secure authentication system
- Explore related topics — Check out CORS Complete Guide and HTTP Caching
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro