"D-J-A-N-G-O... The D is silent." - Authentication in Django

Samuel Dauzon

February 2016

The authentication module saves a lot of time in creating space for users. The following are the main advantages of this module:

  • The main actions related to users are simplified (connection, account activation, and so on)
  • Using this system ensures a certain level of security
  • Access restrictions to pages can be done very easily

(For more resources related to this topic, see here.)

It's such a useful module that we have already used it without noticing. Indeed, access to the administration module is performed by the authentication module. The user we created during the generation of our database was the first user of the site.

This article greatly alters the application we wrote earlier. At the end of this article, we will have:

  • Modified our UserProfile model to make it compatible with the module
  • Created a login page
  • Modified the addition of developer and supervisor pages
  • Added the restriction of access to connected users

How to use the authentication module

In this section, we will learn how to use the authentication module by making our application compatible with the module.

Configuring the Django application

There is normally nothing special to do for the administration module to work in our TasksManager application. Indeed, by default, the module is enabled and allows us to use the administration module. However, it is possible to work on a site where the web Django authentication module has been disabled. We will check whether the module is enabled.

In the INSTALLED_APPS section of the settings.py file, we have to check the following line:

'django.contrib.auth',

Editing the UserProfile model

The authentication module has its own User model. This is also the reason why we have created a UserProfile model and not just User. It is a model that already contains some fields, such as nickname and password. To use the administration module, you have to use the User model on the Python33/Lib/site-package/django/contrib/auth/models.py file.

We will modify the UserProfile model in the models.py file that will become the following:

class UserProfile(models.Model):
  user_auth = models.OneToOneField(User, primary_key=True)
  phone = models.CharField(max_length=20, verbose_name="Phone number", null=True, default=None, blank=True)
  born_date = models.DateField(verbose_name="Born date", null=True, default=None, blank=True)
  last_connexion = models.DateTimeField(verbose_name="Date of last connexion", null=True, default=None, blank=True)
years_seniority = models.IntegerField(verbose_name="Seniority", default=0)
def __str__(self):
  return self.user_auth.username

We must also add the following line in models.py:

from django.contrib.auth.models import User

In this new model, we have:

  • Created a OneToOneField relationship with the user model we imported
  • Deleted the fields that didn't exist in the user model

The OneToOne relation means that for each recorded UserProfile model, there will be a record of the User model. In doing all this, we deeply modify the database. Given these changes and because the password is stored as a hash, we will not perform the migration with South.

It is possible to keep all the data and do a migration with South, but we should develop a specific code to save the information of the UserProfile model to the User model. The code should also generate a hash for the password, but it would be long. To reset South, we must do the following:

  • Delete the TasksManager/migrations folder and all the files contained in this folder
  • Delete the database.db file

To use the migration system, we have to use the following commands:

manage.py schemamigration TasksManager --initial
manage.py syncdb –migrate

After the deletion of the database, we must remove the initial data in create_developer.py. We must also delete the URL developer_detail and the following line in index.html:

<a href="{% url "developer_detail" "2" %}">Detail second developer (The second user must be a developer)</a><br />

Adding a user

The pages that allow you to add a developer and supervisor no longer work because they are not compatible with our recent changes. We will change these pages to integrate our style changes. The view contained in the create_supervisor.py file will contain the following code:

from django.shortcuts import render
from TasksManager.models import Supervisor
from django import forms
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
def page(request):
  if request.POST:
    form = Form_supervisor(request.POST)
    if form.is_valid(): 
      name           = form.cleaned_data['name']
      login          = form.cleaned_data['login']
      password       = form.cleaned_data['password']
      specialisation = form.cleaned_data['specialisation']
      email          = form.cleaned_data['email']
      new_user = User.objects.create_user(username = login, email = email, password=password)
      # In this line, we create an instance of the User model with the create_user() method. It is important to use this method because it can store a hashcode of the password in database. In this way, the password cannot be retrieved from the database. Django uses the PBKDF2 algorithm to generate the hash code password of the user.
      new_user.is_active = True
      # In this line, the is_active attribute defines whether the user can connect or not. This attribute is false by default which allows you to create a system of account verification by email, or other system user validation.
      new_user.last_name=name
      # In this line, we define the name of the new user.
      new_user.save()
      # In this line, we register the new user in the database.
      new_supervisor = Supervisor(user_auth = new_user, specialisation=specialisation)
      # In this line, we create the new supervisor with the form data. We do not forget to create the relationship with the User model by setting the property user_auth with new_user instance.
      new_supervisor.save()
      return HttpResponseRedirect(reverse('public_empty')) 
    else:
      return render(request, 'en/public/create_supervisor.html', {'form' : form})
  else:
    form = Form_supervisor()
  form = Form_supervisor()
  return render(request, 'en/public/create_supervisor.html', {'form' : form})
class Form_supervisor(forms.Form):
  name = forms.CharField(label="Name", max_length=30)
  login = forms.CharField(label = "Login")
  email = forms.EmailField(label = "Email")
  specialisation = forms.CharField(label = "Specialisation")
  password = forms.CharField(label = "Password", widget = forms.PasswordInput)
  password_bis = forms.CharField(label = "Password", widget = forms.PasswordInput) 
  def clean(self): 
    cleaned_data = super (Form_supervisor, self).clean() 
    password = self.cleaned_data.get('password') 
    password_bis = self.cleaned_data.get('password_bis')
    if password and password_bis and password != password_bis:
      raise forms.ValidationError("Passwords are not identical.") 
    return self.cleaned_data

The create_supervisor.html template remains the same, as we are using a Django form.

You can change the page() method in the create_developer.py file to make it compatible with the authentication module (you can refer to downloadable Packt code files for further help):

def page(request):
  if request.POST:
    form = Form_inscription(request.POST)
    if form.is_valid():
      name          = form.cleaned_data['name']
      login         = form.cleaned_data['login']
      password      = form.cleaned_data['password']
      supervisor    = form.cleaned_data['supervisor'] 
      new_user = User.objects.create_user(username = login, password=password)
      new_user.is_active = True
      new_user.last_name=name
      new_user.save()
      new_developer = Developer(user_auth = new_user, supervisor=supervisor)
      new_developer.save()
      return HttpResponse("Developer added")
    else:
      return render(request, 'en/public/create_developer.html', {'form' : form})
  else:
    form = Form_inscription()
    return render(request, 'en/public/create_developer.html', {'form' : form})

We can also modify developer_list.html with the following content:

{% extends "base.html" %}
{% block title_html %}
    Developer list
{% endblock %}
{% block h1 %}
    Developer list
{% endblock %}
{% block article_content %}
    <table>
        <tr>
            <td>Name</td>
            <td>Login</td>
            <td>Supervisor</td>
        </tr>
        {% for dev in object_list %}
            <tr>
                <!-- The following line displays the __str__ method of the model. In this case it will display the username of the developer -->
                <td><a href="">{{ dev }}</a></td>
                <!-- The following line displays the last_name of the developer -->
                <td>{{ dev.user_auth.last_name }}</td>
                <!-- The following line displays the __str__ method of the Supervisor model. In this case it will display the username of the supervisor -->
                <td>{{ dev.supervisor }}</td>
            </tr>
        {% endfor %}
    </table>
{% endblock %}

Login and logout pages

Now that you can create users, you must create a login page to allow the user to authenticate. We must add the following URL in the urls.py file:

url(r'^connection$', 'TasksManager.views.connection.page', name="public_connection"),

You must then create the connection.py view with the following code:

from django.shortcuts import render
from django import forms
from django.contrib.auth import authenticate, login
# This line allows you to import the necessary functions of the authentication module.
def page(request):
  if request.POST:
  # This line is used to check if the Form_connection form has been posted. If mailed, the form will be treated, otherwise it will be displayed to the user.
    form = Form_connection(request.POST) 
    if form.is_valid():
      username = form.cleaned_data["username"]
      password = form.cleaned_data["password"]
      user = authenticate(username=username, password=password)
      # This line verifies that the username exists and the password is correct.
      if user:
      # In this line, the authenticate function returns None if authentication has failed, otherwise it returns an object that validates the condition.
        login(request, user)
        # In this line, the login() function allows the user to connect.
    else:
      return render(request, 'en/public/connection.html', {'form' : form})
  else:
    form = Form_connection()
  return render(request, 'en/public/connection.html', {'form' : form})
class Form_connection(forms.Form):
  username = forms.CharField(label="Login")
  password = forms.CharField(label="Password", widget=forms.PasswordInput)
  def clean(self):
    cleaned_data = super(Form_connection, self).clean()
    username = self.cleaned_data.get('username')
    password = self.cleaned_data.get('password')
    if not authenticate(username=username, password=password):
      raise forms.ValidationError("Wrong login or passwsord")
    return self.cleaned_data

You must then create the connection.html template with the following code:

{% extends "base.html" %}
{% block article_content %}
  {% if user.is_authenticated %}
  <-- This line checks if the user is connected.-->
    <h1>You are connected.</h1>
    <p>
      Your email : {{ user.email }}
      <-- In this line, if the user is connected, this line will display his/her e-mail address.-->
    </p>
  {% else %}
  <!-- In this line, if the user is not connected, we display the login form.-->
    <h1>Connexion</h1>
    <form method="post" action="{{ public_connection }}">
      {% csrf_token %}
      <table>
        {{ form.as_table }}
      </table>
      <input type="submit" class="button" value="Connection" />
    </form>
  {% endif %}
{% endblock %}

When the user logs in, Django will save his/her data connection in session variables. This example has allowed us to verify that the audit login and password was transparent to the user. Indeed, the authenticate() and login() methods allow the developer to save a lot of time. Django also provides convenient shortcuts for the developer such as the user.is_authenticated attribute that checks if the user is logged in. Users prefer when a logout link is present on the website, especially when connecting from a public computer. We will now create the logout page.

First, we need to create the logout.py file with the following code:

from django.shortcuts import render
from django.contrib.auth import logout
def page(request):
    logout(request)
    return render(request, 'en/public/logout.html')

In the previous code, we imported the logout() function of the authentication module and used it with the request object. This function will remove the user identifier of the request object, and delete flushes their session data.

When the user logs out, he/she needs to know that the site was actually disconnected. Let's create the following template in the logout.html file:

{% extends "base.html" %}
{% block article_content %}
  <h1>You are not connected.</h1>
{% endblock %}

Restricting access to the connected members

When developers implement an authentication system, it's usually to limit access to anonymous users. In this section, we'll see two ways to control access to our web pages.

Restricting access in views

The authentication module provides simple ways to prevent anonymous users from accessing some pages. Indeed, there is a very convenient decorator to restrict access to a view. This decorator is called login_required.

In the example that follows, we will use the designer to limit access to the page() view from the create_developer module in the following manner:

  1. First, we must import the decorator with the following line:
    from django.contrib.auth.decorators import login_required
  2. Then, we will add the decorator just before the declaration of the view:
    @login_required
    def page(request): # This line already exists. Do not copy it.
  3. With the addition of these two lines, the page that lets you add a developer is only available to the logged-in users. If you try to access the page without being connected, you will realize that it is not very practical because the obtained page is a 404 error. To improve this, simply tell Django what the connection URL is by adding the following line in the settings.py file:
    LOGIN_URL = 'public_connection'
  4. With this line, if the user tries to access a protected page, he/she will be redirected to the login page. You may have noticed that if you're not logged in and you click the Create a developer link, the URL contains a parameter named next. The following is the screen capture of the URL:
  5. This parameter contains the URL that the user tried to consult. The authentication module redirects the user to the page when he/she connects. To do this, we will modify the connection.py file we created. We add the line that imports the render() function to import the redirect() function:
    from django.shortcuts import render, redirect
  6. To redirect the user after they log in, we must add two lines after the line that contains the code login (request, user). There are two lines to be added:
    if request.GET.get('next') is not None:
      return redirect(request.GET['next'])

This system is very useful when the user session has expired and he/she wants to see a specific page.

Restricting access to URLs

The system that we have seen does not simply limit access to pages generated by CBVs. For this, we will use the same decorator, but this time in the urls.py file.

We will add the following line to import the decorator:

from django.contrib.auth.decorators import login_required

We need to change the line that corresponds to the URL named create_project:

url (r'^create_project$', login_required(CreateView.as_view(model=Project, template_name="en/public/create_project.html", success_url = 'index')), name="create_project"),

The use of the login_required decorator is very simple and allows the developer to not waste too much time.

Summary

In this article, we modified our application to make it compatible with the authentication module. We created pages that allow the user to log in and log out. We then learned how to restrict access to some pages for the logged in users.

To learn more about Django, the following books published by Packt Publishing (https://www.packtpub.com/) are recommended:

Django By Example (https://www.packtpub.com/web-development/django-example)

Learning Django Web Development (https://www.packtpub.com/web-development/learning-django-web-development)

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Django Essentials

Explore Title
comments powered by Disqus