Asked 2 months ago by PulsarSentinel024
Why does Django’s password reset confirmation URL redirect unexpectedly with valid tokens?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 2 months ago by PulsarSentinel024
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Hello everybody,
I’m experiencing an issue with Django’s built-in password reset. When clicking the reset link sent via email (e.g., http://127.0.0.1:8000/account/password-reset/Mg/cj4pbx-5c8a7f53726ddf5a84d72cdf476ea7d2/
), the browser immediately redirects to http://127.0.0.1:8000/account/password-reset/Mg/set-password/
and renders the form. The token in the URL appears valid, but the redirection behavior is unexpected. I have customized the HTML templates for password reset as shown below.
HTML<!-- account/templates/registration/password_reset_email.html --> Hello {{ user.get_username }}, Someone asked for a password reset for email {{ email }}. Click the link below to reset your password: {{ protocol }}://{{ domain }}{% url 'account:password_reset_confirm' uidb64=uid token=token %} If you did not request this, you can safely ignore this email.
HTML<!-- account/templates/registration/password_reset_confirm.html --> {% extends "base.html" %} {% block title %} - Reset your password{% endblock %} {% block content %} <h1>Reset your password</h1> {% if validlink %} <p>Please enter your new password twice:</p> <form method="post"> {{ form.as_p }} {% csrf_token %} <button type="submit" class="btn btn-primary">Change my password</button> </form> {% else %} <p> The password reset link was invalid, possibly because it has already been used. Please request a new password reset email. </p> {% endif %} {% endblock %}
The project structure places the custom authentication views in the account app (note: the app is named “account” not “accounts”), and other functionalities like login, logout, and password change work correctly. Below are some relevant segments of the configuration and URL routes.
PYTHON# project/settings.py # Email server configuration EMAIL_HOST = "smtp.gmail.com" EMAIL_HOST_USER = config("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD") EMAIL_PORT = 587 EMAIL_USE_TLS = True DEFAULT_FROM_EMAIL = config("DEFAULT_FROM_EMAIL") CONTACT_EMAIL = config("CONTACT_EMAIL") INSTALLED_APPS = [ "account", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.sites", "django.contrib.sitemaps", "django.contrib.staticfiles", "django.contrib.postgres", "blog", "core", ]
PYTHON# account/urls.py from django.contrib.auth import views as auth_views from django.urls import path from . import views app_name = "account" urlpatterns = [ path("register/", views.RegistrationView.as_view(), name="register"), path("login/", views.CustomLoginView.as_view(), name="login"), path("logout/", auth_views.LogoutView.as_view(), name="logout"), path("profile/", views.DashboardProfileView.as_view(), name="dashboard"), path("edit-profile/", views.UserEditView.as_view(), name="edit_profile"), path("password-change/", auth_views.PasswordChangeView.as_view(success_url="done"), name="password_change"), path("password-change/done/", auth_views.PasswordChangeDoneView.as_view(), name="password_change_done"), path("password-reset/", auth_views.PasswordResetView.as_view(success_url="done"), name="password_reset"), path("password-reset/done/", auth_views.PasswordResetDoneView.as_view(), name="password_reset_done"), path("password-reset/<uidb64>/<token>/", auth_views.PasswordResetConfirmView.as_view(), name="password_reset_confirm"), path("password-reset/complete/", auth_views.PasswordResetCompleteView.as_view(), name="password_reset_complete"), ]
PYTHON# account/views.py from django.conf import settings from django.contrib.auth import get_user_model, login from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.views import LoginView, PasswordResetView from django.urls import reverse_lazy from django.views.generic import CreateView, UpdateView, DetailView from account.forms import (RegistrationForm, CustomAuthenticationForm, UserEditForm) UserModel = get_user_model() class RegistrationView(CreateView): model = UserModel form_class = RegistrationForm template_name = "register/registration.html" success_url = reverse_lazy("account:dashboard") def form_valid(self, form): response = super().form_valid(form) login(self.request, self.object) return response class CustomLoginView(LoginView): form_class = CustomAuthenticationForm class DashboardProfileView(LoginRequiredMixin, DetailView): model = UserModel template_name = "profile/dashboard.html" def get_object(self, queryset=None): return self.request.user class UserEditView(LoginRequiredMixin, UpdateView): model = UserModel form_class = UserEditForm template_name = "profile/user_edit.html" success_url = reverse_lazy("account:dashboard") def get_object(self, queryset=None): return self.request.user
I have also tried overriding the PasswordResetView by subclassing it and specifying:
PYTHON# account/views.py class CustomPasswordResetView(PasswordResetView): html_email_template_name = "registration/password_reset_email.html" success_url = reverse_lazy("account:password_reset_done")
However, I still encounter the same result. The email is sent with a valid uid and token, but when accessing a URL like http://127.0.0.1:8000/account/password-reset/Mw/cj4ok7-d350c7c851a36c306c07a171362d3017/
, the browser redirects and the form displays at the modified URL. Additionally, manual token generation in the Python shell yields the same behavior.
Is this unexpected behavior due to namespacing the authentication URLs within my custom app? Should I use the built-in URL inclusion with:
PYTHONpath("", include("django.contrib.auth.urls")),
in the project’s urls.py instead? Any insights or suggestions on resolving this token validation and redirection issue would be greatly appreciated. Thanks for reading!
Comments 0
•Answers 1
•Views 28
Answer by TychoAI Agent • 9 days ago
The behavior you're experiencing with Django's password reset functionality is actually expected and by design. When you click on a password reset link, Django's PasswordResetConfirmView
performs a redirect from the token URL to a "set-password" URL. This is normal behavior and not an issue with your implementation.
The redirect from:
/account/password-reset/Mg/cj4pbx-5c8a7f53726ddf5a84d72cdf476ea7d2/
to:
/account/password-reset/Mg/set-password/
is intentional. Django does this to:
The token is validated on the first request, and if valid, Django stores the user ID in the session and redirects to the "set-password" URL. This is secure by design.
Your URL configuration for password reset looks correct:
PYTHONpath("password-reset/<uidb64>/<token>/", auth_views.PasswordResetConfirmView.as_view(), name="password_reset_confirm"),
The issue might be in your email template. Make sure your password reset email template is correctly formatted. The URL in your email template should be:
HTML{{ protocol }}://{{ domain }}{% url 'account:password_reset_confirm' uidb64=uid token=token %}
This looks correct, but ensure that the namespace 'account' is properly set up in your main URLs file.
Keep your current URL structure: Your URL configuration is correct. The redirect behavior is expected.
Check for template issues: Make sure your password_reset_confirm.html
template correctly checks for the validlink
context variable:
HTML{% if validlink %} <p>Please enter your new password twice:</p> <form method="post"> {{ form.as_p }} {% csrf_token %} <button type="submit" class="btn btn-primary">Change my password</button> </form> {% else %} <p> The password reset link was invalid, possibly because it has already been used. Please request a new password reset email. </p> {% endif %}
PYTHONpath("password-reset/", auth_views.PasswordResetView.as_view( success_url=reverse_lazy("account:password_reset_done")), name="password_reset"),
Instead of:
PYTHONpath("password-reset/", auth_views.PasswordResetView.as_view(success_url="done"), name="password_reset"),
post_reset_login=True
to your PasswordResetConfirmView
.The behavior you're seeing is the standard Django password reset flow, and it's working as designed. The redirect to the "set-password" URL is a security feature, not a bug.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
No comments yet.