Blog

Django Third-Party Authentication

Problem : I want to authenticate users form a third-party auth module (can be rest based), without writing custom backends or installing any module (like social-auth).

First of all we need to understand how our third party auth module works or at-least what response it gives/posts on successful login.

Say, to login a user we redirect our user to a third-party auth url which in turn posts some parameters to our django app (can be encrypted user attributes for eg.) on successful login.

This can be easily achieved in Django just by writing some logic in views.py instead of writing a whole new auth-backend to handle it.

We will need the below to make this work:

  1. Third party auth url where we need to redirect our users for authentication. (Can be any SSO/MFA auth url which your org might be providing)
  2. The exact post parameters the third party module returns on successful login.

Note – The third party auth module should trigger a post call to our django app once authentication is successful along with necessary parameters. This is something that needs to be configured at the auth module end.

Below are the code snippets:

views.py

from django.contrib.auth.import login
from django.contrib.auth.models import User
from django.shortcuts import resolve_url, render
from django.views.decorators.csrf import csrf_import
from django.views.decorators.cache import never_cache
from django.http.response import HttpResponseRedirect

@csrf_exempt
@never_cache
def custom_login(request, next_page = None):
    return_values = request.POST.get('ReturnValues')
    next_page = request.GET.get('next_page')

    if return_values is not None:
        # Decrypt and parse response and add to dict
        parameters = decrypt_duo_response(settings.DUO_SECRET_KEY, return_values)

        # 'user_attributes' dict will have all user parameters in key-value pair
        user_attributes = {}

        for param in parameters:
            item = param.split('=')
            user_attributes[item[0]] = item[1]

        # Get user_id and email_id from duo response
        user_id = user_attributes['userid']
        email_id = user_attributes['mail']

        if user_id is not None:
            try:
                user = User.objects.get(username=user_id)
            except:
                # Create user if not present
                user, created = User.objects.update_or_create(username=user_id, password='', email=email_id)
               
            # Login user (No authentication required as it is handled by third-party auth module)
            login(request, user)

    # Redirect to previous page post login if next_page is defined
    if next_page is not None:
        next_page = resolve_url(next_page)
        return HttpResponseRedirect(next_page)
 
    return render(request, 'index.html')

Assumptions:

  1. The third-party auth module does a post call to our django app on successful user authentication with ReturnValues as a parameter which contains user attributes (userid, mail) in encrypted format.    
  2. decrypt_duo_response is a function to decrypt the ReturnValues and get the user attributes

urls.py

from . import views
from django.contrib.auth.views import logout
from django.conf.urls import url

urlpatterns = [
    url(r'^$', views.custom_login, name='custom_login'),
    url(r'^logout/$', logout, name='auth_logout'),
]

index.html

{% if user.is_authenticated %}
    Welcome, <strong>{% firstof user.get_full_name user %}</strong><br/>
    <a href="{% url 'auth_logout' %}?next={{ request.get_full_path }}">Logout</a>
{% else %}
    <a href="<your third party auth url here>?next_page={{ request.get_full_path }}">Login</a>
{% endif %}

settings.py

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
)

MIDDLEWARE = [
    # 'debug_toolbar.middleware.DebugToolbarMiddleware',    
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    # Uncomment the next line for simple clickjacking protection:
    # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
]
Design a site like this with WordPress.com
Get started