In Django's contrib.auth, what is the difference between authenticate() and login(), and why does a sign-in view call both?
answer
- checking versus remembering
- one returns a user or nothing
- the other writes to the session
- which backend vouched for the user
basics
~20 sauthenticate() checks credentials against the configured backends and returns a user or None without changing any state. login() takes that user and records it in the session, so later requests arrive with request.user set. A sign-in view needs both steps.
solid answer
~40 s`authenticate(request, **credentials)` walks `AUTHENTICATION_BACKENDS`, asks each compatible backend to verify the credentials, and returns the first user it gets back, annotated with `user.backend`, or `None` after sending `user_login_failed`. It touches no session and sets no cookie. `login(request, user)` is the stateful step: it rotates or flushes the session, stores the user's primary key, the backend path and a password-derived hash in `request.session`, sets `request.user`, rotates the CSRF token and sends `user_logged_in`. So a view that only authenticates has proved the password but logs nobody in, and a view that calls `login()` on an unverified user object skips the credential check entirely. The built-in `LoginView` does both through `AuthenticationForm`.
code
python · 17 linesfrom django.contrib.auth import authenticate, login
from django.shortcuts import redirect, render
def member_sign_in(request):
error = None
if request.method == 'POST':
user = authenticate(
request,
username=request.POST.get('membership_number'),
password=request.POST.get('password'),
)
if user is not None:
login(request, user)
return redirect('class-schedule')
error = 'Membership number or password is incorrect.'
return render(request, 'members/sign_in.html', {'error': error})go deeper
Recall the pairing: authenticate() returns a user or None, login() stores that user in the session, and request.user is an AnonymousUser when nobody is signed in.
Explain what login() writes to the session, why it needs the backend path, and how AuthenticationMiddleware rebuilds request.user from those keys on the next request.
Spot the bypass of calling login() on an unverified user and the ValueError with multiple backends, and know where the is_active check really lives.
Use the split deliberately: re-verify credentials for sensitive actions with authenticate() alone, and let recovery flows call login() only after an equivalent proof.
## Two steps, two jobs `django.contrib.auth` splits signing in into **verification** and **persistence**: | Function | Input | Output | Side effects | |---|---|---|---| | `authenticate(request, **credentials)` | credentials such as `username` and `password` | a user object or `None` | none on success; sends `user_login_failed` on failure | | `login(request, user, backend=None)` | a user object already verified | nothing | writes the session, sets `request.user`, rotates the CSRF token, sends `user_logged_in` | Keeping them apart lets you verify a password without logging anyone in (for example to confirm a sensitive action) and lets flows such as password reset log a user in without asking for credentials again. ## What authenticate() does 1. It iterates over `AUTHENTICATION_BACKENDS` in order, skipping any backend whose `authenticate()` signature cannot accept the keyword arguments you passed. 2. Each backend returns a user or `None`. The first user wins; Django sets `user.backend` to that backend's dotted path. 3. A backend may raise `PermissionDenied` to veto the login outright; `authenticate()` catches it, stops trying other backends and falls through to failure. 4. On failure it sends `user_login_failed` with the credentials cleansed of anything that looks like a password or token, and returns `None`. With the default `ModelBackend`, an account whose `is_active` is `False` is rejected here too, so `authenticate()` returns `None` for a deactivated member. ## What login() does `login()` assumes the user is genuine. It: - rotates the session key for an anonymous session with `cycle_key()`, or empties it with `flush()` if it belonged to a different user; - stores three keys: `_auth_user_id` (the primary key as a string), `_auth_user_backend` (the backend path) and `_auth_user_hash` (an HMAC of the password field); - sets `request.user` for the rest of the current request; - rotates the CSRF token and sends `user_logged_in`. The backend path matters. `login()` takes it from the `backend` argument or from `user.backend`, which `authenticate()` set. If neither exists and more than one backend is configured, it raises `ValueError` asking you to pass `backend`. With a single backend it uses that one. On every later request, `AuthenticationMiddleware` makes `request.user` a lazy object that reads those keys, asks the stored backend's `get_user()` for the user and checks the hash; if anything is missing or wrong you get an `AnonymousUser`. ## Putting them together A members-only gym portal's sign-in view verifies first and persists second: ```python user = authenticate(request, username=number, password=password) if user is not None: login(request, user) ``` Most projects never write this by hand: `LoginView` uses `AuthenticationForm`, whose `clean()` calls `authenticate()` and then `confirm_login_allowed()` (which rejects inactive users with an error message), and whose `form_valid()` calls `login()` with `form.get_user()`. ## What the next request sees The split also explains what happens after the response is sent. On the following request `AuthenticationMiddleware` installs a lazy `request.user`; the first access runs `get_user()`, which: - reads `_auth_user_id` and `_auth_user_backend` from the session; - checks that the backend path is still listed in `AUTHENTICATION_BACKENDS`; - calls that backend's `get_user(user_id)`, which for `ModelBackend` also rejects inactive accounts; - compares the stored `_auth_user_hash` with a freshly computed one. Any failure yields `AnonymousUser`, whose `is_authenticated` is `False`, whose `pk` is `None` and whose permission checks come back empty. That is why templates can test `user.is_authenticated` without first checking for `None`. ## Timing and enumeration `ModelBackend.authenticate()` runs the password hasher even when no account matches the username, by hashing the submitted password on a throwaway user instance. The response time is then similar for an unknown membership number and a wrong password, which makes it harder to discover which numbers exist. Custom backends should keep that property. ## Common mistakes - **Calling only `authenticate()`** and expecting the member to be logged in on the next page. Nothing was stored, so the next request sees `AnonymousUser`. - **Calling `login()` on a user fetched by primary key** without verifying anything, which is an authentication bypass if the key came from the request. - **Treating `None` as an exception path.** `authenticate()` does not raise for bad credentials; you must test for `None`. - **Assuming `login()` checks `is_active`.** It does not; the check lives in `ModelBackend` and in `AuthenticationForm`.
- Why can login() fail with a ValueError when you pass it a user loaded with get()?`login()` needs to store which backend vouched for the user. It reads `user.backend`, which only `authenticate()` sets. With several entries in `AUTHENTICATION_BACKENDS` and no `backend=` argument, it cannot choose and raises `ValueError`. Pass `backend='django.contrib.auth.backends.ModelBackend'` explicitly when logging in a user you verified another way, for example right after sign-up.
- Does login() keep data the visitor put in the session before signing in?For an anonymous session, yes: `login()` calls `cycle_key()`, which issues a new session key but keeps the stored data, so a class the visitor picked before signing in survives. If the session already belonged to a different authenticated user, `login()` calls `flush()` instead and the data is discarded.
- What does request.user return when nobody is logged in?An `AnonymousUser` instance, not `None`. It has `is_authenticated` set to `False`, `is_anonymous` set to `True`, `pk` set to `None`, and permission checks that return `False` or empty sets, so templates and views can use the same attributes without checking for `None` first.
saying these in an interview costs you the question
- authenticate() logs the user in and sets the session cookie
- authenticate() raises an exception when the password is wrong
- login() re-checks the password before storing the user
- login() refuses inactive users on its own
- request.user is None for visitors who are not signed in