Introduction
Authentication backends are the pluggable system Django uses to verify user credentials. You can customize how users authenticate by writing custom backends.
Key Concepts
Backend: A class with authenticate() and get_user() methods.
ModelBackend: Django's default backend that checks username/password against the database.
Backend Order: Django tries each backend in AUTHENTICATION_BACKENDS until one succeeds.
Real World Context
Custom backends enable email-based login, LDAP/Active Directory integration, token authentication, and multi-tenant setups. Most production Django apps customize their authentication backend.
Deep Dive
Default ModelBackend
python# Django's built-in backend from django.contrib.auth.backends import ModelBackend class ModelBackend: def authenticate(self, request, username=None, password=None, **kwargs): UserModel = get_user_model() try: user = UserModel._default_manager.get_by_natural_key(username) except UserModel.DoesNotExist: UserModel().set_password(password) # Timing attack protection return None if user.check_password(password) and self.user_can_authenticate(user): return user return None
Email Authentication Backend
pythonfrom django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend class EmailBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): UserModel = get_user_model() try: # Allow login with email user = UserModel.objects.get(email=username) except UserModel.DoesNotExist: return None if user.check_password(password) and self.user_can_authenticate(user): return user return None
Configuration
python# settings.py AUTHENTICATION_BACKENDS = [ 'myapp.backends.EmailBackend', 'django.contrib.auth.backends.ModelBackend', # Fallback ] # Now authenticate() checks EmailBackend first, then ModelBackend
Backend with Rate Limiting
pythonfrom django.core.cache import cache class RateLimitedBackend(ModelBackend): MAX_ATTEMPTS = 5 LOCKOUT_DURATION = 300 # 5 minutes def authenticate(self, request, username=None, password=None, **kwargs): cache_key = f'login_attempts_{username}' attempts = cache.get(cache_key, 0) if attempts >= self.MAX_ATTEMPTS: return None # Locked out user = super().authenticate(request, username=username, password=password, **kwargs) if user is None: cache.set(cache_key, attempts + 1, self.LOCKOUT_DURATION) else: cache.delete(cache_key) return user
Common Pitfalls
- Forgetting
get_user(): Custom backends must implement bothauthenticate()and optionallyget_user(). - Not handling timing attacks: Always run
set_password()even when user not found. - Wrong backend order: More specific backends should come first.
Best Practices
- Extend ModelBackend: Instead of writing from scratch, extend the default.
- Keep the default as fallback: Add
ModelBackendas the last backend. - Use
user_can_authenticate(): Respects theis_activeflag.
Summary
Authentication backends let you customize credential verification. Extend ModelBackend for common cases. Configure AUTHENTICATION_BACKENDS in settings. Backends are checked in order until one returns a user.