We already have a working user authentication flow thanks to Part 1: Login & Logout and Part 2: Signup, but to be complete we need a way for the user to reset their password.

Complete source code can be found on Github if you get stuck along the way.

Django auth app

Fortunately Django has us covered. What we want is a password_reset page where the user can enter their email address, and be sent a cryptographically secure email with a one-time link to a reset page.

If you recall the complete set of views and URLs provided by the Django auth app, there are already several for resetting a password.

^login/$ [name='login']
^logout/$ [name='logout']
^password_change/$ [name='password_change']
^password_change/done/$ [name='password_change_done']
^password_reset/$ [name='password_reset']
^password_reset/done/$ [name='password_reset_done']
^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$ [name='password_reset_confirm']
^reset/done/$ [name='password_reset_complete']

The default templates however are quite ugly and we need to customize them. For example, here are the default password reset and password reset done pages:

Django password reset

Django password reset done

But first we need to setup a way to deliver, or at least fake deliver, our email messages.

SMTP Server

In the real-world you would integrate with an email service like MailGun or SendGrid. For development purposes Django lets us store emails either in the console or as a file. We’ll choose the latter and store all sent emails in a folder called sent_emails in our project directory.

To configure this, update our settings.py file by adding the following two lines at the bottom under our redirect URLs.

# my_project/settings.py
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = os.path.join(BASE_DIR, "sent_emails")

Now let’s change the appearance of the password reset pages.

Password Reset Form

The default template for password reset is located at templates/registration/password_reset_form.html. We can customize it by creating our own password_reset_form.html file:

(users) $ touch registration/password_reset_form.html

Then add the following code:

<!-- templates/registration/password_reset_form.html -->
{% extends 'base.html' %}

{% block title %}Forgot Your Password?{% endblock %}

{% block content %}
  <h1>Forgot your password?</h1>
  <p>Enter your email address below, and we'll email instructions for setting a new one.</p>

  <form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Send me instructions!">
  </form>
</div>
{% endblock %}

If you refresh the page at http://127.0.0.1:8000/users/password_reset/ you can see our new update:

Django password reset page new

Note: Make sure the email address you enter matches one for an actual user you’ve created!

Upon successful submission, we’re redirected to the Password reset done page which is also ugly. Let’s change it. The default template is located at templates/registration/password_reset_done.html. So as before, create a new template file at this location and then add the following code:

(users) $ touch templates/registration/password_reset_done.html
<!-- templates/registration/password_reset_done.html -->
{% extends 'base.html' %}

{% block title %}Email Sent{% endblock %}

{% block content %}
  <h1>Check your inbox.</h1>
  <p>We've emailed you instructions for setting your password. You should receive the email shortly!</p>
{% endblock %}

If you refresh the password reset done page at http://127.0.0.1:8000/users/password_reset/done/ we can see our new page.

Password reset done page new

Password Reset Confirm

Remember how we configured our Django project to store emails in a local folder called sent_emails? If you look at your project now that folder exists! The format for the txt file will look something like this:

Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Subject: Password reset on 127.0.0.1:8000
From: [email protected]
To: [email protected]
Date: Mon, 20 Nov 2017 17:58:18 -0000
Message-ID: <[email protected]>


You're receiving this email because you requested a password reset for your user account at 127.0.0.1:8000.

Please go to the following page and choose a new password:

http://127.0.0.1:8000/users/reset/Mg/4rb-aa8ae83d723adfc55232/

Your username, in case you've forgotten: heidi

Thanks for using our site!

The 127.0.0.1:8000 team

This contains Django’s default language which we can customize. But the important section for now is the URL included. In the email above, mine is http://127.0.0.1:8000/users/reset/Mg/4rb-aa8ae83d723adfc55232/. Copy and paste this into your browser and you’ll be automatically routed to the Password reset confirmation page.

Django password confirm page

Ugly, no? Let’s create a new template with our familiar steps. The template is located by default in templates/registration/password_reset_confirm.html, so let’s create it.

(users) $ touch templates/registration/password_reset_confirm.html

And enter new code:

<!-- templates/registration/password_reset_confirm.html -->
{% extends 'base.html' %}

{% block title %}Enter new password{% endblock %}

{% block content %}
<h1>Set a new password!</h1>
<form method="POST">
  {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="Change my password">
</form>
{% endblock %}

Refresh the page at http://127.0.0.1:8000/users/reset/Mg/set-password/ and you’ll see our new template.

Password confirm page

Password Reset Done

Go ahead and create a new password in our form. Upon submission you’ll be redirected to our final default page which is for Password reset complete:

Django password reset done page

To customize this page we’ll create a new password_reset_complete.html template and enter our new code:

(users) $ touch templates/registration/password_reset_complete.html
<!-- templates/registration/password_reset_complete.html -->
{% extends 'base.html' %}

{% block title %}Password reset complete{% endblock %}

{% block content %}
<h1>Password reset complete</h1>
<p>Your new password has been set. You can log in now on the <a href="{% url 'login' %}">log in page</a>.</p>
{% endblock %}

Now reset the page at http://127.0.0.1:8000/users/reset/done/ and view our work.

Password reset done page

Conclusion

We’ve now implemented a robust user authentication flow for our web app with login, logout, signup, and password reset. Congrats!

Django takes a little bit more work to set up a basic configuration than other frameworks, but it makes it very easy to customize everything once you’ve done so.

If we were doing this on a production website we’d need to integrate a 3rd party email service to send the emails for us. We might also want to add Two-Factor Authentication or even Social Authentication via Gmail, Facebook, Github, and other sites. This functionality isn’t built-in to Django but there are popular 3rd party packages that provide it.

Want to learn how to add third-party social authentication via Github, Facebook, Twitter, and more? Check out my tutorial on using django-allauth..




If you’d like to learn more about Django and build step-by-step multiple web applications, check out the free online book I wrote Django For Beginners.