Cookie and Session
HTTP is a stateless protocol — each request arrives without any memory of previous ones. Cookies and Sessions are the two primary mechanisms for persisting user identity across requests. This article explains how cookies are read and written, how Django’s server-side Session storage works, and how to implement login state management using both techniques.
Background: Why State Management Exists
The web evolved through three stages:
- No user state needed — news sites, blogs — every visitor gets the same response.
- Username/password stored in cookies — an early approach that stored credentials directly in the browser; extremely insecure.
- Random session token — when a user logs in successfully, the server generates a random string, stores user data on the server keyed to that string, and sends only the random string to the browser. On subsequent requests the browser sends the string back; the server looks it up to identify the user.
Even the token approach is not perfectly secure — if someone intercepts the token they can impersonate the user. There is no absolute security on the web; the goal is to raise the cost of an attack.
Cookie vs. Session vs. Token
| Cookie | Session | Token (JWT) | |
|---|---|---|---|
| Where data lives | Client browser | Server | Client browser |
| Format | Key/value pairs | Key/value pairs (server-side) | Encrypted payload + signature |
| Scalability | High | Limited (server memory/DB) | High (stateless) |
| Security | Lower (data visible in browser) | Higher (only token in browser) | High (signature prevents tampering) |
| Typical use | Preferences, non-sensitive state | Login sessions, shopping carts | APIs, microservices, mobile apps |
Summary:
- Cookie — information stored on the client browser by the server.
- Session — information stored on the server; the client only holds a session ID cookie.
- Session depends on Cookie — the session ID is delivered to the client via a cookie.
Cookie Operations
All cookie operations must go through a response object (HttpResponse, the object returned by render(), or the object returned by redirect()):
Setting a Cookie
# Get a response object first, then set cookies on it before returning
obj = HttpResponse('ok')
# obj = render(request, 'page.html')
# obj = redirect('/home/')
obj.set_cookie('username', 'jason')
# With options:
obj.set_cookie(
key='username',
value='jason',
max_age=3600, # expiry time in seconds
expires=None, # expiry datetime — use this for IE compatibility
path='/', # path scope: '/' means any page on the site can read it
domain=None, # domain scope
secure=False, # True = HTTPS only
httponly=False, # True = JS cannot read this cookie (not 100% foolproof)
)
# Signed (tamper-evident) cookie:
obj.set_signed_cookie('key', 'value', salt='my_secret_salt')
return objGetting a Cookie
# Plain cookie
value = request.COOKIES.get('username')
value = request.COOKIES['username'] # raises KeyError if missing
# Signed cookie
value = request.get_signed_cookie('key', default='', salt='my_secret_salt')Modifying a Cookie
Set the same key with a new value — the old value is overwritten:
obj.set_cookie('username', 'new_name')Deleting a Cookie
obj.delete_cookie('username')Login with Cookie
A common pattern is a decorator that checks for a login cookie and redirects to the login page if it is missing:
from django.shortcuts import render, redirect, HttpResponse
def login_auth(func):
def inner(request, *args, **kwargs):
# Preserve the URL the user originally tried to visit
target_url = request.get_full_path()
if request.COOKIES.get('username'):
return func(request, *args, **kwargs)
else:
return redirect('/login/?next=%s' % target_url)
return inner
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'jason' and password == '123':
# Redirect to the page the user originally wanted, or default to /home/
target_url = request.GET.get('next')
obj = redirect(target_url) if target_url else redirect('/home/')
# Write the login cookie
obj.set_cookie('username', 'jason666')
return obj
return render(request, 'login.html')
@login_auth
def home(request):
return HttpResponse("Home page — logged-in users only.")Alternatively, a middleware class can enforce login globally with a whitelist:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect
class LoginAuthMiddleware(MiddlewareMixin):
white_list = ['/login/'] # URLs that don't require login
def process_request(self, request):
if request.path not in self.white_list:
if not request.session.get('is_login'):
return redirect('/login/')Session Operations
Django’s session data is stored on the server. The client receives only a random string (sessionid) as a cookie. Django defaults to storing sessions in the django_session database table (created automatically by migrate). The default session lifetime is 14 days.
Setting Session Data
request.session['key'] = value
request.session.setdefault('k1', 123) # sets only if the key doesn't already existGetting Session Data
value = request.session.get('key')Deleting Session Data
del request.session['k1'] # delete one key
request.session.delete() # delete all server-side session data (cookie stays)
request.session.flush() # delete both server-side data AND the cookie (recommended for logout)Iterating Session Data
request.session.keys()
request.session.values()
request.session.items()Session Utility Methods
# The random string used as the session key
request.session.session_key
# Delete all expired sessions from the database
request.session.clear_expired()
# Check whether a session key exists in the database
request.session.exists('session_key')Setting Session Expiry
request.session.set_expiry(value)
# value = integer → session expires after that many seconds
# value = datetime/timedelta → session expires at that specific time
# value = 0 → session expires when the browser closes
# value = None → falls back to the global SESSION_COOKIE_AGE settingHow Sessions Work Internally
Setting a Value (request.session['hobby'] = 'girl')
- Django generates a random string internally.
- It stores the mapping
{random_string: {'hobby': 'girl'}}indjango_session(writes are buffered in memory and flushed at the end of the response by Django’s session middleware). - The random string is sent to the client browser as the
sessionidcookie.
Getting a Value (request.session.get('hobby'))
- Django reads the
sessionidcookie from the incoming request. - It queries
django_sessionfor a row matching that random string. - If found, it deserializes the stored data and returns
'girl'. - If not found, it returns
None.
django_session table stores at most one active row per browser per server. Expired rows may temporarily accumulate but are cleaned up automatically (or manually with clear_expired()). This design saves database space while keeping session lookup fast.Django Session Backends
Django supports five session storage backends out of the box:
| Backend | Description |
|---|---|
db (default) | Stored in the django_session database table |
cache | Stored in the configured cache (e.g. Redis/Memcached) — faster, volatile |
cache_db | Writes to both cache and database — fast reads, persistent |
file | Stored as files on disk |
signed_cookies | Stored client-side in a signed cookie — no server storage needed, but size-limited |
To switch backends, set SESSION_ENGINE in settings.py:
# Example: use Redis-backed cache sessions
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'