SSO(Single Sign-on) integration with Django Project using OKTA SSO

Syed Gufran Ahmad
TrialX Inc.
Published in
7 min readOct 4, 2021

--

PROBLEM STATEMENT: The task was to integrate Okta SSO with the Django project but there was no proper documentation or support available for the Django projects. Several issues I faced like empty dictionary coming inside assertions and had to tweak the local setting according to our need. I tried different approaches and different packages. Tried openid with Django auth worked fine but openid doesn’t give us flexibility and did not satisfy our criteria.

SAML

SAML stands for Security Assertion Markup Language. It is an XML-based open-standard for transferring identity data between two parties: an identity provider (IDP) and a service provider (SP).

Identity Provider — Performs authentication and passes the user’s identity and authorization level to the service provider.

Service Provider — Trusts the identity provider and authorizes the given user to access the requested resource.

Benefits of SAML Authentication

  • Improved User Experience — Users only need to sign in one time to access multiple service providers. This allows for a faster authentication process and less expectation of the user to remember multiple login credentials for every application. In the example above, that user could have clicked on any of the other icons in their dashboard and been promptly logged in without ever having to enter more credentials!
  • Increased Security — SAML provides a single point of authentication, which happens at a secure identity provider. Then, SAML transfers the identity information to the service providers. This form of authentication ensures that credentials are only sent to the IDP directly.
  • Loose Coupling of Directories — SAML doesn’t require user information to be maintained and synchronized between directories.
  • Reduced Costs for Service Providers — With SAML, you don’t have to maintain account information across multiple services. The identity provider bears this burden.

In our case:

  • Our identity provider is Okta
  • Our service provider is a fictional service, Sample-Project

Note: The identity provider could be any identity management platform.

Now, a user is trying to gain access to Sample-Project using SAML authentication.

This is the process flow:

  • The user tries to log in to Sample-Project from a browser.
  • Sample-Project responds by generating a SAML request.
  • The browser redirects the user to an SSO URL, Okta
  • Okta parses the SAML request and authenticates the user. This could be with username and password or even social login. If the user is already authenticated on Okta, this step will be skipped. Once the user is authenticated, Okta generates a SAML response.
  • Okta returns the encoded SAML response to the browser.
  • The browser sends the SAML response to Sample-Project for verification.
  • If the verification is successful, the user will be logged in to Sample-Project and granted access to the resources that they are authorized to view/modify.

Setting up SAML Application in OKTA (IDP side)

Steps:

  1. On developer.okta Click on Applications

2. After that click Create Application and click on SAML 2.0

3. Write the name of the app and add logo if needed then click on next

4. This setting is important and it should match with local setting

Single sign on URL should be like this domain/saml2/acs/ .

Audience URI is ENTITY_ID in local settings.

Attribute statements which OKTA says are optional but are most important. All the user data comes from user attributes only.

In the local setting :

‘ATTRIBUTES_MAP’: {

‘email’: ‘Email’,

‘username’: ‘UserName’,

‘first_name’: ‘FirstName’,

‘last_name’: ‘LastName’ }

5. In group attribute statements add a variable which will save the group information of the user.

6. Click on Identity provider metadata and copy the link to ‘METADATA_AUTO_CONF_URL’

You can find name_id_format from this file.

Setting up SAML in Django (SP side)

In our Django application we have used django-saml2-auth. Lets deep dive into setting this up.

pip install django_saml2_auth

xmlsec is also required by pysaml2 as we have to parse the XML response(assertion) this is important that your system has xmlsec2 :

apt-get install xmlsec1

Then after installing all the dependencies.

Go to root url.py and add following urls.

import django_saml2_auth.viewspath(‘saml2_auth/’, include(‘django_saml2_auth.urls’)),
# The following line will rep
# If you want to specific the after-login-redirect-URL, use parameter “?next=/the/path/you/want”
# with this view.
path(‘accounts/login/’, django_saml2_auth.views.signin),
# This will redirect to okta
path(‘admin/login/’, django_saml2_auth.views.signin),

Add Django saml2 auth in installed apps

INSTALLED_APPS = [
‘…’,
‘django_saml2_auth’,
]

Most important Thing is setting up the settings.py

Django saml2 auth gives the following settings but you have to carefully while configuring it with your IDP. This is the default settings. You can change it according to your needs.

Sample settings

SAML2_AUTH = {

‘METADATA_AUTO_CONF_URL’: ‘https://dev-07324335.okta.com/app/*********/sso/saml/metadata',
#
‘DEFAULT_NEXT_URL’: ‘/worklist/’,
# Custom target redirect URL after the user get logged in.Default to /admin if not set.# This setting will be overwritten if you have parameter ?next= specificed in the login URL.
‘CREATE_USER’: ‘False’,
# Create a new Django user when a new user logs in. In our case we do# not create new.# ‘NEW_USER_PROFILE’: {
# ‘USER_GROUPS’: [], # The default group name when a new user logs in
# ‘ACTIVE_STATUS’: True, # The default active status for new users
# ‘STAFF_STATUS’: True, # The staff status for new users
# ‘SUPERUSER_STATUS’: False, # The superuser status for new users
# },
# the value of keys below should be same as on the okta setting
‘ATTRIBUTES_MAP’: {
# Change Email/UserName/FirstName/LastName to corresponding SAML2 userprofile attributes.
‘email’: ‘Email’,
‘username’: ‘UserName’,
‘first_name’: ‘FirstName’,
‘last_name’: ‘LastName’,

},
# triggers can be used to perform various task. Before_login is triggered right when the saml# response comes form idp
‘TRIGGER’: {
# # ‘CREATE_USER’: ‘path.to.your.new.user.hook.method’,
‘BEFORE_LOGIN’: ‘Sample-Project_dashboard.views.my_view’,
},
‘ASSERTION_URL’: ‘http://localhost:8000’, # Custom URL to validate incoming SAML requests against
‘ENTITY_ID’: ‘http://localhost:8000/saml2_auth/acs/', # Populates the Issuer element in authn request
‘NAME_ID_FORMAT’: “urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress”, # Sets the Format property of authn NameIDPolicy element
# ‘USE_JWT’: False, # Set this to True if you are running a Single Page Application (SPA) with Django Rest Framework (DRF), and are using JWT authentication to authorize client users
# ‘FRONTEND_URL’: ‘http://localhost:8000', # Redirect URL for the client if you are using JWT auth with DRF. See explanation below
}

‘METADATA_AUTO_CONF_URL’ : Auto SAML2 metadata configuration URL. In okta you can find the link easily.

CREATE_USER Determines if a new Django user should be created for new users.

NEW_USER_PROFILE Default settings for newly created users

ATTRIBUTES_MAP Mapping of Django user attributes to SAML2 user attributes

TRIGGER Hooks to trigger additional actions during user login and creation flows. These TRIGGER hooks are strings containing a dotted module name which point to a method to be called. The referenced method should accept a single argument which is a dictionary of attributes and values sent by the identity provider, representing the user’s identity.

TRIGGER.BEFORE_LOGIN A method to be called when an existing user logs in. This method will be called before the user is logged in and after user attributes are returned by the SAML2 identity provider. This method should accept ONE parameter of user dict.

ASSERTION_URL A URL to validate incoming SAML responses against. By default, django-saml2-auth will validate the SAML response’s Service Provider address against the actual HTTP request’s host and scheme. If this value is set, it will validate against ASSERTION_URL instead — perfect for when Django running behind a reverse proxy.

ENTITY_ID The optional entity ID string to be passed in the ‘Issuer’ element of authentication request, if required by the IDP.

NAME_ID_FORMAT Set to the string ‘None’, to exclude sending the ‘Format’ property of the ‘NameIDPolicy’ element in authn requests. Default value if not specified is ‘urn:oasis:names:tc:SAML:2.0:nameid-format:transient’. NAME_ID_FORMAT is present in metadata you have to look into that

DEFAULT_NEXT_URL you have to specify the next url as it would be stuck in an infinite loop

Beware: Don’t add forward slash to Assertion URL. Django-saml2-auth appends that automatically.

Giving a shout out to my colleague Owais who helped me with this problem.

--

--