OverIQ.com

Django Authentication Framework Basics

Last updated on July 27, 2020


Django has an authentication framework which allows us to add login functionality to our application easily. In addition to that it provides following things:

  1. User Model.
  2. Permissions - A way to give permission to the individual users.
  3. Groups - A way to assign permission to one or more users.
  4. A password hashing system.
  5. Form, views and URL patterns to login/logout users.

Django authentication framework is quite big. We will start from the basics and then move on to more complex topics later.

Setting Up Authentication Framework #

The authentication framework is implemented as 'django.contrib.auth' app but it also depends upon on 'django.contrib.contenttype' app and some middlewares.

To use authentication system you must have 'django.contrib.auth' and 'django.contrib.contenttype' in the INSTALLED_APPS list as follows:

TGDB/django_project/django_project/settings.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
    'cadmin',
]
#...

If you are adding 'django.contrib.auth' and 'django.contrib.contenttype' right now, run python manage.py migrate command. This command will create necessary tables in the database.

So what is django.contrib.contenttype ?

The django.contrib.contenttype app is used to keep track of different types of content in the project. This app has slightly advanced use cases, so we won't be covering it in this beginner tutorial.

Django authentication also uses sessions behind the scenes. As a result, you must have the following two middlewares in the MIDDLEWARE list in settings.py.

  1. django.contrib.sessions.middleware.SessionMiddleware
  2. django.contrib.auth.middleware.AuthenticationMiddleware

We have already learned what django.contrib.sessions.middleware.SessionMiddleware middleware does in Session in Django lesson.

The sole job of django.contrib.auth.middleware.AuthenticationMiddleware is to add a user attribute to the request object. The utility of this will become clear in the upcoming sections.

Finally, MIDDLEWARE setting should look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#...
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.common.BrokenLinkEmailsMiddleware',
]
#...

Type of Users #

Django has two types of users:

  1. A user who is not logged in is called an Anonymous User. This type of user is represented using AnonymousUser model. For example, the users who come to our site to consume content belongs to AnonymousUser model.

  2. Another type of users are the ones who are logged in to the application. This type of users belongs to the User model. It doesn't matter whether the logged in user is a superuser or a staff member or what privilege he has. If he is logged in, he belongs to the User models.

The AnonymousUser model is not much used apart from the web requests but the User model is used in many different places. Here are some examples:

  • Registering new users.
  • Assigning privilege.
  • Restricting access.
  • Associate content with its creators.

We will spend most of our time with the User model class. The following section explains everything you need to know about the User model.

The User Model #

The User model is considered as the heart of Django authentication framework. To use it you must first import it from django.contrib.auth.models.

1
2
3
>>>
>>> from django.contrib.auth.models import User
>>>

Creating a User object #

Django provides a custom method on objects manager named create_user() to create users. It accepts at-least three parameters username, password, and email.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
>>>
>>> u = User.objects.create_user(
... 'noisyboy',
... 'noisyboy@mail.com',
... 'password'
... )
>>>
>>>
>>> u
<User: noisyboy>
>>>
>>>

The User model has a lot of attributes. The following table lists all the attributes of the User model.

Attribute Description
username A required, 30 character field to store username. It can only take alphanumeric characters i.e letters, digits and underscores.
password A required field to store password.
first_name An optional field of 30 characters to store user's first_name.
last_name An optional field of 30 characters to store user's last_name.
email An optional field to store email address.
is_active A boolean field which tells whether the account can be used to login or not.
is_superuser A boolean field if set to True then the account has all the permissions.
is_staff A boolean field if set to True then it means user can access Django admin site.
date_joined A field to store date and time when the account was created. This is field is automatically filled with the current date and time when the account is created.
date_joined A field containing the date and time of the account creation.
last_login A field containing the date and time of the last login.

To get attribute values of the newly created User instance type the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
>>>
>>> User.objects.filter(username='noisyboy').values()
<QuerySet
[{
'password': 'pbkdf2_sha256$30000$U1SPNlOr8xJe$58trstDH3OF1APsthvGVJZ5u8mZxOB1ppreoan2UyEs=', 'date_joined': datetime.datetime(2017, 4, 18, 13, 45, 33, 681567, tzinfo=<UTC>),
'email': 'noisyboy@mail.com',
'is_superuser': False, '
username': 'noisyboy',
'first_name': '',
'id': 15,
'last_name': '',
'last_login': None,
'is_staff': False,
'is_active': True
}]>
>>>

Recall that values() method returns a Queryset object containing dictionary rather than model instances, where each dictionary key corresponds to the attribute defined in the model.

As you can see, currently the noisyboy is not a superuser (because 'is_superuser' key is set to False). We can easily change that by assigning is_superuser attribute to True and then calling the save() method as follows:

1
2
3
4
5
6
7
>>>
>>> u.is_superuser = True
>>>
>>> u.is_superuser
True
>>> u.save()
>>>

An important thing to notice here is that the value of password field in the output of User.objects.filter(username='noisyboy').values() command. It looks something like this:

'password': 'pbkdf2_sha256$30000$U1SPNlOr8xJe$58trstDH3OF1APsthvGVJZ5u8mZxOB1ppreoan2UyEs='

Recall that while creating noisyboy user we have used the string "password" as account password. So how come it becomes so long?

It is a common security measure to not store passwords as plaintext into the database. Instead, we use mathematical functions which converts our password to a long string like the one above. Such functions are called hash functions and the long string they return is called password hash. The idea behind these functions is this - creating password hash from a password is easy, but the reverse process is not possible.

Okay! That's enough Cryptography for now.

By default, Django uses PBKDF2 algorithm to create password hashes.

This is the reason why we used create_user() method to create User object instead of create() or bulk_create() method. The create_user() method automatically converts a password to a hash. If we had used create() or bulk_create() method, it would have saved the password as plain text into the database. We can verify this by creating a new user using the create() method as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>>
>>> test_user = User.objects.create(
... username='test',
... email='test@mail.com',
... password='pass'
... )
>>>
>>>
>>> test_user.password
'pass'
>>>

Django stores the user data in the auth_user table. After executing the above code the auth_user table should look like this:

Indeed, the password is stored in plain text in the database.

Here are some of the methods supplied by the User model:

Method Description
get_username() returns username of the user. To get the username you should use this property instead of directly referencing the username attribute.
get_full_name() returns the value of first_name and last_name attribute with a space in between
check_password(pass) It returns True if string passed is the correct password, otherwise False. It first converts the password to password hash then compares it to the one saved in the database.
set_password(passwd) It is used to change the password of the user. It takes care of password hashing. Note that set_password() doesn't save the User object, that's why you have to call save() method to commit the changes to the database.
is_authenticated() A logged in user is referred to as authenticated. It returns a True or False.
is_anonymous() returns True if the user is anonymous, otherwise False.

get_username() #

1
2
3
4
5
6
>>>
>>> u.get_username()
'noisyboy'
>>> u.get_full_name()
''
>>>

check_password() #

1
2
3
4
5
6
>>>
>>> u.check_password("mypass")
False
>>> u.check_password("password")
True
>>>

set_password() #

1
2
3
4
5
>>>
>>> u.set_password("pass")
>>> u
<User: noisyboy>
>>>

To commit the changes call the save() method.

1
2
3
>>>
>>> u.save()
>>>

is_authenticated() #

1
2
3
4
>>>
>>> u.is_authenticated()
True
>>>

Here is_authenticated() method returns True, it doesn't mean that the noisyboy is currently logged in to the Django Admin. In fact, inside the Django shell, is_authenticated() when used on User instances always returns True. We can verify this fact by creating a new user and then checking the return value of is_authenticated() method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
>>>
>>> new_user = User.objects.create_user(
... 'newtestuser',
... 'newtestuser@mail.com',
... 'pass'
... )
>>>
>>> new_user.is_authenticated()
True
>>>

Inside Django Shell is_authenticated() is useless. Its real utility comes into play in views and templates. As we will see.

is_anonymous() #

1
2
3
4
>>>
>>> u.is_anonymous()
False
>>>

The object u belongs to the User class that's why is_anonymous() returns False. Had it belong to the AnonymousUser class, it would have returned True.

AnonymouseUser Model #

Django implements a special class called AnonymousUser which represents users which are not logged in. In other words, the regular user which come to our site to consume content belongs to AnonymousUser class. The AnonymousUser class has almost all the same methods and attributes as that of User class with the following difference.

  • id or pk attribute is always contains None.
  • username attribute will always be an empty string i.e ''.
  • is_anonymous() is True instead of False.
  • is_authenticated() is False instead of True.
  • is_staff and is_superuser will are always be False.
  • is_active will is always be False.
  • Trying to call save() or delete() on AnonymousUser object will raise NotImplementedError.

It is important to note that AnonymousUser class doesn't have any kind of relationship with User class. It is a separate class with its own methods and attributes. The only similarity between User and AnonymousUser class is that most of the attributes and methods are same. It is not a design flaw, It is done on purpose to make things easier.

Earlier in this chapter, we have discussed that django.contrib.auth.middleware.AuthenticationMiddleware middleware adds a user attribute to the request object. The request.user returns either an instance of AnonymousUser or User class. Because both User and AnonymousUser class implements the same interface, we can use any attribute/method to get the relevant information without worrying about whether the object is of type AnonymousUser or User.

The following example perfectly describes how we can use this behavior to our advantage. The test_logged_on_or_not() view tests whether the user is logged in or not using the is_authenticated() method. We are able to write this code because is_authenticated() is implemented in the AnonymousUser class as well as in the User class.

1
2
3
4
5
def test_logged_on_or_not(request):
    if request.user.is_authenticated():
        return HttpResponse("You are logged in.")
    else:
        return redirect("login")

This is how the above code works:

If a user is logged in then the output will be "You are logged in.". Otherwise, the user will be redirected to the login page.

In practice, you would probably never need to create an object of type AnonymousUser. Just in case, you are curious, the following code shows you how to create an object of AnonymousUser type.

1
2
3
4
>>>
>>> from django.contrib.auth.models import AnonymousUser
>>> au = AnonymousUser()
>>>

One we have access to AnonymousUser instance we can use any attribute/method to get any relevant information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
>>>
>>> print(au.id)
None
>>> print(au.pk)
None
>>>
>>> au.username
''
>>>
>>> au.is_authenticated()
False
>>>
>>> au.is_anonymous()
True
>>>
>>> au.is_active
False
>>>
>>> au.is_superuser
False
>>>
>>>
>>> au.delete()
Traceback (most recent call last):
...  
NotImplementedError: Django doesn't provide a DB representation for AnonymousUser.
>>>
>>>
>>> au.save()
Traceback (most recent call last):
...
NotImplementedError: Django doesn't provide a DB representation for AnonymousUser.
>>>

As described, calling save() or delete() on AnonymousUser object raises NotImplementedError exception.