Django Tutorial Part 10: User Authentication

Disclaimer: Your support helps keep JovialGuide running! Our content is reader-supported. This means if you click on some of our links, we may earn a commission.

There are times that you will want to implement an authentication system; where users will have to create an account and login to be able to access some of the pages on your site. This is called user management & authentication. The good news about user authentication is that there is a builtin authentication system that comes with Django. In this complete beginners guide to Django part 10, we will show you how to use the builtin Django authentication app. This is a complete beginners guide to Django, so if you haven’t read part 9, then see Django tutorial series part 9: using flash messages or see part 1 – introduction to Django to get started.

Installing the Django Authentication App

Just like the messages framework, the startproject command automatically installs the authentication app in the INSTALLED_APPS list of the settings.py file, which makes it ready to be used right after starting a Django project and an app.

Open django_project/settings.py and search for INSTALLED_APPS. Check if you have the authentication app installed like below:

INSTALLED_APPS = [
  ...
  'django.contrib.auth',
  ...
]

If you have the auth app installed, then you can start using the builtin Django authentication app right away. If not, then install it like the example above.

How to use the Builtin Django Authentication App

After you have installed the authentication app, to start using it, we will have to add it to our root urls.py file, which is located in django_project/settings.py.

Add the following URL path to the urlpatterns:

  path('accounts/', include('django.contrib.auth.urls')),

Note:

  • include is imported from django.urls.
  • Remember to indent your code!

If you do not have include imported, then copy and paste the following where other imports are:

from django.urls import include

If you have added the URL path above, then you are ready to start using the Django authentication app.

The URL path we just included to the Django authentication app (above) exposes us to all of the URLs you will need to create a complete user authentication system in Django. These URLs are:

  • accounts/login/ [name=’login’] – this is the login URL path, and has a URL name of login
  • accounts/logout/ [name=’logout’] – the URL path to logout. It has a URL name of logout
  • accounts/password_change/ [name=’password_change’] – it is used for changing password. The URL name is password_change
  • accounts/password_change/done/ [name=’password_change_done’] – it is used to show that password change was successful. The URL name is password_change_done
  • accounts/password_reset/ [name=’password_reset’] – it is used for requesting for password reset. The URL name is password_reset
  • accounts/password_reset/done/ [name=’password_reset_done’] – it is used to show a confirmation message that a password reset email was sent. The URL name is password_reset_done
  • accounts/reset/// [name=’password_reset_confirm’] – it is used to set new password through a reset link. The URL name is password_reset_confirm
  • accounts/reset/done/ [name=’password_reset_complete’] – it is used to show a confirmation message that a password reset was successful. The URL name is password_reset_complete

The Django authentication app covers everything you will need for user authentication. This means, you only need to include a path to the Django authentication app and Django will take care of the authentication system for you!

Visit any of the Django authentication URLs. Example: 127.0.0.1:8000/accounts/login

Django Blog - Authentication TemplateDoesNotExist

You are greeted with a TemplateDoesNotExist exception. This is because we have not created the registration/login.html template.

By default, when you use the Django authentication app, it looks for templates in app_name/templates/registration/ folder. This means that the registration/ folder is where Django authentication template are going to be stored.

Creating the Authentication Template

In this section, we will create the HTML templates for the builtin Django authentication app, and we will use Crispy Forms to style our HTML forms. So, let’s start with the login template.

Creating the Login Template

Create blog/templates/registration/ folder. This folder will house all of the authentication templates. Inside the registration folder, create a login.html template. Your folder structure should look like this:

django_project/
--blog/
----templates/
------blog/
------registration/
--------login.html

Paste the following lines of code in the login.html template:

{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title %}Login{% endblock %}

{% block content %}

<div class="row d-flex justify-content-center">
  <div class="col-8 mb-3">
    <div class="card p-4">
      <h2 class="mb-5 text-center">Login</h2>

      <form method="POST">
        {% csrf_token %}
        {{ form|crispy }}
        <input type="submit" value="Login" class="btn btn-primary w-100">
      </form>

      <div class="mt-3">
        <a href="{% url 'homepage' %}" class="btn btn-outline-secondary w-100">Back to homepage</a>
      </div>
    </div>
  </div>
</div>
{% endblock %}

  • This template displays a form for users to login.
  • To style our form, we loaded crispy_forms_tags then used it as a template filter – crispy.
  • After that, we call the {% csrf_token %} to prevent us from Cross Site Request Forgeries.
  • Notice we didn’t create forms variable or pass it as context. This is because the Django authentication app does that for us behind the scene.

To learn more about creating forms, see part 8 – creating modelforms.

Be sure your server is running. If not, then run the following command:

python manage.py runserver

Once your development server is running, navigate to the login page via 127.0.0.1:8000/accounts/login. This is what it looks like:

Django Blog - Login Page

You can login with the credentials you created using the createsuperuser command in part 5 – Django admin site.

Once your login credentials are correct, and you see the error screen like the one below, don’t worry, everything’s fine:

Django Blog - Authentication Profile Error Page

If you follow the error message, you will see that Django authentication can’t find accounts/profile/ path, which is the default location for users when their login is successful.

To get rid of the profile login error of Django authentication, we can also create a new view and map it to accounts/profile. or redirect to the homepage after a successful login. For this JovialGuide, let’s redirect to the homepage after a successful login.

To redirect to the homepage after a successful login, add the following line at the bottom of the settings.py file:

LOGIN_REDIRECT_URL = 'homepage'
  • homepage is the URL name we created in part 3.

After you have done that, try to login again and you will be redirected to the homepage without errors.

Creating the Logout Template

We can login but we cannot logout. The idea is to allow users login as well as logout whenever they want to. When the logout link is clicked, it redirects the user to the accounts/logout path using the registration/logged_out.html template.

Create a logged_out.html template in the registration folder. Paste the following lines of code in it:

{% extends 'blog/base.html' %}
{% block title %}You are logged out{% endblock %}

{% block content %}

<div class="row d-flex justify-content-center">
  <div class="col-8 mb-3">
    <div class="card p-4">

      <h2 class="mb-5 text-center">You are logged out</h2>
      <div class="mt-3">
        <p><a href="{% url 'login' %}">Click here to login again</a></p>
      </div>

    </div>
  </div>
</div>
{% endblock %}
  • This template only displays a message informing you that you are logged-out and provides you with a link to login.

Creating the Password Reset Template

Django authentication system for password reset uses email to send password reset link. This basically means that you will be working with forms mostly, because they will be used to collect user email.

You need to create the following templates for password reset template:

  • password_reset_form.html template
  • password_reset_done.html template
  • password_reset_email.html template
  • password_reset_confirm.html template
  • password_reset_complete.html template

Password Reset Form Template

The password_reset_form.html template will be used for collecting the user email for sending password reset link.

In the registration folder, create a template named password_reset_form.html. Paste the following code in it:

{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title %}Reset Password{% endblock %}

{% block content %}

<h2 class="text-center">Reset Password</h2>

<div class="row d-flex justify-content-center">
  <div class="col-8 mb-3">
    <div class="card">

      <form method="POST">
        {% csrf_token %}

        {% if form.email.errors %}
          {{ form.email.errors }}
        {% endif %}

        {{ form.email|crispy }}

        <input type="submit" value="Reset Password" class="btn btn-sm btn-primary w-100">
      </form>

      <div class="mt-3">
        <a href="{% url 'homepage' %}" class="btn btn-outline-secondary w-100">Back to homepage</a>
      </div>

    </div>
  </div>
</div>

{% endblock %}

Password Reset Done Template

This template is shown after your email address has been collected for password resetting.

Create blog/templates/registration/password_reset_done.html template and paste the following lines of code in it:

{% extends 'blog/base.html' %}
{% block title %}Password Reset Done{% endblock %}

{% block content %}

<div class="row d-flex justify-content-center">
  <div class="col-8 mb-3">
    <div class="card">
      <p>We've emailed you instructions for resetting your password. 
        If they haven't arrived in a few minutes, please check your spam folder.</p>
    </div>
  </div>
</div>

{% endblock %}

Password Reset Email Template

This template provides the text content of the email that contains the password reset link that will be sent to users.

Create blog/templates/registration/password_reset_email.html template and paste the following lines of code in it:

Someone asked for password reset for email {{ email }}. Follow the link below:
{{ protocol}}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}

Password Reset Confirm Template

This template displays after clicking the password reset link (the previous section). It contains a form to enter new and confirm password.

Create blog/templates/registration/password_reset_confirm.html template and paste the following lines of code in it:

{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title %}Password Reset Confirmation{% endblock %}

{% block content %}

<h2 class="text-center">Password Reset Confirmation</h2>

<div class="row d-flex justify-content-center">
  <div class="col-8 mb-3">
    <div class="card">
      {% if validlink %}
        <p>Please enter and confirm your new password.</p>

        <form method="post">
          {% csrf_token %}

          <div class="row">
            <div class="col-md-6">
              {{ form.new_password1.errors }}
              <label for="id_new_password1">New password:</label>
              {{ form.new_password1|as_crispy_field }}
            </div>
            <div class="col-md-6">
              {{ form.new_password2.errors }}
              <label for="id_new_password2">Confirm password:</label>
              {{ form.new_password2|as_crispy_field }}
            </div>
            <div class="col-12">
              <input type="submit" value="Change password" class="btn btn-sm btn-primary w-100">
            </div>
          </div>

        </form>

      {% else %}
        <h2>Password reset failed</h2>
        <p>The password reset link was invalid, possibly because it has already been used. Please request a new password reset!</p>

      {% endif %}

    </div>
  </div>
</div>

{% endblock %}
  • We are using the as_crispy_field template filter because we are accessing the fields individually.

Password Reset Complete Template

This template shows you that the password reset was successful. This is the last Django authentication template that you will create.

Create blog/templates/registration/password_reset_complete.html template and paste the following lines of code in it:

{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title %}Password Reset was Successful{% endblock %}

{% block content %}

<h2 class="text-center">Password Reset was Successful</h2>

<div class="row d-flex justify-content-center">
  <div class="col-8 mb-3">
    <div class="card">

      <p><a href="{% url 'login' %}">click here to login</a></p>

    </div>
  </div>
</div>

{% endblock %}

Creating the Signup Views

The signup views is responsible for creating new users.

Add the following imports at the top of the blog views.py file:

from django.contrib.auth.forms import UserCreationForm

Copy and paste the following view function for signup:

# signup view
def signup(request):
  if request.method == 'POST':
    signup_form = UserCreationForm(request.POST)

    if signup_form.is_valid():
      signup_form.save()
      messages.success(request, 'User has been successfully created, please login!')
      return redirect('login')
    else:
      messages.error(request, 'There is an error, %s' %signup_form.errors)
      
  else:
    signup_form = UserCreationForm()
  
  context = {
    'signup_form': signup_form
  }
  
  return render(request, 'registration/signup.html', context)
  • Here, we are inheriting from the UserCreationForm form. This form comes with all of the fields you would want to create a user. So, this is the reason we didn’t create a form for the signup views.
  • If the user was created successfully, we set a success message and redirect to the login page, if there’s and error, then we display it for the user to correct.

Signup Template

Create blog/templates/registration/signup.html template and paste the following lines of code in it:

{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title %}Signup{% endblock %}

{% block content %}

<div class="row d-flex justify-content-center">
  <div class="col-8 mb-3">
    <div class="card p-4">
      <h2 class="mb-5 text-center">Signup</h2>

      <form method="POST">
        {% csrf_token %}
        {{ signup_form|crispy }}
        <input type="submit" value="Signup" class="btn btn-primary w-100">
      </form>

      <div class="mt-3">
        <a href="{% url 'homepage' %}" class="btn btn-outline-secondary w-100">Back to homepage</a>
      </div>
    </div>
  </div>
</div>
{% endblock %}

Let’s map the signup views to a URL!

Import views to django_project/urls.py:

from blog import views

Add the following path under the accounts paths:

  path('signup/', views.signup, name='signup'),
  • Make sure to fix indentation

Guarding Views from Non-logged-in Users

Our Django blog app is created in a way that non-authenticated or non-logged-in users can create articles and categories. This means that users who are not logged-in can:

  • create articles, but will encounter an error (because of request.user.username) because they are not logged-in, so the article will not be saved!
  • create categories (without errors)

We want to make create, edit and delete operations to be available to logged-in users only. This means that you will have to have an account with our blog, and be currently logged-in to be able to perform such operations.

Django allows us to guard or make views/pages accessible to only logged-in users in two ways:

1: Using Python’s if statement to check the request object

def django_views(request):
  if request.user.is_authenticated:
    # what to do if is authenticated/logged-in
    pass
  else:
    # what to do if is not authenticated/logged-in
    pass
  • The if statement method requires you to check the is_authenticated property of the request object. This property returns a boolean datatype, which is a Truthy or Falsy (True or False) value.
  • If it returns True, then we write the logic inside the if block to handle operations if users are logged-in or authenticated
  • What if it returns False? This means that the user is not logged-in or authenticated. You can go ahead and write the logic inside the else block to happen for non-authenticated users. The logic for this part is usually to redirect to another page/views if not logged-in.

Note:

  • is_authenticated is a property not a method!

For the if method, you will have to always use the if statement to check the is_authenticated property of the request object on each views you want to guard from non-logged-in users.

2: Using decorator (the @login_required decorator)

from django.contrib.auth.decorators import login_required

@login_required(login_url='login-page')
def only_authenticated_users(request):
  # this views is only for logged-in or authenticated users
  pass
  • The @login_required decorator accepts login_url as an argument. What this argument does is to allow you set the page users will be redirected to if they are not logged-in when they try to access this page.

For this complete beginners guide to Django, we will use the decorator method, because it works well with the DRY (Do Not Repeat Yourself) principle and uses less lines of code compare to the if or conditional method.

If you have not imported the login_required decorator, copy and paste the following import at the top of the blog views.py file:

from django.contrib.auth.decorators import login_required

We will decorate the:

Create views:

  • new_article views
  • new_category views

Update/edit views

  • edit_article views
  • edit_category views

Delete views

  • delete_article views and
  • delete_category views

So users will have to be logged-in to create, edit and delete their own articles and categories alone. For our login_url, we will use the login page which has a URL name of login.

Decorating the Create Views

Article Create Views

Find:

def new_article(request):

Add the following decorator at the top of it (after the comment):

@login_required(login_url='login')

Category Create Views

Find:

def new_category(request):

Add the following decorator at the top of it (after the comment):

@login_required(login_url='login')

Decorating the Edit/Update Views

Article Edit/Update Views

Find:

def edit_article(request, article_id):

Add the following decorator at the top of it (after the comment):

@login_required(login_url='login')

Category Edit/Update Views

Find:

def edit_category(request, category_id):

Add the following decorator at the top of it (after the comment):

@login_required(login_url='login')

Decorating the Delete Views

Article Delete Views

Find:

def delete_article(request, article_id):

Add the following decorator at the top of it (after the comment):

@login_required(login_url='login')

Category Delete Views

Find:

def delete_category(request, category_id):

Add the following decorator at the top of it (after the comment):

@login_required(login_url='login')

Hidding Edit and Delete Buttons from Non-loggedin Users

We will use the if Django template tag to hide the edit and delete buttons from users who are not logged-in, so they will have to logged-in to be able to access the buttons.

Open the base.html template, and find:

{% for article in articles %}

Replace everything inside the for template tag with:

<div class="cols-12 col-md-4 col-lg-3 mb-3">
  <div class="card">
    <img src="{{ article.featured_img.url }}" class="card-img-top" alt="{{ article.title }}">
    <div class="card-body">
      <h5 class="card-title"><a href="{{ article.get_absolute_url }}" title="{{ article.title }}">
          {{ article.title }}</a>
      </h5>
      <p class="card-text">{{ article.content|truncatewords:20 }}</p>
      <a href="{{ article.get_absolute_url }}" title="{{ article.title }}" class="btn btn-sm btn-primary">Read
        more</a> {% if request.user.is_authenticated %} | <a href="{{ article.get_delete_url }}" title="{{ article.title }}" class="btn btn-sm btn-danger">Delete</a>
      | <a href="{{ article.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a>
      {% endif %}
    </div>
  </div>
</div>

Find:

{% for category in categories %}

Replace everything inside the for template tag with:

<li class="mb-3"><a href="{{ category.get_absolute_url }}" title="{{ category.title }}">{{ category.title }}</a> {% if request.user.is_authenticated %} | <a
  href="{{ category.get_delete_url }}" title="{{ category.title}}" class="btn btn-sm btn-danger">Delete</a>
  | <a href="{{ category.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a>
{% endif %}
</li>

Open the blog.html template, and find:

{% for article in articles %}

Replace everything inside the for template tag with:

<div class="cols-12 col-md-4 col-lg-3 mb-3">
  <div class="card">
    <img src="{{ article.featured_img.url }}" class="card-img-top" alt="{{ article.title }}">
    <div class="card-body">
      <h5 class="card-title"><a href="{{ article.get_absolute_url }}" title="{{ article.title }}">
          {{ article.title }}</a>
      </h5>
      <p class="card-text">{{ article.content|truncatewords:20 }}</p>
      <a href="{{ article.get_absolute_url }}" title="{{ article.title }}" class="btn btn-sm btn-primary">Read
        more</a> {% if request.user.is_authenticated %} | <a href="{{ article.get_delete_url }}" title="{{ article.title }}" class="btn btn-sm btn-danger">Delete</a>
      | <a href="{{ article.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a>
      {% endif %}
    </div>
  </div>
</div>

Open the categories.html template, and find:

{% for category in categories %}

Replace everything inside the for template tag with:

<li class="mb-3"><a href="{{ category.get_absolute_url }}" title="{{ category.title }}">{{ category.title }}</a> {% if request.user.is_authenticated %} | <a
  href="{{ category.get_delete_url }}" title="{{ category.title}}" class="btn btn-sm btn-danger">Delete</a>
  | <a href="{{ category.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a>
{% endif %}
</li>

Open the categories.html template, and find:

{% for category in categories %}

Replace everything inside the for template tag with:

<li class="mb-3"><a href="{{ category.get_absolute_url }}" title="{{ category.title }}">{{ category.title }}</a> {% if request.user.is_authenticated %} | <a
  href="{{ category.get_delete_url }}" title="{{ category.title}}" class="btn btn-sm btn-danger">Delete</a>
  | <a href="{{ category.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a>
  {% endif %}
</li>

Open the category_detail.html template, and find:

<h2 class="text-center">Posts in {{ category.title }} <a href="{{ category.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a></h2>

Replace with:

<h2 class="text-center">Posts in {{ category.title }} {% if request.user.is_authenticated %}<a href="{{ category.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a>{% endif %}</h2>

Open the article_detail.html template, and find:

| <a href="{{ article.get_delete_url }}">Delete</a> | <a href="{{ article.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a></p>
<img src="{{ article.featured_img.url }}" class="card-img-top" alt="{{ article.title }}"> 
{% if request.user.is_authenticated %}| <a href="{{ article.get_delete_url }}">Delete</a> | <a href="{{ article.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a>{% endif %}

We will update the links that are not working as well as add some user management links that were created in this JovialGuide.

Up on-till now, the navbar links are not taking us to the page they are supposed to take us to. Let’s update them!

Open blog/templates/blog/base.html.

For:

<a class="navbar-brand" href="#">JovialGuide</a>

We will use a logo instead of text. This will require us to work with static files, which basically requires configuration.

Configuring Static Files

This subsection will show you how to configure static files in Django.

In the blog app, create a folder named static. Inside of the static folder, create another folder and name it your Django app which is blog. Since our logo is an image, inside of the blog folder, create another folder and name it img. This is where you will place all of your images. Inside of the img folder, paste a logo for your blog in this folder. Be sure the logo name is logo.png. Your folder structure should look like this:

django_project/
--blog/
----static/
------blog/
--------img/
----------logo.png

If you would like to use CSS in your Django app, inside of the blog/static/blog/ folder, create another folder named css. This is where you will put all of your CSS files for your Django app. Your folder structure should look like this:

django_project/
--blog/
----static/
------blog/
--------css/

Best practice:

  • It is best you create a static folder for each app you start.

Now that you have done those, open blog/templates/blog/base.html and find:

<a class="navbar-brand" href="#">JovialGuide</a>

Replace with:

<a class="navbar-brand" href="{% url 'homepage' %}">
  <img src="{% static 'blog/img/logo.png' %}" alt="Logo" width="160" height="60" class="img-fluid">
</a>

That’s it!

What of CSS? For CSS, assume you have blog/static/blog/css/styles.css, use:

<link rel="stylesheet" href="{% static 'blog/css/styles.css' %}">

For JavaScript, assume you have blog/static/blog/js/main.js, use:

<script src="{% static 'blog/js/main.js' %}"></script>
  • Django uses the static keyword to point to a static directory
  • We accessed the static file in the template using the name of the app, followed by the folder we placed the static files, then the static file itself.
  • If you place them in a subfolder like this: blog/static/blog/main.js, then you will use {% static ‘blog/main.js’ %}.
  • It is a very good practice if you create sub-folders for each of them rather than putting all of them together!

That’s all for configuring static files in Django.

Let’s continue updating other navbar links. Still in the base.html template, change:

Replace:

<a class="nav-link" aria-current="page" href="#">Home</a>

With:

Home

<a class="nav-link" href="#">Blog</a>

To:

<a class="nav-link" href="{% url 'blog' %}">Blog</a>

Change:

<a class="nav-link" href="#">Category</a>

To:

<a class="nav-link" href="{% url 'categories' %}">Category</a>

User management links, we mean link to the authentication pages we just created.

So, let’s add a:

  • link to login (if not logged-in)
  • link to log-out (if logged-in)
  • link to the create article/category pages if logged-in

Open the base.html template. Find:

<li class="nav-item">
  <a class="nav-link" href="{% url 'categories' %}">Categories</a>
</li>

Add the following lines below it:

{% if request.user.is_authenticated %}
<li class="nav-item">
  <a class="nav-link" href="{% url 'new-article' %}">New Post</a>
</li>
<li class="nav-item">
  <a class="nav-link" href="{% url 'new-category' %}">New Category</a>
</li>
<li class="nav-item">
  <a class="nav-link" href="{% url 'logout' %}">Logout</a>
</li>
{% else %}
<li class="nav-item">
  <a class="nav-link" href="{% url 'login' %}">Login</a>
</li>
<li class="nav-item">
  <a class="nav-link" href="{% url 'signup' %}">Signup</a>
</li>
{% endif %}
  • We use Python’s if statement to display certain links if the user is logged-in, and certain links only when user is not logged-in. This means that you will see different links when you are logged-in and different links when you are logged-out

Testing the Authentication Pages

So far, we have used the builtin Django authentication app to build an authentication system for our blog app. All that is remaining now is to test them out!

Navigate to 127.0.0.1:8000/. Create an account, login, and logout.

Unfortunately, the password reset views will not work because we have not configured sending email in Django, which is basically beyond the scope of this JovialGuide.

Django Tutorial Series

In this complete beginners guide to Django. If you need to read them again, or have not read them yet, please see:

We hope this complete beginners guide to Django – user authentication (part 10) – helped you learn how to work with Django authentication. This is a complete beginners guide to Django, so if you haven’t read part 9, then see Django tutorial series part 9: using flash messages or see part 1 – introduction to Django to get started.

See other of our Django tutorials for more.

You Might Also Like

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Shares