User Password Reset Through Email in Django

Words
1965
Reading
9 min
Listen
Play
6y

image.png

Picture Source

Hello everyone, hope everyone is doing fine. The last Django tutorial that I have posted few days back was all about login and logout functionality with default Django forms validation and our own custom form template. This post is going to be the continuation of the same post wherein I will be showing you guys on how to reset password through email when user forgot his/her password.

Here is the source code of that project. We will be adding some files and code to the same project. This is just an overview of the same project.

Login Page

image.png

Registration Page

image.png

Home Page After Successfully Logging In

image.png

For getting started to the same project, the username is testuser and password is testuser123 for both the admin dashboard and the user.

So for this tutorial, we are gonna use a built in Django authentication module that will help us to reset the password. First when user click on "Forgot Password", it will prompt the user to a new page asking for user to enter their registered email. Secondly, it will display a message that password reset link has been sent to their email. Thirdly, after opening password reset link through email, it will display a page to enter new password and confirm the password. And lastly, there will be a success message that password has been reset. For now, we will be using the default template view provided by Django to do the same.

This is our project directory and file structure till now.

image.png

In tree like structure, our project directory and files structure should look like this.

│   db.sqlite3
│   manage.py
│
├───.idea
│   │   .gitignore
│   │   misc.xml
│   │   modules.xml
│   │   project3.iml
│   │   workspace.xml
│   │
│   └───inspectionProfiles
│           profiles_settings.xml
│
├───demo
│   │   admin.py
│   │   apps.py
│   │   forms.py
│   │   models.py
│   │   tests.py
│   │   urls.py
│   │   views.py
│   │   __init__.py
│   │
│   ├───migrations
│   │   │   __init__.py
│   │   │
│   │   └───__pycache__
│   │           __init__.cpython-38.pyc
│   │
│   ├───templates
│   │   └───demo
│   │           home.html
│   │           login.html
│   │           register.html
│   │
│   └───__pycache__
│           admin.cpython-38.pyc
│           forms.cpython-38.pyc
│           models.cpython-38.pyc
│           urls.cpython-38.pyc
│           views.cpython-38.pyc
│           __init__.cpython-38.pyc
│
└───project3
    │   asgi.py
    │   settings.py
    │   urls.py
    │   wsgi.py
    │   __init__.py
    │
    └───__pycache__
            settings.cpython-38.pyc
            urls.cpython-38.pyc
            wsgi.cpython-38.pyc
            __init__.cpython-38.pyc


Don't worry with the files that you don't know. These files like__pycache__, __init__.py and so on comes automatically when you create your Django project. I will be showing which files to use and write our code into to build our desired application as stated above.

First lets open our projec3/demo/urls.py. We will make a quick import of views from django.contrib.auth. We will be importing the same as auth_views otherwise Django views will conflict with our views.py file that is inside the demo directory and has already been imported in the same urls.py file.

Add this code to the top of urls.py file.

from django.contrib.auth import views as auth_views

Now we are going to add path to the URL for showing the password reset view. Lets add this path to the list of urlpatterns at the very last just before closing square bracket.

path('reset-password/', auth_views.PasswordResetView.as_view(), name="reset_password"),

Now go to your terminal, type python manage.py runserver. If there's no error, it will give you a localhost address which is http://127.0.0.1:8000/. Go to the browser and type in http://127.0.0.1:8000/reset-password/ and you should see the Django default password view.

image.png

If you enter your email and click "Reset my password", it won't work because we haven't specify the path to the URL for the remaining three steps (I already discussed about these steps in the top). Lets add the remaining three URL paths to the same list.

    path('reset-link-sent/', auth_views.PasswordResetDoneView.as_view(), name="password_reset_done"),
    path('reset///', auth_views.PasswordResetConfirmView.as_view(), name="password_reset_confirm"),
    path('reset-complete/', auth_views.PasswordResetCompleteView.as_view(), name="password_reset_confirm"),

Now our project3/demo/urls.py code up to this point is:

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    path('', views.homePage, name="home"),
    path('register/', views.registerPage, name="register"),
    path('login/', views.loginPage, name="login"),
    path('logout/', views.logoutPage, name="logout"),
    path('reset-password/', auth_views.PasswordResetView.as_view(), name="reset_password"),
    path('reset-link-sent/', auth_views.PasswordResetDoneView.as_view(), name="password_reset_done"),
    path('reset///', auth_views.PasswordResetConfirmView.as_view(), name="password_reset_confirm"),
    path('reset-complete/', auth_views.PasswordResetCompleteView.as_view(), name="password_reset_complete"),
]

In the second last list of the URL path, you can see we have pass two keyworded arguments in the angular brackets because this is default Django way of providing security while resetting the password. According to official django documentation website, uidb64 means that user's id is encoded in base 64 and token is used to check if the password is valid.

Also Django requires that we provide the URL name to the path as specified by its documentation because each functionality of resetting password is dependent on each others URL naming pattern. Now, we need to add few configuration to our settings.py to handle the password reset through email.

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = '[email protected]'
EMAIL_HOST_PASSWORD = '**********'

EMAIL_BACKEND specifies Django way of how emails are send for resetting password. We can write our own code for this too if we have separate way of sending emails. Since I am a beginner myself, lets just use Django default way. EMAIL_HOST is used for specifying where your emails are stored on the server. For now I am using Gmail just for the demo purposes. EMAIL_PORT is used to specify the port number that will help to route the email to the proper address. We will be using default port number for this which is 587. EMAIL_USE_TLS=True means to use secure connection while communicating with the SMTP server. EMAIL_HOST_USER, what address you have specify here will send the password reset link to the user's email address that want to change the password. I will show you the demo soon. EMAIL_HOST_PASSWORD is used by Django to authenticate to the SMTP server. I can't display my password here for security purposes but you can use your own email address as host and write the password. Now, before running the demo, lets add the Reset Password link to the "Forget Password" in Login form. For this open login.html and add this one line of code just after our Register button.

<h6 class="text-white">Forgot Password<a class="btn btn-info" href="{% url 'reset_password' %}">Click Here</a></h6>

Run the development server and type the localhost address in your browser. The login page should now see like below. We just added the link to reset the password.

image.png

Series of Demo of the Project

i. First register a new user with valid email address

Screenshot_1.png

ii. After registering, it will redirect to the login page.

Screenshot_2.png

iii. Now lets click the link to "Reset Password". The following screen appears. Now put in the same email address used during the registration.

Screenshot_3.png

You may encounter slight error here where Django can't authenticate your username and password eventhough you have provided the correct credentials in settings.py. For this open the email you have used as host and allow access to less secure apps.

Screenshot_4.png

iv. Check your inbox now. If its not there, make sure to check spam folder. Mine has arrived in the spam folder and I later moved it to inbox.

Screenshot_5.png

v. You can see the password link has arrived in that users email through the email address specifed in EMAIL_HOST. As I have specifed my gmail address, you can see the same here too.
Screenshot_6.png

vi. Click on the reset link. A page appears asking user to fill new password and confirm it. Enter the same.

Screenshot_7.png

vii. Finally a success message that your password has been reset successfully.
Screenshot_8.png

viii. Go to login page and login with the newly changed password and this is the final screen.
Screenshot_9.png

This is how we can reset password through email in Django. Why not proceed further since we have some few bad designs and security issues here. Remember the first page that opens when you click "Reset Password". I have again uploaded below highlighting the area of our issues.

Screenshot_3.png

See when the users in front-end want to reset the password, the admin site title in being shown i.e. Django Administration and when you click Home, it actually leads to the admin dashboard login page. Such information's are never to be revealed to the customers or users of the website. In every applications, link to the dashboard always remain hidden from the customers of the website for the security purposes. If by chance the users gets access to it, there's a risk of our application getting hacked.

To avoid this issue, Django gives us the flexibility of replacing this default template with our own custom template giving us full control of the application we are building. And this process is pretty easy and less time consuming. Lets build our own custom template for this password reset view. We are building four templates inside projec3/demo/templates/demo where we already have template for Login and Registration. Each template name and its corresponding functionality can be studied from the table below.

Template NameDescription
reset-password.htmlTo show the password reset view
reset-sent.htmlTo display that reset email has been sent
reset-sent.formTo show the form to enter new password and confirm it
reset-done.htmlTo show reset has been successful and go back to login page

If you have created the four templates, then your project directory and file structure looks like this:

image.png

I will be creating a simple custom design to replace those default email reset design because it is too time consuming to do so. Rather, I would be using a bootstrap jumbotron to give a little bit of nice design. So inside our first reset-password.html, place this code.

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css">
    <title>Reset Password</title>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-12">
            <div class="jumbotron">
                <h2 class="bg-success text-white">
                    Reset Your Password
                </h2>
                <p>
                    Forgotten your password? Enter your email address below, and we’ll email instructions for setting a new one.
                </p>
                <form method="post">
                    {% csrf_token %}
                    {{form}}
                    <input type="submit" name="Send Email">
                </form>
            </div>
        </div>
    </div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>



Inside our reset-sent.html, place this code:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css">
    <title>Reset Password</title>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-12">
            <div class="jumbotron">
                <h2 class="bg-success">
                    Password reset sent
                </h2>
                <p>
                    We’ve emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly.
                </p>
                <p>
                    If you don’t receive an email, please make sure you’ve entered the address you registered with, and check your spam folder.
                </p>
            </div>
        </div>
    </div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>

Inside reset-form.html, place this code:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css">
    <title>Reset Password</title>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-12">
            <div class="jumbotron">
                <h2 class="bg-success text-white">
                    Enter New Password
                </h2>
                <p>
                    Please enter your new password twice so we can verify you typed it in correctly.
                </p>
                <form method="post">
                    {% csrf_token %}
                    {{form}}
                    <input type="submit" name="Update Password">
                </form>
            </div>
        </div>
    </div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>

Inside our reset-done.html, place this code:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css">
    <title>Reset Password</title>
<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-12">
            <div class="jumbotron">
                <h2 class="bg-success text-white">
                    Password reset complete
                </h2>
                <p>
                    Your password has been set. You may go ahead and log in now.
                </p>
                <a href="/">Log in</a>
            </div>
        </div>
    </div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>

I have placed the same message in my template as Django has provided in its default template like: We’ve emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly to show user friendly message.

Now we have created our four custom templates for four-steps password reset, we need to override the Django default template with our own template. This process is so easy. In our urls.py, inside each of our URL path's we need to pass this parameter template_name=<app_name>/<html_file_name> inside our as_view() method. So for displaying the reset password view, we have template called reset-password.html. And we will be passing this template as:

path('reset-password/', auth_views.PasswordResetView.as_view(template_name="demo/reset-password.html"), name="reset_password")

This will override or replace Django default passwordResetView with our own template for same view. Lets do the same for remaining one. We have different different template that serves different purposes for each of the view. So our final urls.py has this code inside it.

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

urlpatterns = [
    path('', views.homePage, name="home"),
    path('register/', views.registerPage, name="register"),
    path('login/', views.loginPage, name="login"),
    path('logout/', views.logoutPage, name="logout"),
    path('reset-password/', auth_views.PasswordResetView.as_view(template_name="demo/reset-password.html"), name="reset_password"),
    path('reset-link-sent/', auth_views.PasswordResetDoneView.as_view(template_name="demo/reset-sent.html"), name="password_reset_done"),
    path('reset///', auth_views.PasswordResetConfirmView.as_view(template_name="demo/reset-form.html"), name="password_reset_confirm"),
    path('reset-complete/', auth_views.PasswordResetCompleteView.as_view(template_name="demo/reset-done.html"), name="password_reset_complete"),
]

Our task is finally done. If you are into backend and if you have this template ready by your designer then its just a matter of time for you to build this functionality. You just have to work with your URLs file configuration and your settings file configuration for handling emails. The demo would be same as I have stated above under "Series of Demo of the Project". Here I'll be only running the demo of the view that we have replaced with our own custom template.

i. Password reset view after user go to the login page and hit "Reset Password".

image.png

ii. Password reset sent view after user enters his/her email and clicks "Submit".

image.png

iii. Reset form for the user to enter new password and confirm it along with custom Django forms validation. This link is opened through email because reset link always arrives in your email.

image.png

iv. Reset successful message view and link to the login page on the same webpage.

image.png

Clicking on Login page will open the login page we have already shown above.

Download the Project source code from here.

Getting Started

FieldsCredentials
Usernametestuser
Passwordtestuser123

This is just for the admin dashboard page, which is of no use if you are learning this tutorial. When you first open the localhost address, it will open you a login page. Just register a new user for you and you can check the password reset through email. Please don't forget to put your email address and password under EMAIL_HOST_USER and EMAIL_HOST_PASSWORD. I have removed mine from the source code link above as it is confidential information but I have shown you a demo above and what to enter, what each field in email settings does. Let me know if you have any queries or if you encounter any error while doing the same project. Thank you.