Django Authentication Framework Basics

Django has an authentication framework which allows user's to login into the the application easily. In addition to that it provides following things:

  1. User Model.
  2. Persmissions - 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.

and many more.

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 is 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:

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 would create necessary tables in the database.

So what is django.contrib.contenttype ?

django.contrib.contenttype app allows us to associate permission with the the models we create in our apps. We will learn more about this in some later chapter.

Django authentication also uses session behind the scenes. As a result you must include the following two middlewares in the MIDDLEWARE list.

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

We have already learned what '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:

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',
]

Type of Users

Django has two types of users:

  1. A user which is not logged in or simply doesn't have credentials to login are called Anonymous Users. These users belong to a special class named AnonymousUser. The users which come to our site to consume content belongs to AnonymousUser Model class.
  2. Another type of users are the ones who are logged in to the application. This type of users belongs to the User Model class. It doesn't matter whether the logged in user is a superuser or a staff member or what privilege he has.

Here are some other uses of User class:

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

We will spend most of our time with the User model class. So, lets start with User Model class.

The User Model

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

>>>
>>> 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.

>>>
>>> u = User.objects.create_user(
... 'noisyboy',
... 'noisyboy@mail.com',
... 'password'
... )
>>>
>>>
>>> u
<User: noisyboy>
>>>
>>>

User model has lot of attributes. The following tables list some of them.

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 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 account was created. This is field is automatically filled with the current date and time when account is created.
last_login A field containing the date and time of last login.

We can also use Django ORM to get a complete list of attributes of the User model by executing the command below:

>>>
>>> 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
}]>
>>>

Note: Output is formatted to make it readable.

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

As you can see at this time noisyboy not a super user. We can easily change that by assigning is_superuser attribute to True and then calling the save() method as follows:

>>>
>>> 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 "password" as account password. So how come it becomes so long ?

It is a common security measure to not to store password in plaintext. 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 it returns is called password hash. The idea behind these functions is this - Although they can easily convert password string to long password hash, 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 we used create_user() method to create User object instead of create() or bulk_create() because create_user() automatically converts password to password hashes. If we had used create(), it would have saved the password in plain text in the database.

>>>
>>> test_user = User.objects.create(
... username='test',
... email='test@mail.com',
... password='pass'
... )
>>>
>>>
>>> test_user.password
'pass'
>>>

[]

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) Updates the password of the user. Just like check_password() it first converts password to password hash then saves it 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()

>>>
>>> u.get_username()
'noisyboy'
>>> u.get_full_name()
''
>>>

check_password()

>>>
>>> u.check_password("mypass")
False
>>> u.check_password("password")
True
>>>

set_password()

>>>
>>> u.set_password("pass")
>>> u
<User: noisyboy>
>>>

Just like create_user() method, the set_password() method first converts the password to a hash then storeds it into the database.

is_authenticated()

>>>
>>> u.is_authenticated()
True
>>>

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

>>>
>>> new_user = User.objects.create_user(
... 'newtestuser',
... 'newtestuser@mail.com',
... 'pass'
... )
>>>
>>> new_user.is_authenticated()
True
>>>

Its real utility comes into play when used in views and templates. As we will see.

is_anonymous()

>>>
>>> u.is_anonymous()
False
>>>

When used with User instances, is_anonymous() always returns False.

AnonymouseUser Model

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

  • id or pk attribute is always contains None.
  • username attribute will always be an empty string.
  • 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 doesn't have any kind of relationship with User class. It is 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 an 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 request object. The request.user returns either a 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 describe how we can use this behavior to our advantage. 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 AnonymousUser class as well as User class.

def test_logged_on_or_not(request):

    if request.user.is_authenticated():
        return HttpResponse("If you are seeing this it means you are logged in")
    else:
        return redirect("login.html")

This is how the above code works:

If user is logged in then the output would be "If you are seeing this it means you are logged
in"
. Otherwise it will redirect the user to login.html page.

In practice you probably would 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.

>>>
>>> from django.contrib.auth.models import AnonymousUser
>>> au = AnonymousUser()
>>>

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

>>>
>>> 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.