Skip to content

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:

  1. No user state needed — news sites, blogs — every visitor gets the same response.
  2. Username/password stored in cookies — an early approach that stored credentials directly in the browser; extremely insecure.
  3. 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

CookieSessionToken (JWT)
Where data livesClient browserServerClient browser
FormatKey/value pairsKey/value pairs (server-side)Encrypted payload + signature
ScalabilityHighLimited (server memory/DB)High (stateless)
SecurityLower (data visible in browser)Higher (only token in browser)High (signature prevents tampering)
Typical usePreferences, non-sensitive stateLogin sessions, shopping cartsAPIs, microservices, mobile apps

Summary:

  1. Cookie — information stored on the client browser by the server.
  2. Session — information stored on the server; the client only holds a session ID cookie.
  3. Session depends on Cookie — the session ID is delivered to the client via a cookie.

Cookie Operations

Browsers can choose to reject cookies. If a user has disabled cookies, any feature that relies on them (including most login systems) will not work.

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 obj

Getting 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 exist

Getting 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 setting

How Sessions Work Internally

Setting a Value (request.session['hobby'] = 'girl')

  1. Django generates a random string internally.
  2. It stores the mapping {random_string: {'hobby': 'girl'}} in django_session (writes are buffered in memory and flushed at the end of the response by Django’s session middleware).
  3. The random string is sent to the client browser as the sessionid cookie.

Getting a Value (request.session.get('hobby'))

  1. Django reads the sessionid cookie from the incoming request.
  2. It queries django_session for a row matching that random string.
  3. If found, it deserializes the stored data and returns 'girl'.
  4. If not found, it returns None.
The 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:

BackendDescription
db (default)Stored in the django_session database table
cacheStored in the configured cache (e.g. Redis/Memcached) — faster, volatile
cache_dbWrites to both cache and database — fast reads, persistent
fileStored as files on disk
signed_cookiesStored 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'
Last updated on