Managing HTTP State in Django: Cookies and Sessions Explained
The Role of Cookies in Stateless HTTP
HTTP functions as a stateless protocol, with each request being isolated and independent of previous interactions. This design creates a challenge when applications need to preserve user data across multiple page loads. Cookies serve as client-side key-value stores carried within the browser, solving this problem.
A server generates a cookie and includes it in the response headers. The browser then stores this data locally and attaches it to subsequent requests, allowing the server to reconstruct session context.
Working with Cookies in Djengo
Storing Values on the Client
Django provides several methods on the response object to set cookies. The standard approach uses set_cookie, while set_signed_cookie adds a cryptographic signature to verify integrity.
# Typical usage on a response object
resp = render(request, 'template.html')
# or resp = HttpResponse(...)
resp.set_cookie('preference', 'dark_mode')
resp.set_signed_cookie('cart_id', 'abc123', salt='hash-salt-here')
return resp
The following parameters control cookie behavior:
max_age: Duration in seconds;Nonepersists until the browser closes.expires: A datetime string or object, often needed for Internet Explorer compatibility.path: URL prefix where the cookie is valid (e.g.,/store/).domain: Enables cross-subdomain sharing, such as.mysite.com.secure: Transmit only over HTTPS.httponly: Block JavaScript access, though not foolproof against packet inspection.
Signed cookies work by internally calling set_cookie:
def set_signed_cookie(self, key, value, salt='', **kwargs):
value = signing.get_cookie_signer(salt=key + salt).sign(value)
return self.set_cookie(key, value, **kwargs)
To retrieve signed values later:
raw_value = request.get_signed_cookie('cart_id', salt='hash-salt-here', default=None)
Accessing and Removing Cookies
On incoming requests, cookies are available through a dictionary-like interface:
# Direct lookup or using .get for safe access
selected = request.COOKIES.get('preference')
status = request.COOKIES.get('login_flag', 'false')
To delete a cookie, instruct the browser to expire it via the response:
resp.delete_cookie('preference')
Practical Login Guard with Cookies
A decorator can enforce authentication by checking for a signed cookie before allowing access to protected views.
from functools import wraps
from django.shortcuts import redirect, render
def require_auth(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
target = request.get_full_path()
if request.get_signed_cookie('auth_token', salt='secret-2025', default=None) == 'valid':
return func(request, *args, **kwargs)
return redirect(f'/login/?next={target}')
return wrapper
def login_view(request):
if request.get_signed_cookie('auth_token', salt='secret-2025', default=None) == 'valid':
return HttpResponse('Already logged in')
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'admin' and password == 'secretpass':
redirect_to = request.GET.get('next', '/dashboard/')
reply = redirect(redirect_to)
reply.set_signed_cookie('auth_token', 'valid', salt='secret-2025')
return reply
return render(request, 'login.html')
@require_auth
def dashboard(request):
return HttpResponse('secure content')
Server-Side Persistence with Sessions
Cookies help track state but store data on the client, which introduces security risks. Sessions shift storage to the server. Django creates a unique session identifier, sends it to the browser as a cookie (typically named sessionid), and maintains session data in a backend.
Before using sessions, run python manage.py migrate to create the necessary database table.
Core Session Operations
Interacting with the session resembles working with a dictionary on the request object:
# Setting values triggers the creation of a session entry and cookie
request.session['theme'] = 'ocean'
request.session.setdefault('visits', 0)
# Retrieving data
name = request.session.get('user_name', 'Guest')
# Removing a specific key
del request.session['temporary_data']
# Iterating over keys, values, or pairs
for k in request.session.keys():
pass
for v in request.session.values():
pass
for k, v in request.session.items():
pass
# Getting the auto-generated session identifier
session_key = request.session.session_key
Several utility methods manage session lifecycle:
request.session.clear_expired()removes stale entries from the database.request.session.exists('some_key')verifies a session key exists.request.session.delete()removes only the database row.request.session.flush()removes both the database row and the client cookie—useful during logout.
Sesssion expiry is configurable:
# Expire after 1800 seconds (30 minutes)
request.session.set_expiry(1800)
# Expire on browser close
request.session.set_expiry(0)
# Use the global policy from settings
request.session.set_expiry(None)
Available Session Backends
Django's SESSION_ENGINE setting determines where data lives:
- Database (default):
django.contrib.sessions.backends.db - Caching system:
django.contrib.sessions.backends.cache(requiresSESSION_CACHE_ALIAS) - File system:
django.contrib.sessions.backends.file(configureSESSION_FILE_PATH) - Hybrid cache and database:
django.contrib.sessions.backends.cached_db - Signed cookies:
django.contrib.sessions.backends.signed_cookies(data resides on client but is signed)
Common configuration variables in settings.py include:
SESSION_COOKIE_NAME = 'sessionid'
SESSION_COOKIE_AGE = 1209600 # two weeks in seconds
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_SAVE_EVERY_REQUEST = False
Implementing Login Checks Using Sessions
The decorator pattern adjusts naturally to session-based authentication:
def restrict_access(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
next_page = request.get_full_path()
if request.session.get('authenticated') == 'yes':
return func(request, *args, **kwargs)
return redirect(f'/login/?next={next_page}')
return wrapper
def login(request):
if request.session.get('authenticated') == 'yes':
return HttpResponse('You are already authenticated')
if request.method == 'POST':
user = request.POST.get('username')
passw = request.POST.get('password')
if user == 'admin' and passw == 'p@ss':
request.session['authenticated'] = 'yes'
destination = request.GET.get('next', '/dashboard/')
return redirect(destination)
return render(request, 'login_form.html')
Using the Decorator on Class-Based Views
Class-based views need a different approach for applying function decorators, typically via method_decorator.
Decorating Individual HTTP Methods
from django.utils.decorators import method_decorator
class ProfileView(View):
def get(self, request):
return render(request, 'profile.html')
@method_decorator(restrict_access)
def post(self, request):
# Handle form submission
return redirect('/dashboard/')
Wrapping the dispatch Method
Decorating dispatch secures all handlers (get, post, etc.) at once.
class SettingsView(View):
@method_decorator(restrict_access)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request):
return render(request, 'settings.html')
Applying at the Class Level
When decorating the class directly, specify the method name with the name parameter.
@method_decorator(restrict_access, name='get')
@method_decorator(restrict_access, name='post')
class ReportsView(View):
def get(self, request):
return render(request, 'reports.html')
def post(self, request):
# Generate report
return redirect('/reports/')