User Authentication in Django

A significant percentage of modern, interactive websites allow some form of user interaction – from allowing simple comments on a blog, to full editorial control of articles on a news site. If a site offers any sort of ecommerce, authentication and authorization of paying customers is essential.

Just managing users – lost usernames, forgotten passwords and keeping information up to date – can be a real pain. As a programmer, writing an authentication system can be even worse.

Lucky for us, Django provides a default implementation for managing user accounts, groups,  permissions and cookie-based user sessions out of the box.

Like most things in Django, the default implementation is fully extensible and customizable to suit your project’s needs. So let’s jump right in.

Overview

The Django authentication system handles both authentication and authorization. Briefly, authentication verifies a user is who they claim to be, and authorization determines what an authenticated user is allowed to do. Here the term authentication is used to refer to both tasks.

The authentication system consists of:

  • Users
  • Permissions: Binary (yes/no) flags designating whether a user may perform a certain task
  • Groups: A generic way of applying labels and permissions to more than one user
  • A configurable password hashing system
  • Forms for managing user authentication and authorization
  • View tools for logging in users, or restricting content
  • A pluggable backend system

The authentication system in Django aims to be very generic and doesn’t provide some features commonly found in web authentication systems. Solutions for some of these common problems have been implemented in third-party packages:

  • Password strength checking
  • Throttling of login attempts
  • Authentication against third-parties (OAuth, for example)

Using The Django Authentication System

Django’s authentication system in its default configuration has evolved to serve the most common project needs, handling a reasonably wide range of tasks, and has a careful implementation of passwords and permissions. For projects where authentication needs differ from the default, Django also supports extensive extension and customization of authentication.

User Objects

User objects are the core of the authentication system. They typically represent the people interacting with your site and are used to enable things like restricting access, registering user profiles, associating content with creators etc. Only one class of user exists in Django’s authentication framework, i.e., superusers or admin staff users are just user objects with special attributes set, not different classes of user objects. The primary attributes of the default user are:

  • username
  • password
  • email
  • first_name
  • last_name

Creating Superusers

Create superusers using the createsuperuser command:

python manage.py createsuperuser --username=joe --email=joe@example.com 

You will be prompted for a password. After you enter one, the user will be created immediately. If you leave off the --username or the --email options, it will prompt you for those values.

Creating Users

The simplest, and least error prone way to create and manage users is through the Django admin. Django also provides built in views and forms to allow users to log in and out and change their own password. We will be looking at user management via the admin and generic user forms a bit later in this chapter, but first, let’s look at how we would handle user authentication directly.

The most direct way to create users is to use the included create_user() helper function:

>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user('john', 'lennon@thebeatles.com',  'johnpassword')

# At this point, user is a User object that has already been saved
# to the database. You can continue to change its attributes
# if you want to change other fields.
>>> user.last_name = 'Lennon'
>>> user.save()

Changing Passwords

Django does not store raw (clear text) passwords on the user model, but only a hash. Because of this, do not attempt to manipulate the password attribute of the user directly. This is why a helper function is used when creating a user. To change a user’s password, you have two options:

  1. manage.py changepassword username offers a method of changing a User’s password from the command line. It prompts you to change the password of a given user which you must enter twice. If they both match, the new password will be changed immediately. If you do not supply a user, the command will attempt to change the password of the user whose username matches the current system user.
  2. You can also change a password programmatically, using set_password():
      >>> from django.contrib.auth.models import User
      >>> u = User.objects.get(username='john')
      >>> u.set_password('new password')
      >>> u.save()
    

Changing a user’s password will log out all their sessions if the SessionAuthenticationMiddleware is enabled.

Permissions and Authorization

Django comes with a simple permissions system. It provides a way to assign permissions to specific users and groups of users. It’s used by the Django admin site, but you’re welcome to use it in your own code. The Django admin site uses permissions as follows:

  • Access to view the “add” form and add an object is limited to users with the “add” permission for that type of object.
  • Access to view the change list, view the “change” form and change an object is limited to users with the “change” permission for that type of object.
  • Access to delete an object is limited to users with the “delete” permission for that type of object.

Permissions can be set not only per type of object, but also per specific object instance. By using the has_add_permission(), has_change_permission() and has_delete_permission() methods provided by the ModelAdmin class, it’s possible to customize permissions for different object instances of the same type. User objects have two many-to-many fields: groups and user_permissions. User objects can access their related objects in the same way as any other Django model.

Default Permissions

When django.contrib.auth is listed in your INSTALLED_APPS setting, it will ensure that three default permissions – add, change and delete – are created for each Django model defined in one of your installed applications. These permissions will be created for all new models each time you run manage.py migrate.

Groups

django.contrib.auth.models.Group models are a generic way of categorizing users so you can apply permissions, or some other label, to those users. A user can belong to any number of groups. A user in a group automatically has the permissions granted to that group. For example, if the group Site editors has the permission can_edit_home_page, any user in that group will have that permission.

Beyond permissions, groups are a convenient way to categorize users to give them some label, or extended functionality. For example, you could create a group “Special users,” and you could write code that could, say, give them access to a members-only portion of your site, or send them members-only email messages.

Programmatically Creating Permissions

While custom permissions can be defined within a model’s Meta class, you can also create permissions directly. For example, you can create the can_publish permission for a BookReview model in books:

from books.models import BookReview
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(BookReview)
permission = Permission.objects.create(codename='can_publish',
                                       name='Can Publish Reviews',
                                       content_type=content_type)

The permission can then be assigned to a User via its user_permissions attribute or to a Group via its permissions attribute.

Permission Caching

The ModelBackend caches permissions on the User object after the first time they need to be fetched for a permissions check. This is typically fine for the request-response cycle since permissions are not typically checked immediately after they are added (in the admin, for example).

If you are adding permissions and checking them immediately afterward, in a test or view for example, the easiest solution is to re-fetch the User from the database. For example:

from django.contrib.auth.models import Permission, User
from django.shortcuts import get_object_or_404

def user_gains_perms(request, user_id):
    user = get_object_or_404(User, pk=user_id)
    # any permission check will cache the current set of permissions
    user.has_perm('books.change_bar')

    permission = Permission.objects.get(codename='change_bar')
    user.user_permissions.add(permission)

    # Checking the cached permission set
    user.has_perm('books.change_bar')  # False

    # Request new instance of User
    user = get_object_or_404(User, pk=user_id)

    # Permission cache is repopulated from the database
    user.has_perm('books.change_bar')  # True

    # ...