9
minutes
Mis à jour le
15/9/2019


Share this post

Django is one of the most famous Python frameworks. In this tutorial, I explain how to automatically log in users in its built-in admin interface using an LDAP.

#
Django
#
Python
#
Security

Along with LDAP and Kerberos

Django is one of the most famous Python frameworks. In this tutorial, I explain how to automatically log in users in its built-in admin interface using an LDAP.

Django provides a very useful admin interface that permits to manage users, display and modify model data. But have you ever tried to integrate a Django application in a big company?

I have and, most of the time, they already have an authentication system to authenticate their users. For example, 95% of Fortune 1000 companies use Active Directory from Microsoft to manage their users.


Therefore, the users are authenticated all over the network and there is no point adding the Django Admin login page with custom credentials. Not to mention the company security department that would probably not accept a different credential database.

Let’s see how we can automatically log in users with a concrete example!

Prerequisites

Before creating our application, you need to install python and pip.
This article gives the installation details on Mac and Linux.

We will also need pipenv to create a contained environment and avoid conflicts with other projects (more details on why using pipenv).

You can install it with this command: pip install pipenv —-user

Creation of the Django application

The first step is to create our contained pipenv environment and install Django and ldap3, a library to connect to an LDAP:


mkdir DjangoApplications && cd DjangoApplications
pipenv install Django ldap3

Now that we have a configured environment, we can create our Django application and initialize the database:


pipenv run django-admin startproject AutomaticDjangoAuthentication
cd AutomaticDjangoAuthentication
pipenv run python manage.py migrate

We have now a Django application called AutomaticDjangoAuthentication ready to be run.
Let’s launch it: pipenv run python manage.py runserver

The application is launched and you should be able to access the admin dashboard on http://localhost:8000/admin:

admin
Django Admin login page

By default, Django uses a user-oriented authentication with a login page. To connect on the admin dashboard and add other users, we need to create a user with all the permissions (super user) using the createsuperuser command:


pipenv run python manage.py createsuperuser

You should be able to connect using the user you created and access the admin dashboard.

We can now customize the default Django authentication process to authenticate using LDAP and auto-create the users in the Django database.

Auto-create users in the Django database on login

The default Django authentication uses backend classes to authenticate the users. On login, each backend authenticate method is called by priority until a user is returned or no more backends are to try.

In order to have an interesting external authentication system, I used a public LDAP server shared by forumsys. It provides several users and different groups that make LDAP tests very easy (more details here).

I created a service to check the user can authenticate in the LDAP given a username and password:


from ldap3 import Server, Connection, ALL

LDAP_URL = 'ldap.forumsys.com'

# Check user authentication in the LDAP and return his information
def get_LDAP_user(username, password):
    try:
        server = Server(LDAP_URL, get_info=ALL)
        connection = Connection(server,
                                'uid={username},dc=example,dc=com'.format(
                                    username=username),
                                password, auto_bind=True)

        connection.search('dc=example,dc=com', '({attr}={login})'.format(
            attr='uid', login=username), attributes=['cn'])

        if len(connection.response) == 0:
            return None

        return connection.response[0]
    except:
        return None

Then we can create our custom authentication backend. We check that the user can access the LDAP (or your own authentication system).
If so, we create and return a Django user with the permissions we want him to have. Here I give him only the right to access the admin dashboard (see Django permissions):


from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
from .services.ldap import get_LDAP_user


class AuthenticationBackend(ModelBackend):

    def authenticate(self, request, username=None, password=None, **kwargs):

        # Get the user information from the LDAP if he can be authenticated
        if get_LDAP_user(username, password) is None:
            return None

        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            user = User(username=username)
            user.is_staff = True
            user.save()
        return user

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

The last step before trying our new login is to override the default configuration. To do this, we add our backend in the Django app settings file AutomaticDjangoAuthentication/settings.py:


...
# Application definition

AUTHENTICATION_BACKENDS = [
   'AutomaticDjangoAuthentication.authentication_backend.AuthenticationBackend',
]

INSTALLED_APPS = [
...

Now we should be able to log in the admin dashboard with one of the LDAP users, newton, euler, einstein…, with the password password. 🎉

Automatic user login

With a single sign-on protocol implemented, like Kerberos in Active Directory, the users only have to log in once to be authenticated over the network. It is then not UX friendly to ask the users to log in another time.

To avoid that, we will use the Django middleware to automatically log in instead of displaying the login page.

Django uses several middlewares, which are defined with a priority in AutomaticDjangoAuthentication/settings.py, to ensure security, cookies, authentication and more.

Each middleware is a layer wrapping the view that can implement five hooks. Each hook is a step to manage the request, display the view and manage the response and potential exceptions (more details in the Django documentation). In the schema below you can see in which order the hooks are called in Django 1.7:
* The process_request and process_response hooks have been removed in Django 2.2 (see the details here).

httprequest
Middleware order of execution from Django doc

We will then add a new middleware after the AuthenticationMiddleware to automatically authenticate the users:


from django.contrib import auth
from django.contrib.auth.middleware import MiddlewareMixin
from django.http import HttpResponseForbidden


class AutomaticUserLoginMiddleware(MiddlewareMixin):

    def process_view(self, request, view_func, view_args, view_kwargs):
        if not AutomaticUserLoginMiddleware._is_user_authenticated(request):
            user = auth.authenticate(request)
            if user is None:
                return HttpResponseForbidden()

            request.user = user
            auth.login(request, user)

    @staticmethod
    def _is_user_authenticated(request):
        user = request.user
        return user and user.is_authenticated

It is very basic!
We try to authenticate the user if he is not already authenticated.
Then we either log in the user in the app or return a Forbidden response if the user cannot be authenticated.

As with the backend, we have to add the middleware to the Django configuration file AutomaticDjangoAuthentication/settings.py:


# Application definition

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    # Add the new middleware just after the default AuthenticationMiddleware that manages sessions and cookies
    'AutomaticDjangoAuthentication.authentication_middleware.AutomaticUserLoginMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

...

To simplify this article, I did not implement a complex Kerberos server. Instead, I used query strings to pass the user’s credentials

We must then modify our custom authentication backend to get the query strings from the request:


import ...

class AuthenticationBackend(ModelBackend):

    def authenticate(self, request, username=None, password=None, **kwargs):

        # Get credentials from the query strings
        username = request.GET.get('username')
        password = request.GET.get('password')

        # Check that the user can authenticate in the LDAP using its username and password
        if get_LDAP_user(username, password) is None:
            return None

        ...

Now you can try to access the admin dashboard

And receive a 403 Forbidden! 🤯 (if you’re already logged in, log out and retry)

That’s because you need to send your credentials as query strings to be authenticated.

Let’s try again with http://localhost:8000/admin/?username=euler&password=password … Magic, it works 🎉

We can now authenticate each user without going through the login page.

How to implement it with Active Directory

A concrete implementation would not use query strings. Instead, we would use the external authentication system to get the users’ information. For example, I integrated this solution with Active Directory.

Active Directory is a Microsoft solution that uses the LDAP protocol and the Kerberos single sign-on protocol:

  • LDAP permits accessing and storing information on the users.
  • Kerberos permits to securely authenticate the users through the network using encrypted tokens (more explanations in this video).

If you want to plug with an Active Directory Kerberos, you can follow this tutorial. Once you configured Apache, you will be able to access the user’s username in the request REMOTE_USER header.

With the username, you will then be able to fetch the user information from the Active Directory LDAP and create the user in the Django database.


Conclusion

In this article, I have shown how to use a custom authentication to automatically log in Django admin. With this strategy, you avoid the implementation of a new authentication protocol and a complete copy of your existing database.

Furthermore, you can easily customize it with your own authentication system. You can also personalize the user experience by setting the user’s permissions using LDAP groups.

You can see, clone, fork the whole project on Github: AutomaticDjangoAuthentication, ybri 👨‍🎓.

I hope this helped, feel free to comment 😎