Impossible to make the built-in password reset in an app work

Hello everybody,

I have a huge problem to make the password reset work. When I click on the password reset link in the sent password reset email (example: http://127.0.0.1:8000/account/password-reset/Mg/cj4pbx-5c8a7f53726ddf5a84d72cdf476ea7d2/) the browser immediately redirects the URL in browser into: http://127.0.0.1:8000/account/password-reset/Mg/set-password/ but the form is rendered.

I have created this html template for the password reset in my app: ‘account/templates/registration/password_reset_confirm.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.
<!-- account/templates/registration/password_reset_confirm.html -->

{% extends "base.html" %}

{% block title %} - Reset your password{% endblock %}

<!-- Password Reset Content - Start -->
{% block content %}
  <h1>Resetyour 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 %}
<!-- Password Reset Content - Start -->

This issue is going on since several days and I can not figure out what is the problem.

Structure of the app:
project, with several apps, like account (not accounts) where I want to define, update some of the authentication views and templates. The login, logout, password change works fine.

# 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 = [
    # custom apps
    "account",
    # built-in
    "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",
    # custom apps
    "blog",
    "core",
]
# 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",
    ),
]

Legend:

  • without success_url=‘done’ in as_view() method (example in: auth_views.PasswordResetView.as_view(success_url="done"),) the redirection to the view PasswordResetDoneView is not working, it does not find the view name. Is that a correct way of handling it or is that causing the issue?
# 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

Can someone spot the issue?
Or is the password reset logic only possible outside a namespace or an application, like I should add the path("", include("django.contrib.auth.urls")), inside the project urls.py file and it is impossible to use the views in an app?

I have also tried to overwrite the PasswordResetView like:

# account/views.py

class CustomPasswordResetView(PasswordResetView):

    html_email_template_name = "registration/password_reset_email.html"
    success_url = reverse_lazy("account:password_reset_done")

but I get the same result. Every time I request a new password via the URL: http://127.0.0.1:8000/account/password-reset/ I enter the a valid email address and receive immediately the email with an uid and a token. Can someone tell me why the token seems to be invalid, even it gets checked and seems to be true because the form is displayed but the URL in browser changed.

I am using Firefox, even in private mode the same issue. When making an URL like:

In [1]: from django.contrib.auth import get_user_model
In [2]: from django.contrib.auth.tokens import default_token_generator
In [3]: from django.utils.encoding import force_bytes
In [4]: from django.utils.http import urlsafe_base64_encode

In [5]: User = get_user_model()
In [6]: user = User.objects.filter(email__startswith='test').first()

In [7]: user
Out[7]: <User: TestUser>
In [8]: uid = urlsafe_base64_encode(force_bytes(user.pk))
In [9]: token = default_token_generator.make_token(user)
In [10]: uid
Out[10]: 'Mw'
In [11]: token
Out[11]: 'cj4ok7-d350c7c851a36c306c07a171362d3017'

modeling the URL: http://127.0.0.1:8000/account/password-reset/Mw/cj4ok7-d350c7c851a36c306c07a171362d3017/ same result.

In terminal it looks like this:

[04/Jan/2025 23:15:13] "GET /account/password-reset/Mw/cj4ok7-d350c7c851a36c306c07a171362d3017/ HTTP/1.1" 302 0
[04/Jan/2025 23:15:13] "GET /account/password-reset/Mw/set-password/ HTTP/1.1" 200 10125
[04/Jan/2025 23:21:21] "POST /account/password-reset/Mw/set-password/ HTTP/1.1" 200 10220

The third line is after entering a password and submit the form, it stays on the website.

I have used this link: closed, invalid issue and no luck.

Thanks for reading, if you made it to the end :wink:

This is the correct behaviour isn’t it? What do you expect to happen?

Thank you philigyford to pointing this out!

So, that means the URL with uid and token gets redirected to URL with uid and /set-password/! That part was obviously not clear to me.

I enter twice a new password and ‘change’ the password. Then I get this message:

How did I fix that part?

class CustomPasswordResetConfirmView(PasswordResetConfirmView):
    success_url = reverse_lazy("account:password_reset_complete")

Why is the app name important?
Otherwise the view is not found.

Because your account/urls.py file has this line:

See the docs at URL dispatcher | Django documentation | Django for more details.