Creating Custom User Authentication using AbstractBaseUser in Django

Introduction

This article will explain how we can create and consume custom user models in creating a custom authentication system in Django.

AbstractBaseUser Class

The AbstractBaseUser class in Django is a fundamental component of the authentication system. It provides the foundation for creating custom user models in Django, allowing developers to define their user model with the desired fields and behaviors.

  • When creating a custom user model in Django, developers can use AbstractBaseUser as the base class, which requires them to implement the necessary authentication methods such as creating the user, authenticating the user, and determining the user's permissions.
  • AbstractBaseUser provides the flexibility to define custom user models with specific requirements for fields such as username, email, name, etc. This enables developers to tailor the user model to the needs of their application.

AbstractBaseUser class contains the following methods.

Method Description
get_username() Returns the value of the field nominated by USERNAME_FIELD.
clean() Normalizes the username by calling normalize_username(). If you override this method, be sure to call super() to retain the normalization.
get_email_field_name() Returns the name of the email field specified by the EMAIL_FIELD attribute. Defaults to 'email' if EMAIL_FIELD isn’t specified.
normalize_username(username) Applies NFKC Unicode normalization to usernames so that visually identical characters with different Unicode code points are considered identical.
is_authenticated Read-only attribute which is always True. It's used to tell if the user has been authenticated. This doesn’t imply any permissions and doesn’t check if the user is active or has a valid session.
is_anonymous Read-only attribute which is always False. It's a way of differentiating User and AnonymousUser objects.
set_password(raw_password) Sets the user’s password to the given raw string, taking care of the password hashing. Doesn’t save the AbstractBaseUser object.
check_password(raw_password) Returns True if the given raw string is the correct password for the user. This takes care of the password hashing in making the comparison.
acheck_password(raw_password) Asynchronous version of check_password.
set_unusable_password() Marks the user as having no password set. check_password() for this user will never return True. Doesn’t save the AbstractBaseUser object.
has_usable_password() Returns False if set_unusable_password() has been called for this user.
get_session_auth_hash() Returns an HMAC of the password field. Used for Session invalidation on password change.
get_session_auth_fallback_hash() Yields the HMAC of the password field using SECRET_KEY_FALLBACKS. Used by get_user().


PermissionsMixin

The PermissionsMixin in Django is a built-in class that provides a set of permissions-related fields and methods for a custom user model. When used in conjunction with a custom user model that inherits from AbstractBaseUser, the PermissionsMixin adds the necessary attributes and methods to handle permissions and user access control.

  • The PermissionsMixin includes fields such as is_staff, which indicates whether the user is a staff member with access to the admin interface, and is_superuser, which denotes whether the user has all permissions without explicitly assigning them.
  • Additionally, it includes methods for checking user permissions, such as has_perm and has_module_perms.
  • By utilizing the PermissionsMixin, developers can easily integrate permission management into their custom user models, enabling fine-grained control over user access and privileges within a Django application.

PermissionsMixin class provides the following methods.

Method Description
is_superuser Boolean. Designates that this user has all permissions without explicitly assigning them.
get_user_permissions(obj=None) Returns a set of permission strings that the user has directly. If obj is passed in, only returns the user permissions for this specific object.
get_group_permissions(obj=None) Returns a set of permission strings that the user has, through their groups. If obj is passed in, only returns the group permissions for this specific object.
get_all_permissions(obj=None) Returns a set of permission strings that the user has, both through group and user permissions. If obj is passed in, only returns the permissions for this specific object.
has_perm(perm, obj=None) Returns True if the user has the specified permission, where perm is in the format "." (see permissions). If User.is_active and is_superuser are both True, this method always returns True. If obj is passed in, this method won’t check for permission for the model, but for this specific object.
has_perms(perm_list, obj=None) Returns True if the user has each of the specified permissions, where each perm is in the format ".". If User.is_active and is_superuser are both True, this method always returns True. If obj is passed in, this method won’t check for permissions for the model, but for the specific object.
has_module_perms(package_name) Returns True if the user has any permissions in the given package (the Django app label). If User.is_active and is_superuser are both True, this method always returns True.


BaseUserManager

BaseUserManager is a class provided by Django that includes methods for creating User instances. This manager class is suitable for most types of user models and can be overridden to customize user creation according to project needs. It is often used when creating a custom user model in Django to manage user creation and authentication.

BaseUserManager class provides the following methods.

Method Description
normalize_email(email) Normalizes email addresses by lowercasing the domain portion of the email address.
get_by_natural_key(username) Retrieves a user instance using the contents of the field nominated by USERNAME_FIELD.


UserCreationForm

This form is used for creating a new user within a Django application. It typically includes fields for username, password1, and password2 (password confirmation) and inherits from the ModelForm class. It provides a convenient way to handle the creation of new users and is customizable to accommodate additional fields or validation requirements.

UserChangeForm

This form is used for modifying existing user attributes, such as permissions, within the Django admin interface. It is employed to edit user details and is designed to be included in the admin site for user management.

Creating a Custom User Model

As per the table, given above, it is clear that some of the methods and parameters are imported whenever we inherit the AbstractBaseUser, PermissionsMixin, and BaseUSerManager class rest all the methods and variables we need to define.

To start with our development, we first need to create a Django application, for guidance on setting up the project, refer here.

Let's start writing out models.py.

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin

We need to import AbstractBaseUser and PermissionsMixin, as we want to utilize 'groups' & 'user_permissions' from PermissionsMixin; and 'password' & 'last_login' from AbstractUserModel.

from django.db import models

Post that, we will import django.db.models, to be able to use the model fields. You can read more here.

class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(_("email address"), unique=True)
    password = models.CharField(max_length=100)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)

In the above code,

  • We create a custom user model named 'CustomUser', which inherits AbstractBaseUser and PermissionsMixin.
  • After which we use "email" as our primary key and a password field.
  • "is_staff" and "is_active" are necessary fields to be able to create a superuser, these are not provided by the AbstractBaseUser class and, hence need to be defined.

Note: By default, Django keeps 'username' as the primary key.

    USERNAME_FIELD = "email"

USERNAME_FIELD variable is employed to let Django know, which field to be considered as the primary key for our model.

    REQUIRED_FIELDS = ["email", "password"]

REQUIRED_FIELDS variables are employed to make certain fields mandatory for the user to input.

Custom User Manager

After we are done writing the definition of models.py. We need to create our custom user manager written according to our custom user model.

To start, create a new file named "manager.py".

from django.contrib.auth.base_user import BaseUserManager

We will start by importing the BaseUserManager class.

class CustomUserManager(BaseUserManager):
    """
    Custom user model manager where email is the unique identifiers
    for authentication instead of usernames.
    """
    def create_user(self, email, password, **extra_fields):
        pass

    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """
        pass

We will then create the "CustomUserManager" class which will inherit the BaseUserManager class.

  • create_user() is used to create simple users
  • create_superuser() is used to create superusers

Since we have created our custom user model, we need to write the definition for these two functions.

    def create_user(self, email, password, **extra_fields):
        """
        Create and save a user with the given email and password.
        """
        if not email:
            raise ValueError("The Email must be set")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

In the above function,

  • In the function parameters, **extra_fields is used to take in dynamically added fields which may or may not be mandatory for the user.
  • We use the normalize_email() provided by BaseUserManager class to formal the email ID provided by the user.
  • We use the set_password() function to accept the password in the proper format from the user.
  • save() is used to save the model data to the corresponding database tables.
    def create_superuser(self, email, password, **extra_fields):
        """
        Create and save a SuperUser with the given email and password.
        """
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        extra_fields.setdefault("is_active", True)

        if extra_fields.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True.")
        if extra_fields.get("is_superuser") is not True:
            raise ValueError("Superuser must have is_superuser=True.")
        return self.create_user(email, password, **extra_fields)

Admin/super user is a type of user who has special/additional rights, hence in the above code

  • We set "is_staff", "is_superuser", and "is_active" to true.
  • To do error handling, we check if all the values were properly set, post which we call the simple user creation process.

Once we complete the manager.py, we need to import it into models.py using the following code.

from .manager import CustomUserManager

class CustomUser(AbstractBaseUser, PermissionsMixin):
    ## previous code
    objects = CustomUserManager()

Setting up a custom Authentication user model

Once we are done setting up the manager and user model, we need to let Django know that every time a user is created it has to be authenticated using our custom user model.

AUTH_USER_MODEL = "users.CustomUser"

Create the Database

We are done creating the base structure, now to utilize the above in our application, we need to make migrations to create the respective tables in the database. Use the following commands to achieve this

python manage.py makemigrations
python manage.py migrate

Managing Custom User Management from Django Admin (Optional)

This step is optional, as in many cases developers don't want to use the admin panel to manage the user model. To be able to manage the users in admin, we would need to create user creation and user modification forms i.e. GUI.

Note: It is highly recommended to create a superuser, before moving forward.

Creating Custom Admin Form

To create a custom admin creation and updation form, we need to create a new file named "forms.py",

from django.contrib.auth.forms import UserCreationForm, UserChangeForm

from .models import CustomUser

We need to import the "UserCreationForm" and "UserChangeForm" classes, which will be inherited by our custom classes. With that, we need to import the 'CustomUser' class, as the model has to be specified in both the custom classes, under "Meta".

Note. The use of "meta" in a class is to provide metadata about the model. It allows you to specify various options or behaviors for the model. For example, you can define the database table name, specify the ordering of results, define unique constraints, and more using the "Meta" class within a Django model.

class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = CustomUser
        fields = ("email","password")

class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = CustomUser
        fields = ("email","password")

In the above code, we define two classes with Meta class to let Django know which model to use and which fields need to be provided as editable.

Creating Admin Controller

Now we have reached the final step, i.e. to create the admin controller to manage the view for our custom user model. Since Django already provides the admin.py inside your app, hence we will be using that only.

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser

We need to import all the required packages.

  • django.contrib.admin provides a customizable administrative interface for managing data models in a Django application.
  • django.contrib.auth.admin.UserAdmin offers a default administrative interface for managing user accounts, groups, and permissions in Django's built-in authentication system.
  • We need to import our custom user model and creation & modification form classes.
class CustomUserAdmin(UserAdmin):
    CreationForm = CustomUserCreationForm
    ChangeForm = CustomUserChangeForm
    model = CustomUser
    list_display = ("email", "is_staff", "is_active",)
    list_filter = ("email", "is_staff", "is_active",)
    add_fieldsets = (
        (None, {
            "classes": ("wide",),
            "fields": (
                "email", "password1", "is_staff",
                "is_active", "groups", "user_permissions"
            )}
        ),
    )
    search_fields = ("email",)
    ordering = ("email",)

In the above code, we create a "CustomUserAdmin" class, which inherits the "UserAdmin" class. We need to specify the following data

  • User Management forms
  • Custom User Model
  • Table columns and filters
  • Search by which field
  • Order by clause
  • Define the layout and configuration of the fields displayed when adding a new user through the Django admin interface.
admin.site.register(CustomUser, CustomUserAdmin)

To be able to manage the custom user model using admin, we need to register "CustomUserAdmin" with Django.

Note. If you are adding custom authentication to the project that already has an admin registered, then you need to unregister first.

Conclusion

The process of creating custom user models using AbstractBaseUser in Django involves defining a new class that subclasses AbstractBaseUser, adding fields such as email, is_staff, is_active, and date_joined, specifying the unique identifier for the User model, and stating that all objects for the class come from the CustomUserManager. This approach allows for the creation of a versatile and personalized user model that can be tailored to specific project requirements and offers a high degree of flexibility and customization.


Similar Articles