Skip to main content

Customizing Authentication Backends

To implement custom authentication logic in this project—such as integrating with external identity providers, handling remote headers, or modifying permission checks—you must create a custom backend class and register it in your settings.

Implementing a Custom Authentication Backend

The most direct way to implement custom logic is to extend BaseBackend and provide implementations for authenticate() and get_user().

from django.contrib.auth.backends import BaseBackend
from django.contrib.auth import get_user_model

UserModel = get_user_model()

class MyCustomBackend(BaseBackend):
def authenticate(self, request, token=None, **kwargs):
# Custom logic to verify a token or external credential
if token == "valid-secret-token":
try:
return UserModel.objects.get(username="admin")
except UserModel.DoesNotExist:
return None
return None

def get_user(self, user_id):
try:
return UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None

To use this backend, add it to the AUTHENTICATION_BACKENDS setting in your settings.py:

AUTHENTICATION_BACKENDS = [
'path.to.MyCustomBackend',
'django.contrib.auth.backends.ModelBackend',
]

Integrating with Remote Headers (SSO)

If your application is behind a proxy that handles authentication (like LDAP or an SSO provider) and passes the username in an HTTP header, use RemoteUserBackend. You can customize how the username is cleaned and how the user object is configured upon creation.

As seen in tests/auth_tests/test_remote_user.py, you can override clean_username and configure_user:

from django.contrib.auth.backends import RemoteUserBackend

class CustomRemoteUserBackend(RemoteUserBackend):
def clean_username(self, username):
# Example: Strip domain from "user@example.com"
return username.split("@")[0]

def configure_user(self, request, user, created=True):
# Populate additional attributes from request headers
user.email = request.META.get("HTTP_REMOTE_EMAIL", "")
if not created:
# Update existing users if necessary
user.last_name = user.username
user.save()
return user

Providing Async Support

This codebase supports asynchronous authentication. When implementing a custom backend, you should provide aauthenticate and aget_user methods to avoid blocking the event loop in async contexts.

from django.contrib.auth.backends import BaseBackend
from asgiref.sync import sync_to_async

class AsyncCompatibleBackend(BaseBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
# Sync implementation
...

async def aauthenticate(self, request, username=None, password=None, **kwargs):
# Async implementation using sync_to_async or native async calls
return await sync_to_async(self.authenticate)(request, username, password, **kwargs)

def get_user(self, user_id):
...

async def aget_user(self, user_id):
return await sync_to_async(self.get_user)(user_id)

Customizing Permissions

You can override how permissions are calculated by implementing get_user_permissions or get_group_permissions. This is useful for integrating with external authorization services.

Example from tests/auth_tests/test_auth_backends.py:

from django.contrib.auth.backends import BaseBackend

class SimplePermissionBackend(BaseBackend):
def get_user_permissions(self, user_obj, obj=None):
# Return a set of permission strings
return {"custom_app.view_reports"}

def get_group_permissions(self, user_obj, obj=None):
return {"custom_app.edit_reports"}

Allowing Inactive Users

By default, ModelBackend and RemoteUserBackend reject users where is_active is False. If you need to allow these users to authenticate, use the "AllowAll" variants provided in django/contrib/auth/backends.py:

  • AllowAllUsersModelBackend: Inherits from ModelBackend but returns True for user_can_authenticate.
  • AllowAllUsersRemoteUserBackend: Inherits from RemoteUserBackend but returns True for user_can_authenticate.
# In settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.AllowAllUsersModelBackend',
]

Troubleshooting and Gotchas

  • Backend Order: Django iterates through AUTHENTICATION_BACKENDS in order. If a backend returns None, it tries the next one. If it returns a user, the process stops.
  • Inactive Users: If your custom backend inherits from ModelBackend, it will automatically check user.is_active. If you want to bypass this, override user_can_authenticate(self, user) to return True.
  • Remote User Middleware: When using RemoteUserBackend, ensure django.contrib.auth.middleware.RemoteUserMiddleware is added to your MIDDLEWARE setting after AuthenticationMiddleware.
  • Permissions Cache: ModelBackend caches permissions on the user object (_perm_cache). If you modify permissions in the database during a request, you may need to re-fetch the user or clear this cache to see changes.