A Django gym portal's custom password-change view logs the member out right after saving; why, and how does update_session_auth_hash() change what happens to their sessions?
answer
- the session remembers something about the password
- checked when request.user is loaded
- one function refreshes the current session
- other devices fail on their next request
basics
~20 slogin() stores an HMAC of the password field in the session, and each request recomputes and compares it. A new password changes it, so the session is flushed. update_session_auth_hash() stores the new hash and cycles the key for the current session only.
solid answer
~40 sAt login Django saves `_auth_user_hash`, the result of `user.get_session_auth_hash()`: an HMAC of the stored password hash keyed by `SECRET_KEY`. Whenever `request.user` is loaded, `get_user()` recomputes that value and compares it in constant time; on a mismatch it flushes the session and returns `AnonymousUser`. A custom view that calls `set_password()` and `save()` therefore invalidates the member's own session. Calling `update_session_auth_hash(request, user)` after saving cycles the session key and writes the new hash, so the member stays signed in on this device. Every other device still carries the old hash and is logged out on its next request. That is the built-in way to 'sign out everywhere', and `PasswordChangeView` already does it for you.
code
python · 16 linesfrom django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
from django.shortcuts import redirect, render
@login_required
def change_member_password(request):
form = PasswordChangeForm(user=request.user, data=request.POST or None)
if request.method == 'POST' and form.is_valid():
form.save()
update_session_auth_hash(request, form.user)
messages.success(request, 'Password changed. Other devices have been signed out.')
return redirect('member-account')
return render(request, 'members/change_password.html', {'form': form})go deeper
Recall that changing a password in a custom view needs update_session_auth_hash(), or the member gets signed out, and that PasswordChangeView already calls it.
Explain the mechanism: an HMAC of the password field stored at login, recomputed by get_user() when request.user loads, and a flush on mismatch.
Use the behaviour operationally: password changes and deactivation as sign-out-everywhere levers, lazy invalidation on other devices, and SECRET_KEY rotation that must not sign everyone out.
Decide which events should end all sessions, such as email change or MFA reset, and whether overriding get_session_auth_hash() to include them fits the product.
## The session hash When `login()` runs it stores three values in the session, one of which is **`_auth_user_hash`**. It comes from `AbstractBaseUser.get_session_auth_hash()`, which computes a `salted_hmac` with SHA-256 over the user's `password` field, using `SECRET_KEY`. The raw password is never involved; the input is the stored password hash. On later requests, `AuthenticationMiddleware` gives the request a lazy `request.user`. The first time it is evaluated, `django.contrib.auth.get_user()`: 1. reads `_auth_user_id` and `_auth_user_backend` from the session; 2. asks that backend's `get_user()` for the user; 3. recomputes `get_session_auth_hash()` and compares it with the stored value using a constant-time comparison; 4. if the stored hash is missing or does not match (and no fallback secret matches), calls `session.flush()` and returns `AnonymousUser`. The check is lazy: it happens when a view, template or middleware first touches `request.user`, which on almost every page is immediately. ## Why the custom view logs the member out A hand-written view typically does: ```python form.save() # calls set_password() and saves ``` `set_password()` stores a new hash in the `password` field, so `get_session_auth_hash()` now returns a different value. The session still holds the old one. On the next request, or even later in the same request if `request.user` is reloaded, the comparison fails and the member is signed out. Nothing is broken; the view skipped a step. ## What update_session_auth_hash() does `update_session_auth_hash(request, user)`: - calls `request.session.cycle_key()`, so a session key stolen before the password change stops working; - if `request.user == user`, writes the new `get_session_auth_hash()` into `_auth_user_hash`. It affects **only the session on this request**. `PasswordChangeView` calls it in `form_valid()`, and the admin's own password-change view does the same. The async version is `aupdate_session_auth_hash()`. ## What happens to the member's other sessions | Session | After a password change with update_session_auth_hash() | |---|---| | This browser | new key, new hash, still signed in | | Phone app, other browsers | old hash; flushed on their next request | | Sessions of other members | unaffected | This gives a member who suspects their account was used elsewhere a simple lever: change the password and every other session dies. The same mechanism explains a support question: when staff reset a member's password in the admin, the member is signed out on all devices. ## Related situations to recognise - **Deactivation.** With the default `ModelBackend`, `get_user()` returns `None` for a user whose `is_active` is `False`, so a deactivated member is signed out on the next request as well. - **Secret rotation.** Because the hash is keyed by `SECRET_KEY`, changing the key would sign everyone out; Django checks `SECRET_KEY_FALLBACKS` before flushing and re-stores the hash under the current key when a fallback matches. - **Custom user models.** Any model built on `AbstractBaseUser` gets this behaviour; a model that defines its own `get_session_auth_hash()` controls what invalidates sessions. ## Diagnosing the symptom in production When members report being signed out after changing their password, the evidence usually looks like this: - the password change succeeds and is saved; - the redirect after the change lands on the sign-in page, or on a page showing the member as anonymous; - sessions on other devices also end, which is expected and hides the real bug. The cause is almost always a custom view or an API endpoint that saves the new password without calling `update_session_auth_hash()`. Grep for `set_password(` outside of `PasswordChangeView` and check each call site. Code that changes the password on behalf of a member, such as a staff tool, should not call it for the member's session, because the point there is to end their sessions. ## Checklist for a custom password-change view 1. Validate the old password and the new one with `PasswordChangeForm` or equivalent. 2. Save the user. 3. Call `update_session_auth_hash(request, form.user)`. 4. Tell the member that other devices have been signed out.
- Does update_session_auth_hash() sign out the member's other devices immediately?No. It only rewrites the hash and cycles the key of the current session. The other sessions are untouched in storage; they are rejected lazily when their next request loads `request.user`, `get_user()` sees the stale hash, flushes that session and returns `AnonymousUser`.
- Why does update_session_auth_hash() also cycle the session key?A password change is often a reaction to suspected compromise. If the attacker already holds this browser's session key, keeping the key would let them continue with the refreshed hash. Cycling it means only the browser that performed the change keeps a valid session.
saying these in an interview costs you the question
- Django stores the raw or hashed password itself in the session
- update_session_auth_hash() immediately deletes every other session
- The session hash is only checked once, at login
- A password change never affects sessions on other devices
- Deactivating a user has no effect until their session expires