Walk through Django's built-in password reset flow: which four views run, and why does the confirm view redirect to a set-password URL?
answer
- request, done, confirm, complete
- same page whether or not the email exists
- uid and token in the link
- keep the token out of the Referer
- not logged in afterwards by default
basics
~20 sPasswordResetView emails a uidb64/token link, PasswordResetDoneView confirms, PasswordResetConfirmView checks the token and sets the new password, PasswordResetCompleteView finishes. The confirm view moves the token into the session and redirects so it stays out of Referer headers.
solid answer
~40 sIncluding `django.contrib.auth.urls` wires four views. `PasswordResetView` (`password_reset/`) shows `PasswordResetForm`; on submit it looks up active users whose email matches case-insensitively and who have a usable password, emails each a link containing `uidb64` (the base64-encoded primary key) and a token from `default_token_generator`, and always redirects to `PasswordResetDoneView`, so the page reveals nothing about which emails exist. The link hits `PasswordResetConfirmView` at `reset/<uidb64>/<token>/`; if the token is valid it stores it in the session under `_password_reset_token` and redirects to the same URL with the token replaced by `set-password`, so the token cannot leak through the `Referer` header of resources on that page. The form there is `SetPasswordForm`; after saving, the session token is removed and the user lands on `PasswordResetCompleteView`. `post_reset_login` is `False` by default, so the user signs in normally.
code
python · 11 linesfrom django.contrib.auth import views as auth_views
from django.urls import include, path
urlpatterns = [
path(
'accounts/reset/<uidb64>/<token>/',
auth_views.PasswordResetConfirmView.as_view(post_reset_login=True),
name='password_reset_confirm',
),
path('accounts/', include('django.contrib.auth.urls')),
]go deeper
Name the four views in order and know that including django.contrib.auth.urls gives you all of them with default templates.
Explain what the email link contains, how get_users() filters recipients, and why the confirm view moves the token into the session.
Check the edges: enumeration-safe responses, host validation for the link domain, inactive or unusable-password users who silently get nothing, and post_reset_login.
Decide whether the built-in flow's guarantees suffice or you need rate limiting, audit events and notifications when a reset completes.
## The four views and their URLs `path('accounts/', include('django.contrib.auth.urls'))` registers these, each with a default template under `registration/`: | Step | View | URL name | Path | |---|---|---|---| | 1. ask | `PasswordResetView` | `password_reset` | `password_reset/` | | 2. acknowledge | `PasswordResetDoneView` | `password_reset_done` | `password_reset/done/` | | 3. set | `PasswordResetConfirmView` | `password_reset_confirm` | `reset/<uidb64>/<token>/` | | 4. finish | `PasswordResetCompleteView` | `password_reset_complete` | `reset/done/` | All four are exempt from `LoginRequiredMiddleware` through `login_not_required`, because the user by definition cannot sign in. ## Step 1 - PasswordResetView The view renders `PasswordResetForm`. When it is valid, `form.save()`: 1. calls `get_users(email)`, which filters the user model on the email field with `__iexact`, keeps only `is_active=True` users, and drops any user without a usable password; 2. for each match, builds a context with `uid` (`urlsafe_base64_encode` of the primary key), `token` (`token_generator.make_token(user)`), `domain`, `protocol` and `site_name`; 3. renders `registration/password_reset_email.html` and the subject template and sends the mail. Whatever happened, the view redirects to `password_reset_done`. An attacker typing addresses into the form learns nothing about which accounts exist. The domain comes from the sites framework if installed, otherwise from the request's host, which is why host validation matters for reset links. ## Step 3 - PasswordResetConfirmView This view is where the interview detail lives: - It decodes `uidb64` and loads the user; any decoding error or missing user leads to the "unsuccessful" page with `validlink=False`. - If the URL carries a real token and `check_token()` accepts it, the view stores the token in the session under `_password_reset_token` and **redirects** to the same path with the token replaced by `reset_url_token`, which is `'set-password'`. - On the `set-password` URL it re-checks the token taken from the session and, if valid, shows `SetPasswordForm`. Why the detour? The page that shows the form may load images, scripts or analytics from other hosts. If the token were still in the URL, browsers could send it to them in the `Referer` header. After the redirect the address bar holds only `set-password`. On a valid submission the view saves the new password (running the password validators through `SetPasswordForm`), deletes the session token, optionally logs the user in, and redirects to `password_reset_complete`. ## Settings and attributes you can change - `PasswordResetConfirmView.post_reset_login` - `False` by default; set it (and `post_reset_login_backend` when several backends exist) to sign the user in immediately. - `token_generator` on both views - swap in a subclass of `PasswordResetTokenGenerator`. - `email_template_name`, `html_email_template_name`, `subject_template_name`, `extra_email_context`, `from_email` on `PasswordResetView`. - `PASSWORD_RESET_TIMEOUT` - how long links stay valid, 3 days by default. ## Templates you must provide The views ship with default template names but Django's admin app supplies styled versions only when `django.contrib.admin` is installed. A project that wants its own look provides, under a `registration/` template directory: 1. `password_reset_form.html` - the email form; 2. `password_reset_email.html` and `password_reset_subject.txt` - the message, built from `protocol`, `domain`, `uid` and `token`; 3. `password_reset_done.html` - "check your inbox"; 4. `password_reset_confirm.html` - which must handle both `validlink` true and false; 5. `password_reset_complete.html` - the final page. The email template usually builds the link with `{% url 'password_reset_confirm' uidb64=uid token=token %}` prefixed by `protocol` and `domain`. ## Legacy-account pitfalls For a user base imported from an older system, two filters in `get_users()` surprise teams: accounts marked inactive and accounts whose password was set unusable during the import receive no email at all, and the form still shows the success page. Override `get_users()` if those users must be able to recover access.
- Why does PasswordResetView redirect to the done page even when no account matches the email?Showing a different result for known and unknown addresses would let anyone enumerate which emails have accounts. The view always redirects to `password_reset_done`, and `form.save()` simply sends nothing when `get_users()` yields no one.
- What does uidb64 contain, and is it secret?It is the user's primary key, serialised as a string and encoded with `urlsafe_base64_encode`. It is not secret; it only tells the confirm view which user to load. The security comes from the token, which is an HMAC keyed with `SECRET_KEY` over that user's state and is checked by `check_token()`.
saying these in an interview costs you the question
- PasswordResetView shows an error when the email is not registered
- The uidb64 part of the link is the secret that proves ownership
- The confirm view keeps the token in the URL while the form is shown
- Users are automatically signed in after a reset by default
- Inactive users still receive reset emails from the built-in form