Implementing Custom Authentication Backends for Third-Party Login in Django

Third-party authentication allows an application to delegate identity verification to an external provider. Upon successful verification, the provider returns a unique identifier to the application server. The server uses this identifier to locate the corresponding user record in the local database. Since the external provider handles the credential validation, the local system primarily manages the mapping between the external identity and the internal user account. Django facilitates this process through a flexible authentication backend system. Developers can define custom authentication logic by configuring the AUTHENTICATION_BACKENDS setting in the project's settings module.
AUTHENTICATION_BACKENDS = [
    'app.authentication.SocialAuthBackend',
    'django.contrib.auth.backends.ModelBackend',
]
The authentication workflow involves two primary functions: authenticate and login. The authenticate function is responsible for verifying credentials or tokens and retrieving the user object. It iterates through the configured backends until one successfully returns a user. Internally, the authentication loop loads each backend defined in the settings and calls its authenticate method. If a backend successfully validates the user, Django attaches the backend path to the user object via the backend attribute. This attribute is essential for the subsequent login process.
def authenticate(request=None, **credentials):
    for backend_path in settings.AUTHENTICATION_BACKENDS:
        backend = load_backend(backend_path)
        user = backend.authenticate(request, **credentials)
        if user is not None:
            # Annotate the user object with the backend path
            user.backend = backend_path
            return user
    return None
To implement a custom backend, for instance, one that uses a WeChat UnionID, you create a class inheriting from ModelBackend. The class must implement the authenticate method, which queries the database for the user associated with the provided external identifier.
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model

class SocialAuthBackend(ModelBackend):
    def authenticate(self, request, union_id=None, **kwargs):
        UserModel = get_user_model()
        
        if union_id is None:
            return None
            
        try:
            # Query the user by the unique external identifier
            user = UserModel.objects.get(wechat_unionid=union_id, is_active=True)
            return user
        except UserModel.DoesNotExist:
            return None

    def get_user(self, user_id):
        UserModel = get_user_model()
        try:
            return UserModel.objects.get(pk=user_id)
        except UserModel.DoesNotExist:
            return None
Once the user object is retrieved and annotated with the backend attribute, the login function is called. This function persists the user's ID and the authentication backend path in the session, ensuring the user remains authenticated across requests. Attempting to call login without first calling authenticate will result in an error, as the user object will lack the required backend attribute.
from django.contrib.auth import login

def login_view(request, user):
    # The user object must have the 'backend' attribute set by authenticate()
    if not hasattr(user, 'backend'):
        raise ValueError('User object has no backend attribute.')
        
    request.session.cycle_key()
    request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
    request.session[BACKEND_SESSION_KEY] = user.backend
    request.session[HASH_SESSION_KEY] = user.get_session_auth_hash()
    
    if hasattr(request, 'user'):
        request.user = user
This abstraction allows Django to handle the heavy lifting of session management and user persistence, while developers focus on the specific logic required to identify users via external providers.

Tags: Django python Authentication web development oauth

Posted on Sat, 01 Aug 2026 17:03:42 +0000 by Unseeeen