Using Custom Authentication Backends in Django
Table of Contents
- Introduction
- Django’s default authentication
- Authentication backends
- Permissions
- Extending and customizing user models
- Conclusion
Introduction
If you’re working in an organization with an established product line that serves live users, supporting a new site with Django probably means integrating with an existing authentication system. Many organizations use widely-adopted authentication systems provided by services like Google, Facebook, or GitHub. A few Python packages provide authentication integration with these services, but most of them expect you to be handling the final user accounts on with Django. What happens when you need to work with user accounts that live in another system altogether?
In this article, you’ll see the interface that Django exposes for authenticating to an external system. By the end, you should understand the pieces involved in mapping an external system’s information to Django’s native User
objects in order to work with them on your own site.
Django’s default authentication
In the Django User Authentication System, we covered the basics of how default authentication works in Django. Ultimately, you can interact with User
objects and understand if a user is_authenticated
or not. Using the default authentication system, you can make use of many of Django’s built-in features like its login and logout views and password reset workflow.
When working with an external authentication system, you have to manage these pieces yourself. Some of them may not make sense to you depending on how your authentication system works.
Authentication backends
As with many of Django’s systems, authentication is modeled as a plugin system. Django will try to authenticate users through a series of authentication backends. The default backend checks a user’s username and password against all the existing User
objects in the database to authenticate them. The AUTHENTICATION_BACKENDS
setting is your entrypoint to intercept this workflow and point Django to your external system.
An authentication backend is a class that, minimally, implements two methods:
get_user(user_id)
— auser_id
can be whatever unique identifier your external system uses to distinguish users, andget_user
returns either a user object matching the givenuser_id
orNone
.authenticate(request, **credentials)
— therequest
is the current HTTP request, and the credentials keyword arguments are whatever credentials your external system needs to check if a user should be authenticated or not. This is often a username and password, but it could be an API token or some other scheme.authenticate
returns an authenticatedUser
object orNone
.
Inside your authentication backend’s authenticate method, you can pass along the credentials to your external system via a REST API or another common authentication scheme like LDAP or SAML.
Using the wonderful Yes or No? API, you could build an authentication backend that authenticates a user occasionally if the API permits:
import requests
class FickleAuthBackend:
def authenticate(self, request, username):
response = requests.get(
'https://yesno.wtf/api/'
).json()
return User(username=username, password='') if response['answer'] == 'yes' else None
While authenticate
can return a user object or None
, it may also return an AnonymousUser
object, or raise PermissionDenied
to explicitly halt any further authentication checks. This allows for a variety of ways to proceed, and anonymous users may still have certain permissions. You’ll want to account for that in your middleware and views.
If the external user service provides additional information about the user, get_user
might be a good place to grab some of that data. You can add attributes to the user object in authenticate
before you return it if you’d like, but be careful of how many attributes you add dynamically.
Permissions
I also covered Django’s permission scheme in The Django User Authentication System: when given a user, you can inquire about their permissions generally or against specific objects using the has_perm
method. Custom authentication backends can override permission checking methods and Django will check against those first before falling back to its default checks. This allows you to make queries to your external system about permissions in addition to authentication:
…
def has_perm(self, user_obj, perm, obj=None):
response = requests.get(
'https://yesno.wtf/api/'
).json()
return response['answer'] == 'yes'
has_perm
can also raise PermissionDenied
to halt further authorization checks, similar to authenticate
.
Extending and customizing user models
If you’d like to fully integrate Django with your external system, there’s much more you can do by way of the User
model. I won’t dive too deeply into that portion of Django, but it is fully laid out in Customizing authentication in Django.
This kind of customization lets you use the built-in behaviors of a user while adding your own information and behaviors through proxy models, or one-to-one mappings to custom models. For example, you can pull in information from your external system, creating a new user in your Django database each time a new user authenticates for the first time.
If you’re working in an ecosystem with a mature external user management service, I recommend consistently keeping user-related data and behavior there instead of fragmenting it into your Django code.
For internal tools or tools with a separate audience and differing information storage needs, though, custom user models may work well for you.
Conclusion
Django provides a flexible and extensible way to customize user authentication, whether you want to let another system do most of the user account management or want to do it yourself. Using custom authentication backends, you can easily integrate with external systems using almost anything you do in Python. These integrations give you the power to customize permissions checking as well, opening the floor for many possibilities all while working within Django’s native interfaces.
Are you new to software development or Python? Do you want to understand the core principles and practices developers in the industry follow today? You might like my upcoming book!