Unit 6: Cookies and Sessions, users and authentication
I. Orientation: State on a Stateless Protocol
HTTP is stateless: each request arrives with no memory of the last one. Cookies (standardised as RFC 6265) solved this by letting a server ask the browser to store a small key–value pair and return it on every subsequent request to that domain. Django wraps this raw mechanism in two contributed applications shipped since Django 1.0 (2008): django.contrib.sessions for anonymous server-side state, and django.contrib.auth for identity, permissions and password handling.
- Where state is read:
HttpRequest—request.COOKIES(a plaindictof strings),request.session(a dict-likeSessionBaseobject),request.user(aUserorAnonymousUser). - Where state is written:
HttpResponse— cookies are set on the response (response.set_cookie(...)), never on the request. - Middleware order matters: in
settings.MIDDLEWARE,SessionMiddlewaremust precedeAuthenticationMiddleware, becauserequest.useris resolved from a user id stored inside the session. - Required apps:
django.contrib.sessionsanddjango.contrib.auth(plusdjango.contrib.contenttypes, which permissions depend on) inINSTALLED_APPS. - Default cookie names:
sessionidfor the session key,csrftokenfor CSRF protection. - Trust convention: cookie data is client-controlled and untrusted; session data lives on the server and only the opaque session key travels over the wire.
II. Cookies and Sessions — client-side storage versus server-side storage
A. Definitions and the underlying mechanism
A cookie is a name/value string pair plus attributes, transported by the Set-Cookie response header and the Cookie request header; a session is a server-side dictionary keyed by a random session key that is itself delivered as a cookie.
- Cookie attributes:
max_age(seconds),expires(datetime),path,domain,secure(HTTPS only),httponly(invisible to JavaScript),samesite('Lax','Strict','None'). - Size limit: roughly 4 KB per cookie — one reason bulk data belongs in a session, not a cookie.
- Session key: a 32-character random string; the payload is serialised (JSON by default) and stored by the configured engine, so the browser learns nothing about its contents.
B. Creating Cookies and sessions in Django
Both are created through ordinary view code; the difference is where the value ends up.
1. Cookies (client-side).
def set_pref(request):
response = HttpResponse("theme saved")
response.set_cookie('theme', 'dark',
max_age=60 * 60 * 24 * 30, # 30 days
httponly=True, samesite='Lax')
return response
def read_pref(request):
theme = request.COOKIES.get('theme', 'light') # always a string
return HttpResponse(theme)- Deleting:
response.delete_cookie('theme')— implemented by re-sending the cookie with an expiry in the past;path/domainmust match those used when setting it. - Tamper-evident cookies:
response.set_signed_cookie('cart', '3', salt='cart')appends an HMAC derived fromSECRET_KEY; reading usesrequest.get_signed_cookie('cart', salt='cart', max_age=3600), which raisesBadSignatureif altered orKeyErrorif absent. Signing proves integrity, not confidentiality — the value is still readable. - Type caveat: everything returns as
str, soint(request.COOKIES['count'])is needed for arithmetic.
2. Sessions (server-side).
def cart_add(request, item):
cart = request.session.get('cart', [])
cart.append(item)
request.session['cart'] = cart # marks session modified -> saved
return HttpResponse(len(cart))- Dict-like API:
get(),pop(),setdefault(),keys(),del request.session['cart'], plusflush()(delete data and key, issue a new one),cycle_key()(keep data, new key) andset_expiry(). - Mutation detection: Django saves only when
session.modifiedisTrue. Editing a nested object in place (request.session['cart'].append(x)) is not detected — either reassign the key or setrequest.session.modified = True. - Serialisation: the default
JSONSerializeraccepts only JSON-safe values, so model instances must be reduced to ids.
C. Session engines, expiry and security settings
The engine decides the storage medium; expiry settings decide the lifetime of both the record and the cookie.
SESSION_ENGINEoptions:db(default, tabledjango_session),cache,cache_db(write-through cache),file,signed_cookies(data in the cookie itself — no server storage, but no true invalidation either).- Lifetime:
SESSION_COOKIE_AGEdefaults to1209600seconds (2 weeks).request.session.set_expiry(0)makes the cookie expire at browser close;set_expiry(None)restores the global default. - Sliding vs fixed window:
SESSION_SAVE_EVERY_REQUEST = Truere-saves and refreshes expiry on every request (sliding); the defaultFalsecounts from the last write only. - Hardening:
SESSION_COOKIE_SECURE = True,SESSION_COOKIE_HTTPONLY = True(default),SESSION_COOKIE_SAMESITE = 'Lax'(default). - Housekeeping: expired database rows are not removed automatically — run
python manage.py clearsessionson a schedule.
III. Users and Authentication — the auth application
A. The User model and the authentication pipeline
django.contrib.auth.models.User is the default identity record, and authenticate() is the only sanctioned way to verify credentials.
- Core fields:
username,password(hash string),email,first_name,last_name,is_active,is_staff,is_superuser,last_login,date_joined. - Password storage format:
<algorithm>$<iterations>$<salt>$<hash>, produced by the default PBKDF2-SHA256 hasher; hashes are never reversible. - Backends:
AUTHENTICATION_BACKENDSdefaults to['django.contrib.auth.backends.ModelBackend'], which checks the password and rejects users whoseis_activeisFalse. authenticate(request, username=..., password=...)returns aUseron success andNoneon failure — it does not create a session.
B. Creating and Managing Users in Django
Users must be created through the manager or a form, never by assigning to password directly.
from django.contrib.auth import get_user_model
User = get_user_model()
u = User.objects.create_user('asha', 'asha@example.com', 'S3cret!pass')
u.set_password('newS3cret!') # hashes in place
u.save()
u.check_password('newS3cret!') # True- Manager methods:
create_user()hashes the password;create_superuser()additionally setsis_staff=Trueandis_superuser=True. - Why not
User(password='x'): that stores plaintext;set_password()applies the hasher. - Management commands:
manage.py createsuperuser,manage.py changepassword <username>. - Deactivating rather than deleting: set
is_active = Falseto block login while preserving foreign-key history. - Forms:
UserCreationForm(username + two password fields, runs validators fromAUTH_PASSWORD_VALIDATORS) andUserChangeFormfor admin-style editing. - Custom user models:
AbstractUser: subclass to add fields while keeping username/password machinery.AbstractBaseUser: subclass for a different identifier (e.g. email), definingUSERNAME_FIELDand a custom manager.- Point
AUTH_USER_MODEL = 'accounts.User'before the first migration, and always refer to the model viaget_user_model()orsettings.AUTH_USER_MODEL.
C. Permissions, groups and profile data
Authorisation is separate from authentication and is checked per action.
- Auto-created permissions: for every model, four codenames —
add_<model>,change_<model>,delete_<model>,view_<model>. - Checking:
user.has_perm('shop.add_order'); superusers returnTruefor every check. - Assignment:
user.user_permissions.add(perm)for direct grants,user.groups.add(group)for role-based grants;Groupbundles permissions for many users. - Extra attributes: attach a
OneToOneField(settings.AUTH_USER_MODEL)profile model rather than editingUser.
IV. Login, Logout and View-level Protection
A. The built-in class-based auth views
django.contrib.auth.views supplies LoginView, LogoutView, PasswordChangeView, PasswordResetView and their *Done/*Confirm partners, all rendering templates under a registration/ directory.
- Default templates:
registration/login.htmlforLoginView,registration/logged_out.htmlforLogoutView. - Context in
login.html: a boundAuthenticationFormasform, plusnextfor post-login redirection.
B. Login and Logout URLs in Django
One include wires the whole set of authentication URLs; settings control where users land afterwards.
# project/urls.py
urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')),
]- Names created:
login,logout,password_change,password_change_done,password_reset,password_reset_done,password_reset_confirm,password_reset_complete— referenced in templates as{% url 'login' %}. - Key settings:
LOGIN_URL(default'/accounts/login/', the redirect target for unauthenticated access),LOGIN_REDIRECT_URL(default'/accounts/profile/'),LOGOUT_REDIRECT_URL(defaultNone, which renders the logged-out template instead). All three accept URL names. - The
nextparameter:?next=/orders/on the login URL overridesLOGIN_REDIRECT_URL; Django validates it against allowed hosts to prevent open redirects. - Logout must be POST in current Django versions, so the template needs a form, not a link:
<form method="post" action="{% url 'logout' %}">{% csrf_token %}
<button type="submit">Log out</button>
</form>- Overriding:
path('accounts/login/', LoginView.as_view(template_name='shop/login.html'), name='login')placed before the include.
C. Using Django Login in Views
For custom flows, drive the session directly with the three functions from django.contrib.auth.
from django.contrib.auth import authenticate, login, logout
def sign_in(request):
if request.method == 'POST':
user = authenticate(request,
username=request.POST['username'],
password=request.POST['password'])
if user is not None:
login(request, user) # writes user id + backend to session
return redirect(request.GET.get('next', 'dashboard'))
messages.error(request, 'Invalid credentials')
return render(request, 'registration/login.html')
def sign_out(request):
logout(request) # flushes the session entirely
return redirect('login')login(request, user): stores_auth_user_id,_auth_user_hashand_auth_user_backendin the session and callscycle_key(), which rotates the session key to defeat session fixation.logout(request): flushes session data and resetsrequest.usertoAnonymousUser; it is safe to call when nobody is logged in.- Identity checks:
request.user.is_authenticatedis a property, not a method —{% if user.is_authenticated %}in templates. - Password change side effect: changing a password invalidates other sessions because the stored
_auth_user_hashno longer matches; callupdate_session_auth_hash(request, user)to keep the current one alive.
D. Restricting access, and the limits of the system
Protection is applied per view, and the framework deliberately stops short of some responsibilities.
- Function views:
@login_requiredredirects anonymous users toLOGIN_URLwith?next=appended;@permission_required('shop.add_order', raise_exception=True)returns HTTP 403 instead of redirecting;@user_passes_test(lambda u: u.is_staff)for arbitrary predicates. - Class-based views:
LoginRequiredMixinandPermissionRequiredMixin, listed before the base view class in the MRO. - Limitations: no registration view is provided (build one with
UserCreationForm); permissions are model-level, not per-object; and cookies deleted or blocked by the browser silently break both sessions and login, so any state built on them must degrade gracefully.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →