In this chapter we will complete the authorization flow of our social messaging app by adding password change and reset functionality. Users will be able to change their current password or, if they’ve forgotten it, to reset it via email.

Django comes with built-in views and urls for both password change and reset. We’ll go through the default pages first and then learn how to customize them with our own templates and email service.

Complete source code can be found on Github.

Password Change

Letting users change their passwords is a common feature on many websites. Django provides a default implementation that already works at this stage. To try it out first click on the “log in” button to make sure you’re logged in. Then navigate to the “Password change” page at http://127.0.0.1:8000/users/password_change/.

Password change

Enter in both your old password and then a new one. Then click the “Change My Password” button.

You’ll be redirected to the “Password change successful” page located at http://127.0.0.1:8000/users/password_change/done/.

Password change done

Customizing password change

Let’s customize these two password change pages so that they match the look and feel of our social messaging site. Because Django already has created the views and URLs for us, we only need to add new templates.

On the command line create two new template files in the registration folder.

(msg) $ touch templates/registration/password_change_form.html
(msg) $ touch templates/registration/password_change_done.html

Update password_change_form.html with the following code.

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

{% block title %}Password Change{% endblock %}

{% block content %}
  <h1>Password change</h1>
  <p>Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly.</p>

  <form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <input class="btn btn-success" type="submit" value="Change my password">
  </form>
</div>
{% endblock %}

At the top we extend base.html and set our page title. Because we used “block” titles in our base.html file we can override them here. The form uses POST since we’re sending data and a csrf_token for security reasons. By using form.as_p we’re simply displaying in paragraphs the content of the default password reset form. And finally we include a submit button that uses Bootstrap’s btn btn-success styling to make it green.

Go ahead and refresh the page at http://127.0.0.1:8000/users/password_change/ to see our changes.

New password change form

Next up is the password_change_done template.

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

{% block title %}Password Change Successful{% endblock %}

{% block content %}
    <h1>Password change successful</h1>
    <p>Your password was changed.</p>
{% endblock content %}

It also extends base.html and includes a new title. However there’s no form on the page, just new text.

The new page is at http://127.0.0.1:8000/users/password_change/done/.

New password change done

That wasn’t too bad, right? Certainly it was a lot less work than creating everything from scratch, especially all the code around securely updating a user’s password.

Next up is our password reset functionality.

Password reset

Password reset handles the common case of users forgetting their passwords. The steps are very similar to configuring password change, as we just did. Django already provides a default implementation that we will use and then customize the templates so it matches the rest of our site.

The only configuration required is telling Django how to send emails. After all, a user can only reset a password if they have access to the email linked to the account. In production we’ll use the email service SendGrid to actually send the emails but for testing purposes we can rely on Django’s console backend setting which outputs the email text to our command line console instead.

At the bottom of the settings.py file make the following one-line change.

# msg_project/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

And we’re all set! Django will take care of all the rest for us. Let’s try it out.

Navigate to http://127.0.0.1:8000/users/password_reset/ to view the default password reset page.

Default password reset page

Make sure the email address you enter matches one of your user accounts. Upon submission you’ll then be redirected to the password reset done page at http://127.0.0.1:8000/users/password_reset/done/.

Default password reset done page

Which says to check our email. Since we’ve told Django to send emails to the command line console, the email text will now be there. This is what I see in my console.

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: Wed, 31 Jan 2018 13:56:54 -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/4tb-6575ef6d2761916bf732/

Your username, in case you've forgotten: testuser

Thanks for using our site!

The 127.0.0.1:8000 team



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: Wed, 31 Jan 2018 13:56:54 -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/4tb-6575ef6d2761916bf732/

Your username, in case you've forgotten: testuser

Thanks for using our site!

The 127.0.0.1:8000 team

Your email text should be identical except for three lines:

  • the “To” on the sixth line contains the email address of the user
  • the URL link contains a secure token that Django randomly generates for us and can be used only once
  • Django helpfully reminds us of our username

We will customize all of the email default text shortly but for now focus on finding the link provided. Mine is http://127.0.0.1:8000/users/reset/Mg/4tb-6575ef6d2761916bf732/ in the message above.

Enter this link into your web browser and you’ll be redirected to the “change password page”.

Default change password page

Now enter in a new password and click on the “Change my password” button. The final step is you’ll be redirected to the “Password reset complete” page.

Default password reset complete

To confirm everything worked, click on the “Log in” link and use your new password. It should work.

Custom Templates

As with “Password change” we only need to create new templates to customize the look and feel of password reset.

Create four new template files.

(msg) $ touch templates/registration/password_reset_form.html
(msg )$ touch templates/registration/password_reset_done.html
(msg) $ touch templates/registration/password_reset_confirm.html
(msg) $ touch templates/registration/password_reset_complete.html

Start with the password reset form which is password_reset_form.html.

<!-- 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 class="btn btn-success" type="submit" value="Send me instructions!">
  </form>
</div>
{% endblock %}

At the top we extend base.html and set our page title. Because we used “block” titles in our base.html file we can override them here. The form uses POST since we’re sending data and a csrf_token for security reasons. By using form.as_p we’re simply displaying in paragraphs the content of the default password reset form. Finally we include a submit button and use Bootstrap’s btn btn-success styling to make it green.

If you navigate to http://127.0.0.1:8000/users/password_reset/ and refresh the page you can see our new page.

New password reset

Now we can update the other three pages. Each takes the same form of extending base.html, a new title, new content text, and for “password reset confirm” an updated form as well.

<!-- 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 %}

Confirm the changes by going to http://127.0.0.1:8000/users/password_reset/done/.

New reset done

Next the password reset confirm page.

<!-- 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 class="btn btn-success" type="submit" value="Change my password">
</form>
{% endblock %}

Grab the one-time URL link from the email outputted to the console and you’ll see the following.

New set password

Finally here is the password reset complete code.

<!-- 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 %}

You can view it at http://127.0.0.1:8000/users/reset/done/.

New password reset complete

Users can now reset their account password!

Configure SMTP Email Service

At this point you may be feeling a little overwhelmed by all the user authentication configuration we’ve done up to this point. That’s normal. After all, we haven’t even created any core social messaging app features yet! Everything has been about setting up custom user accounts and the rest.

The upside to Django’s approach is that it is incredibly easy to customize any piece of our website. The downside is Django requires a bit more out-of-the-box code than some competing web frameworks. As you become more and more experienced in web development, the wisdom of Django’s choice will ring true.

Now we want to have our emails be actually sent to users, not just outputted to our console. We will need to signup for an account on SendGrid and update our settings.py files. Django will take care of the rest. Ready?

SendGrid is a popular service for sending transactional emails so we’ll use it. Django doesn’t care what service you choose though; you can just as easily use MailGun or any other service of your choice.

On the SendGrid homepage click on the large blue button for “See Plans and Pricing”.

SendGrid homepage

On the next page scroll down slightly and look on the left side for the “Try for Free” button. SendGrid provides a free tier we can use although they make it somewhat difficult to find.

SendGrid pricing

Sign up for your free account on the next page.

SendGrid new account

After confirming your new account via email–that’s kinda meta, no?–you’ll be asked to login and taken to your SendGrid dashboard page.

SendGrid loggedin

Now we can configure our Django code in the settings.py file. First we update the email backend to use SMTP.

# msg_project/settings.py
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

Then right below it add the following five lines of email configuration. Note that ideally you should store secure information like your password in environment variables, but we won’t here to keep things simple.

# msg_project/settings.py
EMAIL_HOST = 'smtp.sendgrid.net'
EMAIL_HOST_USER = 'sendgrid_username'
EMAIL_HOST_PASSWORD = 'sendgrid_password'
EMAIL_PORT = 587
EMAIL_USE_TLS = True

Make sure to use enter your own SendGrid username for EMAIL_HOST_USER and password for EMAIL_HOST_PASSWORD.

That’s it. We’re done! Navigate to the password reset form again at http://127.0.0.1:8000/users/password_reset/ and you should receive an email in your inbox!

Here’s what mine looks like. Notice that the text is exactly the same as that outputted to our console previously.

Default email

Custom emails

That text isn’t very personal, is it? Let’s change things. At this point I could just show you what steps to take, but I think it’s helpful if I can explain how I figured out how to do this. After all, you want to be able to customize all parts of Django if needed.

In this case, I knew what text Django was using by default but it wasn’t clear where in the Django source code it was written. And since all of Django’s source code is available on Github we can can just search it.

Github Django

Use the Github search bar and enter a few words from the email text. If you type in “You’re receiving this email because” you’ll end up at this Github search page.

Github search

The first result is the one we want. It shows the code is located at django/contrib/admin/templates/registration/password_reset_email.html. That means in the contrib app the file we want is called password_reset_email.html.

Here is that default text.

{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.{% endblocktrans %}

{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}

{% trans "Thanks for using our site!" %}

{% blocktrans %}The {{ site_name }} team{% endblocktrans %}

{% endautoescape %}

Let’s change it. We need to create a new password_reset_email.html file in our registration folder.

(msg) $ touch templates/registration/password_reset_email.html

Then use the following code which tweaks what Django provided by default.

<!-- templates/registration/password_reset_email.html -->
{% load i18n %}{% autoescape off %}
{% trans "Hi" %} {{ user.get_username }},

{% trans "We've received a request to reset your password. If you didn't make this request, you can safely ignore this email. Otherwise, click the button below to reset your password." %}

{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% endautoescape %}

This code might look a little scary so let’s break it down line-by-line. Up top we load the template tag i18n which means this text is eligible to be translated into multiple languages. Django has robust internationalization support though covering it is beyond the scope of this book.

We’re greeting the user by name thanks to user.get_username. Then we use the built-in reset_link block to include the custom URL link. Honestly this is just copy/pasted from the Django source code. I’d love to explain it further but as this is a beginner book we’ll move on for now. Django explains its password management approach in the official docs.

Let’s also update the email’s subject title. To do this we’ll create a new file templates/registration/password_reset_subject.txt.

(msg) $ touch templates/registration/password_reset_subject.txt

Then add the following line of code to the file.

Please reset your password

And we’re all set. Go ahead and try out our new flow again by entering a new password at http://127.0.0.1:8000/users/password_reset/. Then check your email and it will look like the below.

Password reset updated

Conclusion

We’ve now finished implementing a complete user authentication flow. Users can sign up for a new account, login, logout, change their password, and reset their password.

Continue on to Chapter 12: Social Messaging app where we’ll build the functionality for our social messaging app itself.