Skip to main content

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-time token.
  • It sends an email containing a link to the password_reset_confirm view, formatted as reset/<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:

  1. It validates the token using PasswordResetTokenGenerator.check_token.
  2. If valid, it stores the token in the user's session under the key _password_reset_token.
  3. It redirects the user to a "clean" URL (replacing the token with the string set-password).
  4. 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: PasswordResetView allows overriding email_template_name, subject_template_name, and html_email_template_name.
  • Success URLs: All views support a success_url attribute, often defined using reverse_lazy.
  • Post-Reset Login: PasswordResetConfirmView has a post_reset_login attribute (default False). If set to True, 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),
),