Authentication
Custom User Model
A custom user model is a user-defined class that replaces Django's built-in User model to handle authentication and authorization. It is essential because it allows developers to store extra fields or change the authentication identity, such as using an email address instead of a username. You should implement this immediately at the start of any new project, as swapping the user model after migrations exist is technically complex and error-prone.
Why the Default Model is Limiting
Django's default User model is a one-size-fits-all solution that assumes every user has a username, password, and email. While this works for simple projects, real-world applications often require tracking additional metadata like phone numbers, birth dates, or specific account types. Because the User model is deeply integrated into Django's authentication system, changing it later requires a manual migration strategy that can be incredibly difficult to manage once production data exists. By default, Django uses a model that forces the 'username' field for identification, which is an outdated practice for modern web applications where users identify by email addresses. Starting with a custom model gives you complete control over the underlying database table structure and validation logic. Understanding that the User model is just another database table, we realize we must define it before any migrations are created to ensure Django properly links all related foreign keys to our new class.
# settings.py
# You must declare this early to tell Django to use your model
AUTH_USER_MODEL = 'accounts.CustomUser'Implementing AbstractBaseUser
To build a custom model, we inherit from 'AbstractBaseUser'. Unlike the basic 'User' model, 'AbstractBaseUser' provides only the absolute core of the authentication system, such as password hashing and token management. It does not provide fields like usernames or email addresses by default; you must define exactly which fields you want. This approach follows the principle of composition over inheritance. By manually adding your fields, you ensure that the database schema is clean and tailored exactly to your business requirements. You are also responsible for defining the 'USERNAME_FIELD', which identifies which specific field acts as the unique identifier for authentication. This design pattern ensures that your application is not carrying unnecessary 'baggage' from the default implementation. It requires developers to be explicit, which reduces bugs and unexpected behavior when the application grows in complexity. This is the foundation upon which you add your domain-specific user requirements.
from django.contrib.auth.models import AbstractBaseUser
class CustomUser(AbstractBaseUser):
# The field used for login
email = models.EmailField(unique=True)
is_active = models.BooleanField(default=True)
USERNAME_FIELD = 'email' # Replaces 'username'Creating the Required Manager
Django's authentication system relies heavily on a 'UserManager' to handle the logic of object creation, such as setting passwords and validating unique constraints. When you create a custom user, the default manager will fail because it expects the standard username-based fields. You must create a custom 'BaseUserManager' class that implements 'create_user' and 'create_superuser' methods. This manager acts as the entry point for Django's commands and admin forms. By overriding these methods, you define the business logic for what constitutes a valid user during the signup process. For instance, if you require a phone number or a specific company ID during user creation, you inject that logic here. This ensures that every user created through your application—or through the command line—adheres to the exact same validation rules and initialization requirements, preventing dirty data from entering your database at the source of creation.
from django.contrib.auth.models import BaseUserManager
class UserManager(BaseUserManager):
def create_user(self, email, password=None):
# Logic to normalize and save the user
user = self.model(email=self.normalize_email(email))
user.set_password(password)
user.save(using=self._db)
return userAdding Permissions and Admin Compatibility
To make a custom user model fully compatible with Django's admin interface and permission system, it must implement a few specific methods and fields. Specifically, 'PermissionsMixin' should be inherited. This mixin adds the necessary fields and methods, such as 'is_superuser' and 'groups', to interact with Django's existing permission checking system. Without this, your custom model would have no way to track which users are administrators or what access level they have to individual models. It is a vital step because it bridges the gap between your custom user object and the broad array of built-in features Django provides for security. The methods 'has_perm' and 'has_module_perms' are also provided by this mixin, allowing the admin dashboard to verify if a user has access to specific parts of the system. This integration allows you to maintain full functionality without reinventing the authentication logic from scratch.
from django.contrib.auth.models import PermissionsMixin
class CustomUser(AbstractBaseUser, PermissionsMixin):
# Now supports groups and permissions
is_staff = models.BooleanField(default=False)Registering the Custom User
Finally, after defining the model and the manager, you must link them together in your application settings. Django needs to know that your project is using a different model than the one it provides internally. This is done by pointing the 'AUTH_USER_MODEL' variable in your settings file to your new class. It is imperative that this is completed before running your first migration. If you try to switch models after your database has already been initialized with the default User table, you will encounter serious schema migration errors that require database intervention to fix. By declaring it early, Django's ORM generates the initial database schema correctly, with all foreign keys pointing to your 'CustomUser' table. This ensures that whenever you reference the 'User' model in other parts of your code, Django dynamically resolves it to your custom implementation, maintaining consistency across your entire project architecture.
# settings.py
# This tells the entire framework to use your class
AUTH_USER_MODEL = 'accounts.CustomUser'Key points
- You must define a custom user model before running your very first migration.
- Inheriting from AbstractBaseUser allows you to define only the fields you actually need.
- The USERNAME_FIELD property dictates which attribute is used for unique identification during login.
- A custom BaseUserManager is required to handle user creation logic properly for your new model.
- PermissionsMixin provides the necessary support for Django's internal admin and group-based permission system.
- Using AUTH_USER_MODEL in your settings file informs the framework to substitute your model for the default.
- The UserManager ensures that password hashing and email normalization occur consistently during object creation.
- Modifying the user model after the database is already populated is a highly complex and risky operation.
Common mistakes
- Mistake: Changing the User model after initial migrations have run. Why it's wrong: It causes database inconsistency and migration dependency conflicts. Fix: Always set up a custom User model before running the initial migrate command in a new project.
- Mistake: Using AbstractUser when you only need to add a few fields. Why it's wrong: It inherits all fields from the default Django user, which may lead to bloated database tables. Fix: Use AbstractBaseUser for full control or extend AbstractUser only if you want to keep existing authentication fields.
- Mistake: Forgetting to update AUTH_USER_MODEL in settings.py. Why it's wrong: Django will continue to point to the default auth.User model, causing attribute errors when accessing user-related logic. Fix: Explicitly set AUTH_USER_MODEL = 'app_name.CustomUser' in settings.py.
- Mistake: Overwriting the 'username' field without updating the UserManager. Why it's wrong: Django's default UserManager expects a username field by default and will crash if it's absent or modified. Fix: Create a custom subclass of BaseUserManager to handle field requirements for the new model.
- Mistake: Not including the 'is_staff' or 'is_superuser' flags when using AbstractBaseUser. Why it's wrong: The Django admin panel relies on these flags to grant permissions, and they are not included by default in AbstractBaseUser. Fix: Manually add PermissionsMixin to your model to inherit the necessary admin fields.
Interview questions
What is the recommended practice for handling user authentication in a new Django project?
The best practice is to always define a custom user model before you run any migrations for the first time in a new Django project. Even if the default User model currently meets your needs, extending AbstractUser or creating a custom model gives you the flexibility to add fields like 'date_of_birth' or 'bio' later without performing complex database migrations. Changing the user model after migrations exist is technically possible but significantly more painful, so starting with a custom model from the very beginning is the industry standard for scalable Django applications.
How do you implement a custom user model in Django?
To implement a custom user model, you create a new class that inherits from 'AbstractUser' or 'AbstractBaseUser' inside one of your apps. You then update your settings.py file by setting 'AUTH_USER_MODEL' to 'app_name.CustomUser'. This tells Django to use your model instead of the built-in one. Once configured, you must ensure that your migration history is set up correctly. This approach is powerful because it allows you to maintain full control over the authentication process while still leveraging Django’s built-in authentication views and admin integration.
What is the difference between AbstractUser and AbstractBaseUser?
When subclassing to create a custom user model, 'AbstractUser' is the go-to choice if you want to keep all of Django's default fields like username, email, and password, but simply want to add a few custom fields. Conversely, 'AbstractBaseUser' is for when you want total control; it provides only the core authentication functionality and nothing else. If you use 'AbstractBaseUser', you must manually define the 'USERNAME_FIELD' and include a custom manager that implements the 'create_user' and 'create_superuser' methods, which is significantly more work but necessary for highly unique user requirements.
Compare the approach of using a Profile model versus extending the User model.
Using a Profile model involves a OneToOne relationship between your custom model and the built-in Django User model. This is easier to implement later in a project's lifecycle, but it requires extra database joins and signaling logic to keep the profile in sync with the user. Extending the User model via AbstractUser is cleaner as it consolidates all user data into a single table. I prefer extending the User model because it improves query performance and simplifies code maintenance by removing the need to manage separate profile objects across the entire application.
Why is it mandatory to use a custom User Manager when using AbstractBaseUser?
When you inherit from 'AbstractBaseUser', you lose the default functionality that Django's built-in User model provides, including the logic for handling user creation and password hashing. Django's authentication system relies on specific methods within a manager to interface with the database. Specifically, you must implement a manager that defines 'create_user' and 'create_superuser' methods to handle field validation and object creation correctly. Without this custom manager, you would not be able to use 'createsuperuser' via the command line or store passwords effectively, effectively breaking the Django administrative authentication system.
Explain the risks of changing AUTH_USER_MODEL in a project that already has migrations.
Changing the user model in a production environment or a project with existing migrations is extremely dangerous because the database schema for the original User model is already baked into your migration files. If you change 'AUTH_USER_MODEL' mid-stream, Django will struggle to map the foreign keys in your other models to the new user table. You would essentially have to drop your database, delete your migrations, or write complex data migration scripts to migrate user data from the old table to the new one. This is why planning the user model architecture before initializing the database is considered a foundational task in Django development.
Check yourself
1. Why is it recommended to define a custom User model at the start of a project?
- A.It improves the performance of database queries globally
- B.Changing it later requires complex manual migrations and data migration scripts
- C.It is required by the Django security framework to enable encryption
- D.It automatically optimizes the template rendering engine
Show answer
B. Changing it later requires complex manual migrations and data migration scripts
Changing the User model later is difficult because Django's ORM and foreign keys depend on the User model structure. Options 0, 2, and 3 are incorrect because user model selection does not inherently affect performance, security, or template rendering speed.
2. What is the primary difference between AbstractUser and AbstractBaseUser?
- A.AbstractUser provides full auth fields; AbstractBaseUser provides only the essential infrastructure
- B.AbstractUser is used for admin users; AbstractBaseUser is used for standard users
- C.AbstractBaseUser includes built-in email validation; AbstractUser requires custom logic
- D.There is no functional difference; they are aliases for the same class
Show answer
A. AbstractUser provides full auth fields; AbstractBaseUser provides only the essential infrastructure
AbstractUser includes all the default Django User fields (username, email, etc.), while AbstractBaseUser only includes the password and last_login fields, requiring manual implementation of other fields. Options 1, 2, and 3 are factually incorrect.
3. When using AbstractBaseUser, which mixin must you include if you want to use the Django Admin interface's permission system?
- A.AdminMixin
- B.PermissionMixin
- C.PermissionsMixin
- D.UserAdmin
Show answer
C. PermissionsMixin
PermissionsMixin adds the fields and methods necessary for Django's permission and group management. Option 0 and 1 are not standard mixins, and Option 3 is a model admin class, not a mixin for the User model.
4. If you are replacing the default 'username' with 'email' for authentication, what must you update in your UserManager?
- A.The AUTH_USER_MODEL variable in the manager
- B.The REQUIRED_FIELDS list to include username
- C.The USERNAME_FIELD attribute to point to 'email'
- D.The authenticate() method to manually query the database
Show answer
C. The USERNAME_FIELD attribute to point to 'email'
USERNAME_FIELD defines the unique identifier for the user. Setting it to 'email' tells Django to use that field for authentication. Options 0, 1, and 3 do not correctly facilitate switching the primary authentication identifier.
5. What happens if you define a custom User model but fail to update AUTH_USER_MODEL in settings.py?
- A.The project will crash immediately upon startup
- B.Django will use the default User model, ignoring your custom one
- C.The admin panel will display both user models side-by-side
- D.The database will automatically merge your fields into the default model
Show answer
B. Django will use the default User model, ignoring your custom one
If AUTH_USER_MODEL is not set, Django defaults to 'auth.User'. It does not merge fields or crash immediately. Options 0, 2, and 3 describe behaviors that do not occur in the Django framework.