When the Django application needs to be separated into front-end and back-end, and you want to authenticate your calls to your other platforms/services, the stateless JWT in pair with Django Rest Framework is a good choice.
But what if you want to integrate single sign-on/single log-out with the other applications which are using SAML? Moreover, your application may be Service Provider and Identity Provider at the same time.
SAML (Security Assertion Markup Language) is an open standard that allows you to perform single sign-on (SSO), namely secure log in to third-party applications using session from another application. That means you don’t need to enter your credentials to authenticate some sites (Service Providers) if you once logged in to the particular site (Identity Provider).
There are many libraries on the internet that allow us to easily integrate the Django authentication mechanism with the SAML, but all of them are based on the standard Django’s session-based authentication, but you can’t use that. Therefore, you need some adapter between JWT → SAML and vice versa SAML → JWT. Have a look at the illustration below:
This diagram shows the basic idea of the project. The circle in the middle is our Django app with some front end layer and JWT token authentication enabled. Imagine, you have hundreds of instances of our app, so users, who have an account in the external IDP (left circle), have the ability to log in to the particular instance with just one click. Also, since your app acts as an IDP, imagine that there is an external service, for example, some statistics aggregator, and when the user clicks the “statistics” tab in our site, they will be redirected and silently authenticated to a completely different site, without having to enter credentials and have the account at that statistics website.
Libraries used in the project:
- djangorestframework – Rest framework
- django-rest-framework-simplejwt – JWT Auth
- djangosaml2 – SAML Service Provider
- djangosaml2idp – SAML Identity Provider
Assuming that the SAML and metadata are configured properly, and you can perform SSO using Django and obtain a session. Let’s start from the part when the Django app acts as a Service Provider.
Django app as a Service Provider
First, you have to create view, to which the user will be redirected after SAML login:
Add this view to urls.py:
And to get redirected to this view, add this line to settings.py
What just happened? Basically, you obtained a JWT token for the authenticated (session-based) user, set it to the cookie, and deleted the session as you didn’t need it. It was easy, wasn't it?
To increase security, it’s better to store refresh and auth token in the httpOnly cookie instead of local storage (Be aware that this option closes the XSS vulnerability but opens the CSRF). At the time of writing, library Django-rest-framework-simplejwt doesn’t deliver storing tokens in the cookies, this functionality can be found in one of the pull requests. If you don’t want to use cookie storage, as an option, you can just add these tokens as URL params to the redirected URL, instead of setting them into cookies.
Django app as an Identity ProviderIn
this case, things are even easier. Let’s say you have logged in into your site, you have set JWT cookie, and you want to perform IDP-initiated login to the different site. And here you are stuck again because to perform an IDP-initiated login, you have to have a Django session (SSOInitView is using LoginRequiredMixin), but as we are using stateless tokens, from the perspective of Django views, you are not authenticated:
This time you have to do the opposite: Authenticate Django view using JWT token. To implement this in the clean and reusable way, let’s create the view decorator:
Also you can reuse this decorator to decorate the SLO view in the same way.
Conclusion
As you can see, The solution turned out to be easier than it seemed at first. And if the library doesn't provide needed functionality out of the box, it doesn't mean that you have to dig and rewrite everything. You can just do the adapter:)