Password Recovery and Change Flows
The authentication system provides robust, built-in workflows for both recovering forgotten passwords and changing existing ones. These workflows are implemented through a series of class-based views in django.contrib.auth.views and specialized forms in django.contrib.auth.forms.
Password Reset (Recovery) Flow
The password reset process is a four-step workflow designed to securely verify a user's identity via email before allowing them to set a new password.
1. Requesting the Reset
The flow begins with the PasswordResetView. This view uses the PasswordResetForm to collect the user's email address.
When the form is submitted, PasswordResetForm.save() performs the following:
- It searches for active users with the provided email address using
get_users(). - It filters out users with unusable passwords (e.g., those authenticated via external systems like LDAP).
- For each matching user, it generates a unique
uid(base64 encoded user ID) and a one-timetoken. - It sends an email containing a link to the
password_reset_confirmview, formatted asreset/<uidb64>/<token>/.
2. Request Confirmation
Upon successful submission of the reset request, the user is redirected to PasswordResetDoneView. This is a simple TemplateView that informs the user that an email has been sent.
3. Token Validation and Password Entry
When the user clicks the link in their email, they reach the PasswordResetConfirmView. This view is responsible for validating the uidb64 and token parameters from the URL.
To prevent the reset token from leaking in the Referer header to third-party sites (like external CSS or JS providers), PasswordResetConfirmView implements a session-based redirect:
- It validates the token using
PasswordResetTokenGenerator.check_token. - If valid, it stores the token in the user's session under the key
_password_reset_token. - It redirects the user to a "clean" URL (replacing the token with the string
set-password). - The clean URL then displays the
SetPasswordForm, which allows the user to enter a new password without knowing the old one.
4. Completion
Once the user successfully sets a new password via SetPasswordForm, they are redirected to PasswordResetCompleteView, which displays a success message and a link to the login page.
Password Change Flow
The password change flow is intended for authenticated users who know their current password and wish to update it.
PasswordChangeView
The PasswordChangeView uses the PasswordChangeForm. Unlike the reset form, this form requires the user to enter their old_password for verification.
A critical feature of this view is the use of update_session_auth_hash in the form_valid method:
# From django/contrib/auth/views.py
def form_valid(self, form):
form.save()
# Updating the password logs out all other sessions for the user
# except the current one.
update_session_auth_hash(self.request, form.user)
return super().form_valid(form)
Because changing a password changes the user's session hash, the user would normally be logged out immediately. update_session_auth_hash updates the current session with the new hash so the user remains logged in.
Token Generation and Security
The security of the reset flow relies on the PasswordResetTokenGenerator. This class generates a hash-based token that is tied to the user's state.
The _make_hash_value method ensures that a token is invalidated if any of the following change:
- The user's password (ensuring the token is one-time use).
- The user's last login timestamp.
- The user's email address.
- The
PASSWORD_RESET_TIMEOUT(defaulting to 3 days) is exceeded.
# From django/contrib/auth/tokens.py
def _make_hash_value(self, user, timestamp):
login_timestamp = (
""
if user.last_login is None
else user.last_login.replace(microsecond=0, tzinfo=None)
)
email_field = user.get_email_field_name()
email = getattr(user, email_field, "") or ""
return f"{user.pk}{user.password}{login_timestamp}{timestamp}{email}"
Customization and Integration
All password views inherit from PasswordContextMixin, which allows for easy injection of extra context and custom titles.
URL Configuration
The standard implementation is typically included in a project's urls.py by referencing django.contrib.auth.urls:
# django/contrib/auth/urls.py
urlpatterns = [
path("password_change/", views.PasswordChangeView.as_view(), name="password_change"),
path("password_change/done/", views.PasswordChangeDoneView.as_view(), name="password_change_done"),
path("password_reset/", views.PasswordResetView.as_view(), name="password_reset"),
path("password_reset/done/", views.PasswordResetDoneView.as_view(), name="password_reset_done"),
path("reset/<uidb64>/<token>/", views.PasswordResetConfirmView.as_view(), name="password_reset_confirm"),
path("reset/done/", views.PasswordResetCompleteView.as_view(), name="password_reset_complete"),
]
Overriding Behavior
Developers can customize these flows by passing arguments to the as_view() method or by subclassing:
- Email Templates:
PasswordResetViewallows overridingemail_template_name,subject_template_name, andhtml_email_template_name. - Success URLs: All views support a
success_urlattribute, often defined usingreverse_lazy. - Post-Reset Login:
PasswordResetConfirmViewhas apost_reset_loginattribute (defaultFalse). If set toTrue, the user is automatically logged in after successfully resetting their password.
# Example of customization in tests/auth_tests/urls.py
path(
"reset/post_reset_login/<uidb64>/<token>/",
views.PasswordResetConfirmView.as_view(post_reset_login=True),
),