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:
- 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)
- 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:
- 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.
- 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', ]