Performing SAML SSO using JWT in Django

Oleksandr Gorbunov
Oleksandr Gorbunov Python Developer

Read time: 13 min

Sep 17, 2020

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:

Performing SAML SSO using JWT in Django

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:

from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import RedirectView

from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.views import TokenCookieViewMixin

class Saml2JwtView(LoginRequiredMixin, TokenCookieViewMixin, RedirectView):
   # url to redirect to (front end url)
   url = ""

   def get(self, request, *args, **kwargs):
       response = super().get(request, *args, **kwargs)
       # Obtain JWT tokens for logged in user
       refresh = RefreshToken.for_user(request.user)
       # Set JWT cookies
       response = self.set_auth_cookies(
           response, {"access": str(refresh.access_token), "refresh": str(refresh)}
       # Logout from django (remove session)
       return response

Add this view to

urlpatterns = [
   path("saml2/jwt/", Saml2JwtView.as_view()),
   path("saml2/", include("djangosaml2.urls")),

And to get redirected to this view, add this line to

LOGIN_REDIRECT_URL = "/saml2/jwt/"

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:

@method_decorator(never_cache, name='dispatch')
class SSOInitView(LoginRequiredMixin, IdPHandlerViewMixin, View):
    """ View used for IDP initialized login, doesn't handle any SAML authn request

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:

from functools import wraps
from rest_framework_simplejwt.authentication import JWTAuthentication
def authenticate_by_jwt(view):
    Authenticate django views by JWT token from cookies.
    def inner(request, *args, **kwargs):
        auth = JWTAuthentication()
            user, _ = auth.authenticate(request)
        except Exception:
            if user:
                request.user = user
        return view(request, *args, **kwargs)
    return inner

And decorate the SSO view:

from djangosaml2idp.views import SSOInitView
from apps.custom_auth.decorators import authenticate_by_jwt
urlpatterns = [
   path("idp/", include("djangosaml2idp.urls")),

Also you can reuse this decorator to decorate the SLO view in the same way.


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:)

Oleksandr Gorbunov
Oleksandr Gorbunov Skilled python enthusiast with 8 years of backend experience. Over the years, he has developed, both frontend and backend applications, no challenge is too big.

Read time: 13 min

Sep 17, 2020

Schedule /a meeting/

with our specialist Michał Maj

Michał Maj
Michał Maj Business Development Manager

Read more from our brave's writers

Our mission is to be /trusted partner
to our clients/
in the field of webplatform
development & staff augmentation

Check our rank

  • development
  • clutch
Item 1 of 3

0 Professionals

0 Finished project


  • “Not afraid of doing even the most difficult tasks and proposing innovative solutions. They don’t avoid challenges, rely on new technologies, and - most importantly - always carry the projects through from the beginning to the end.”

    Adam Bogdan
    Adam Bogdan Python Developer
Item 1 of 8

/keep in touch/

We're happy to help! Leave your contact data, and we will get in touch with you within one business day.

Tell us about your needs, ask about our experience, portfolio, and even partnership programs.

Contact us

Thinking of

We are on constant lookout for new talents to join our Brave team! Don’t wait and check our latest Job openings!