User Model and Authentication Basics
The authentication system in this codebase is built around a flexible hierarchy of user models. This structure allows for everything from a standard username-based login to highly customized user entities with unique identifiers or specialized authentication logic.
User Model Hierarchy
The system provides three levels of abstraction for defining users, located in django/contrib/auth/base_user.py and django/contrib/auth/models.py.
AbstractBaseUser
The AbstractBaseUser class in django/contrib/auth/base_user.py is the foundation of the authentication system. it provides the essential machinery for handling passwords and session security without enforcing a specific schema for identifying users (like a username or email).
Key features include:
- Password Management: Implements
set_password()andcheck_password()(and their async counterpartsacheck_password()), which handle hashing and verification. - Session Security: The
get_session_auth_hash()method returns an HMAC of the password field. This is used by the session backend to invalidate sessions across all devices when a user changes their password. - Authentication State: Hardcodes
is_authenticatedtoTrueandis_anonymoustoFalse.
AbstractUser
Located in django/contrib/auth/models.py, AbstractUser inherits from AbstractBaseUser and PermissionsMixin. It provides a "batteries-included" user model that includes common fields:
username(withUnicodeUsernameValidator)first_nameandlast_nameemailis_staffandis_activedate_joined
It also defines USERNAME_FIELD = "username", which tells the authentication backends which field to use for identification.
User
The User class is the concrete implementation of AbstractUser. In most projects, this is the model used by default. It is marked as swappable, meaning it can be replaced by a custom model via the AUTH_USER_MODEL setting.
Representing Anonymous Visitors
The AnonymousUser class in django/contrib/auth/models.py provides a way to handle unauthenticated visitors using the same API as a real User object. This consistency allows developers to write code like request.user.is_authenticated without checking if request.user is None.
Unlike the standard user models, AnonymousUser:
- Is not a database model (calling
.save()or.delete()raisesNotImplementedError). - Has
idandpkset toNone. - Returns
Trueforis_anonymousandFalseforis_authenticated. - Provides empty implementations for permission checks (e.g.,
has_perm()always returnsFalse).
Customizing the User Model
Depending on the requirements, you can customize the user model by inheriting from either AbstractUser or AbstractBaseUser.
Extending AbstractUser
If you want to keep the default fields but add extra data or change the primary key, inherit from AbstractUser. For example, tests/auth_tests/models/uuid_pk.py demonstrates using a UUID as a primary key:
from django.contrib.auth.models import AbstractUser
from django.db import models
import uuid
class User(AbstractUser):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
Building from AbstractBaseUser
If you want to completely redefine the user identification logic (e.g., using only an email or a custom token), inherit from AbstractBaseUser. You must define a USERNAME_FIELD and a REQUIRED_FIELDS list.
A minimal example from tests/auth_tests/models/no_password.py shows a user model that only requires a username:
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.db import models
class NoPasswordUser(AbstractBaseUser):
username = models.CharField(max_length=100, unique=True)
USERNAME_FIELD = 'username'
objects = BaseUserManager()
Best Practices for Referencing Users
Because the user model can be swapped, you should never import the User model directly in your application code. Instead, use the following patterns:
- In Code: Use
django.contrib.auth.get_user_model()to retrieve the active user model class. - In Models: When defining a
ForeignKeyto the user model, usesettings.AUTH_USER_MODEL.
The get_user_model() function in django/contrib/auth/__init__.py ensures you are always interacting with the model defined in the project's configuration:
def get_user_model():
"""
Return the User model that is active in this project.
"""
try:
return django_apps.get_model(settings.AUTH_USER_MODEL, require_ready=False)
except ValueError:
# ... error handling ...
This approach ensures that your application remains compatible even if the project switches to a custom user model later.