Security and Authentication
The security and authentication system in this codebase is centered around the django.contrib.auth package. It provides a robust framework for verifying user identities, managing sessions, and enforcing access controls through middleware and decorators.
Authentication Lifecycle
The authentication process is split into three distinct phases: validation, persistence, and integration.
Validation with Backends
Authentication begins with the authenticate() function (or its asynchronous counterpart aauthenticate()) found in django/contrib/auth/__init__.py. This function iterates through the backends defined in the AUTHENTICATION_BACKENDS setting to verify credentials.
# django/contrib/auth/__init__.py
@sensitive_variables("credentials")
def authenticate(request=None, **credentials):
"""
If the given credentials are valid, return a User object.
"""
for backend, backend_path in _get_compatible_backends(request, **credentials):
try:
user = backend.authenticate(request, **credentials)
except PermissionDenied:
break
if user is None:
continue
# Annotate the user object with the path of the backend.
user.backend = backend_path
return user
The default backend is django.contrib.auth.backends.ModelBackend, which validates usernames and passwords against the database.
Session Persistence
Once a user is authenticated, their identity is persisted across requests using the login() function. This function stores the user's ID and the backend used for authentication in the session.
# django/contrib/auth/__init__.py
def login(request, user, backend=None):
# ... (session management logic)
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
request.session[BACKEND_SESSION_KEY] = backend
# ...
rotate_token(request) # Security: rotate CSRF token on login
user_logged_in.send(sender=user.__class__, request=request, user=user)
To clear the session, logout(request) is used, which flushes the session data and resets request.user to an AnonymousUser instance.
Request Integration and Middleware
The system integrates the authenticated user into every request via AuthenticationMiddleware.
AuthenticationMiddleware
Located in django/contrib/auth/middleware.py, this middleware populates request.user with a SimpleLazyObject. This ensures that the database is only queried if the user attribute is actually accessed during the request lifecycle.
# django/contrib/auth/middleware.py
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
if not hasattr(request, "session"):
raise ImproperlyConfigured(...)
request.user = SimpleLazyObject(lambda: get_user(request))
request.auser = partial(auser, request)
It also provides request.auser(), an asynchronous method to retrieve the user, supporting modern async view patterns.
LoginRequiredMiddleware
For applications requiring global authentication, LoginRequiredMiddleware can be used to redirect all unauthenticated requests to the login page by default.
# django/contrib/auth/middleware.py
class LoginRequiredMiddleware(MiddlewareMixin):
def process_view(self, request, view_func, view_args, view_kwargs):
if not getattr(view_func, "login_required", True):
return None
if request.user.is_authenticated:
return None
return self.handle_no_permission(request, view_func)
Views can opt-out of this global requirement using the @login_not_required decorator from django/contrib/auth/decorators.py.
Protecting Views
Beyond middleware, individual views can be protected using decorators that check for authentication or specific permissions.
The @login_required Decorator
The @login_required decorator is a shortcut for ensuring a user is authenticated before accessing a view.
# django/contrib/auth/decorators.py
def login_required(
function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None
):
actual_decorator = user_passes_test(
lambda u: u.is_authenticated,
login_url=login_url,
redirect_field_name=redirect_field_name,
)
if function:
return actual_decorator(function)
return actual_decorator
Permission Checks
Permissions are handled via the permission_required decorator or by calling user.has_perm() directly. The system supports both individual user permissions and group-based permissions, managed by the ModelBackend.
Security Hardening
The codebase includes several built-in protections against common web vulnerabilities.
CSRF Protection
CsrfViewMiddleware (in django/middleware/csrf.py) provides protection against Cross-Site Request Forgery. It requires a valid CSRF token for all "unsafe" HTTP methods (POST, PUT, DELETE, PATCH).
The middleware supports two storage mechanisms for the CSRF secret:
- Cookie-based (Default): The secret is stored in a browser cookie.
- Session-based: If
CSRF_USE_SESSIONSis enabled, the secret is stored inrequest.session.
# django/middleware/csrf.py
def _get_secret(self, request):
if settings.CSRF_USE_SESSIONS:
return request.session.get(CSRF_SESSION_KEY)
else:
return request.COOKIES.get(settings.CSRF_COOKIE_NAME)
Session Security
To prevent session fixation attacks, the login() function calls rotate_token(request), which changes the CSRF token whenever a user logs in. Additionally, update_session_auth_hash() can be used after a password change to invalidate other active sessions while keeping the current session alive.
# django/contrib/auth/__init__.py
def update_session_auth_hash(request, user):
request.session.cycle_key()
if hasattr(user, "get_session_auth_hash") and request.user == user:
request.session[HASH_SESSION_KEY] = user.get_session_auth_hash()