Django’s built-in authentication is great, but sometimes you need to plug in a completely different system.

Let’s say you want to authenticate users against an external LDAP server, or maybe a custom database table that doesn’t map directly to Django’s User model. This is where custom authentication backends shine.

Here’s a basic setup for a custom backend that authenticates against a simple dictionary of users. This isn’t for production, but it illustrates the core concepts.

# myapp/auth_backends.py

from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.models import User

class MyCustomBackend(BaseBackend):

    def authenticate(self, request, username=None, password=None, **kwargs):
        # This is where the magic happens.
        # You'll check your external source (LDAP, custom DB, etc.)
        # against the provided username and password.

        # For this example, we'll use a hardcoded dictionary
        valid_users = {
            "alice": "wonderland",
            "bob": "builder",
        }

        if username in valid_users and valid_users[username] == password:
            try:
                # If authentication succeeds, return the user object.
                # Here, we're mapping to Django's User model.
                # In a real scenario, you might fetch or create a user.
                user = User.objects.get(username=username)
                return user
            except User.DoesNotExist:
                # If the user doesn't exist in Django's DB,
                # you might create them on the fly.
                # For simplicity, we'll just return None here.
                return None
        return None

    def get_user(self, user_id):
        # This method is used by Django to retrieve a user object
        # given a user ID (e.g., from a session).
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

To make Django use this backend, you need to add it to your settings.py:

# settings.py

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',  # Keep the default if you want to use Django's User model too
    'myapp.auth_backends.MyCustomBackend',
]

Now, when a user tries to log in using Django’s login view or authenticate() function, Django will iterate through your AUTHENTICATION_BACKENDS list. It calls authenticate() on each backend until one of them returns a User object. If no backend successfully authenticates the user, authenticate() returns None.

The get_user() method is crucial for session management. Once a user is authenticated, Django stores their user ID in the session. When the user returns to the site, Django uses the ID and the get_user() method of your backend (and others) to retrieve the full user object.

This example uses a simple dictionary, but imagine replacing that with an LDAP query or a call to a microservice. The authenticate method is your gateway to any authentication source. You’ll fetch credentials, validate them against your external system, and if successful, return the corresponding Django User object. If the user doesn’t exist in Django’s primary User model, you might need to create them programmatically within the authenticate method itself, mapping attributes from your external source to the Django User model.

The most surprising part is how ModelBackend and your custom backend work together: Django calls authenticate on each backend in the AUTHENTICATION_BACKENDS list, in order, until one returns a user. If you return None from your custom authenticate, Django simply moves on to the next backend. This allows for graceful fallback or combination of authentication methods.

The next hurdle is handling user creation and updates when authenticating against an external system that might not perfectly align with Django’s User model attributes.

Want structured learning?

Take the full Django course →