Authentication

Django Intermediate/Advanced — Authentication
Django Intermediate/Advanced
Course 2 · Chapter 1 · Authentication

🔐 Authentication

AuthenticationMiddleware has been sitting in MIDDLEWARE since Chapter 1 of the last course — active the whole time, doing nothing until you actually use it. Where FastAPI and Express typically reach for a JWT library and build the login flow by hand, Django's django.contrib.auth ships a complete, battle-tested authentication system: a user model, password hashing, session-based login, and access-control decorators, all included.

The Built-in User Model

Django's default User model already has the fields most apps need — username, email, and a password field that is always stored hashed, never in plain text:

from django.contrib.auth.models import User user = User.objects.create_user( username="jane", email="jane@example.com", password="a-strong-password", ) # create_user() hashes the password automatically — .password on the # resulting object is never the plain text, even immediately after creation.

create_user(), Not create()

Bypassing this and using the plain ORM .create() (or a ModelForm without care) would store the raw password unhashed — always go through create_user() or set_password().

Custom User Models

For a production app, swapping in a custom AUTH_USER_MODEL from day one (even one that just extends the default) is standard advice — changing it later requires a full migration rebuild.

Password Hashing, Handled For You

The Authentication & Session Security course covered bcrypt/scrypt/Argon2id and why passwords must never be stored in plain text. Django's default hasher (PBKDF2) implements that guidance automatically — no manual hashing call required anywhere in your own code:

user.set_password("a-new-password") # hashes and stores it correctly user.save() user.check_password("a-new-password") # True — verifies against the stored hash

Switching to Argon2id (the algorithm the security course recommended as the strongest modern default) is a one-line settings change, not a rewrite:

# settings.py PASSWORD_HASHERS = [ "django.contrib.auth.hashers.Argon2PasswordHasher", "django.contrib.auth.hashers.PBKDF2PasswordHasher", # kept so existing hashes still verify ]

🍪 Session-Based Login

Where a typical FastAPI/Express API issues a JWT the client stores and resends, Django's default is session-based — a session ID cookie the server looks up against server-side session data:

from django.contrib.auth import authenticate, login, logout from django.shortcuts import render, redirect def login_view(request): if request.method == "POST": username = request.POST.get("username") password = request.POST.get("password") user = authenticate(request, username=username, password=password) if user is not None: login(request, user) # establishes the session return redirect("dashboard") return render(request, "registration/login.html") def logout_view(request): logout(request) # destroys the session return redirect("login")

FastAPI/Express JWT Flow vs Django Session Flow

Typical FastAPI/Express JWT

Verify credentials → sign a JWT yourself → client stores it (localStorage or a cookie) → client resends it → you verify the signature on every request.

Django Session

login() stores the user's ID server-side and sets a session cookie — AuthenticationMiddleware looks it up and attaches request.user automatically on every subsequent request.

Notice login() does something the Session Security course insisted on doing manually: it rotates the session key on every successful login, preventing session fixation by design — one call, not a step you have to remember.

Protecting Views

Once login() has run, every later request in that session has request.user populated — @login_required is the decorator that turns "must be logged in" into one line:

from django.contrib.auth.decorators import login_required @login_required def dashboard(request): return render(request, "catalog/dashboard.html", {"user": request.user}) # An anonymous request is redirected to LOGIN_URL automatically — # no manual "if not request.user: redirect" check needed.

request.user.is_authenticated

A boolean, true for a logged-in user and false for AnonymousUser — always present, never None, so no null check is needed before reading it.

@permission_required

A step further than @login_required — checks for a specific permission ("catalog.add_book") rather than just "logged in at all."

💻 Coding Challenges

Challenge 1: Write a Registration View

Write a register view that reads username, email, and password from a POST request, creates the user with User.objects.create_user(), and logs them in immediately afterward.

Goal: Practice using create_user() (never a raw ORM .create()) for anything that touches passwords.

→ Solution

Challenge 2: Protect a View

Add @login_required to an existing book_create view, and explain what happens (in terms of the actual HTTP response) when an anonymous user requests that URL.

Goal: Practice the decorator-based access-control pattern and understand its default redirect behavior.

→ Solution

Challenge 3: Switch to Argon2

Write the PASSWORD_HASHERS setting change needed to make Argon2id the primary hashing algorithm for new passwords, while keeping existing PBKDF2-hashed passwords still able to log in.

Goal: Practice a settings-only security upgrade, and explain why the old hasher must stay in the list rather than being removed outright.

→ Solution

⚠️ Gotcha: Don't Roll Your Own Auth

It's tempting, especially coming from a FastAPI project where you built JWT auth by hand, to reach for the same pattern in Django — rolling a custom password check and a hand-written session cookie. Resist it: django.contrib.auth already handles password hashing correctly, session fixation prevention, and secure cookie flags, all reviewed by a large security-conscious community over many years. The Authentication & Session Security course's whole thesis — that auth is easy to get subtly wrong — is exactly why using the framework's built-in system, rather than reimplementing it, is the safer default here.

🎯 What's Next

With login working, the next chapter turns to building actual APIs on top of these models: Django REST Framework — serializers, viewsets, and how DRF's approach to building an API compares to a plain Django view returning JsonResponse.