Skip to main contentArrow Right

Table of Contents

In this tutorial, we will cover in detail how to integrate Descope authentication into your app using the Django Plugin.

To see the django-descope plugin in action, check out this tutorial where we create a sample text summarizer app with full authentication and RBAC.

Prerequisites

All the code for the tutorial will be in the GitHub Repository at django-descope. Instructions on the installation are on the README.md file. 

Overview

To break down the django-descope plugin, we will go over six parts:

  1. Settings

  2. URLs + Views

  3. Descope.py 

  4. Models

  5. Authentication

  6. Middleware

Settings

Upon cloning the repository, you will see django_descope, example_app, and static folder. django_descope is our main plugin, while example_app is the app that was created to demonstrate the functionality of the django_descope plugin. 

Let’s first view our settings.py file.

Code block: settings.py

  • First, we import our global settings into the django_descope app project settings.

  • The WEB_COMPONENT_SRC variable gets the attribute from our global settings file. The purpose of the WEB_COMPONENT_SRC is to allow for the functionality of Descope's HTML login widget. 

  • PROJECT_ID works similarly. We get the DESCOPE_PROJECT_ID from the settings file. 

  • IS_STAFF_ROLE and IS_SUPERUSER_ROLE also look for the attributes in the settings folder and set the default values if they’re not found.

Before we see our routes and paths, let’s see our init.py file.

URLs + Views

Now let’s make our way to the urls.py file. There's only one path, store_jwt, which calls the StoreJwt view in our views.py file.

Code block: views.py file

Starting at the top, we import our Descope dependencies using the Descope Python SDK. Here’s what happens next:

  1. The StoreJwt class view is wrapped in Django’s never_cache method decorator. The never_cache decorator tells Django to never cache the route, meaning we do not store / cache the JWT in the browser. Nothing is saved. 

  2. The post method within our StoreJwt class handles the post requests. In the post method, we get our session and refresh tokens from the cookie in the request. Every time a request is made, cookies are sent in the request. The if-statement checks if a refresh cookie exists, else we get it from the POST request. 

  3. The last if statement now sets our Django session to the cookie values. The session persists across requests, so the cookie values must match the session values. Every time a request is made, Django’s session middleware gets the incoming request before it reaches our routes in the urls.py, grabs our cookies in the request, and sets the proper session values.  

So, where is StoreJwt called?

Descope.py

Within the django_descope plugin, open the templatetags folder and open descope.py. The custom template tag renders the Descope authentication component.

The folder is named templatetags because Django recognizes this directory as the place to store the template tags.

Code block: descope_flow imports

At the top, we import our PROJECT_ID and WEB_COMPONENT_SRC variables from our settings file. Also notice that we import template from Django.

NOTE: the WEB_COMPONENT_SRC gives us access to the functionality from Descope’s web.js package

Now let’s see how we initialize our template tags.

Code block: descope_flow decorator

An instance of the template Library is created to register our module. Then we have our descope_flow function/template that’s wrapped in a simple_tag decorator. This tells Django that the descope_flow is our custom template tag and allows us to accept any number of parameters. 

Notice how within the simple_tags there is take_context=True. This allows Django to get the current context data of the HTML template. 

Now let’s look at the descope_flow method and its parameters.

Code block: descope_flow parameters

Let’s take a deeper look at our parameters. 

The parameters are context, flow_id, and success_redirect. These three arguments must be present in the descope_flow template tags when we use them in the HTML templates. So what are these?

  1. context: Since we set the take_context to true, we must have the context as the first parameter. We can now access the data of the template. 

  2. flow_id: The flow_id is unique to Descope and determines the authentication flow. Ex: sign-up, sign-up-or-in, etc. 

  3. success_redirect: This is the URL we will be redirected to upon successful login.

Now let’s look at the rest of the code. 

Code block: descope_flow template

Here’s the breakdown:

  • We have a script variable which is set to an empty string for now. There are also the CONTEXT_KEY and WEB_COMPONENT_SRC variables. 

  • We need an ID for our descope component so we have an id variable where we call the get_random_string method that is inbuilt and imported from Django. The get_random_string method generates a random alphanumeric string. 

  • The store_jwt_url variable stores our route which is in our urls.py paths. 

  • The flow string variable stores the actual Descope JavaScript widget that will be rendered in the HTML template. It contains our ID, Project ID, and Flow ID.

  • The form_data variable creates a new FormData object. The FormData object is like an HTML form but in JavaScript. It allows us to append key-value pair values so that when we fetch from the store_jwt_url, we can pass in the FormData object in the body. The session and refresh JWTs along with the CSRF token are sent in the FormData object. 

  • We then return the entire string wrapped in mark_safe, which tells Django that it’s safe to be rendered as output in HTML. 

Now let’s see how we create and store users through the user models.  

Models

Models in Django are an important part of handling data and our authentication because it allows us to modify the data schema.

Code block: Model imports

We import the django auth models and we also import our IS_STAFF_ROLE, IS_SUPERUSER_ROLE and PROJECT_ID from our global project settings file. 

Code block: models.py file

The DescopeUser model inherits the attributes from Django's default auth_models user model. The meta class within our model then allows us to add different properties to our model. Setting the proxy to true allows us to manipulate the properties of the model as well.

We then have the sync function which is the constructor of the class; it takes in the session and refresh tokens as arguments. 

In the _me method, we return cache.get_or_set where we can set key-value pairs to the cache. We have the descope_me set to our username and a lambda function that calls _descope.me with the refresh token. The _descope.me is from Descope and allows us to get user information such as name and email. 

Now let’s see where we use our DescopeUser model. 

Authentication.py

Our authentication.py file handles the validation and authentication of the user.

Code block: authentication.py file

In our authentication.py file, descope_client is imported from our init file. We also have our DescopeAuthentication Backend class. Since we’re using a custom authentication backend, the BaseBackend class is inherited, which is Django’s default way of handling permissions and user management. 

Here’s the breakdown: 

  • The super() in the init inherits the properties of the BaseBackend class. 

  • In the authenticate method, we validate the session and refresh tokens which we get from the session in the request. In the try block, we try validating the session and refresh token using the descope_client.validate_and_refresh_session method.

  • In the get_user method, we take in the validated session and refresh token as arguments and use them to get our user information. 

Now that we have the Backend and Models, let’s create a middleware.py file to add it to our entire Django app. At the very beginning of the tutorial, we added the middleware in our settings.py.

Middleware.py

The middleware ties everything together.

Code block: middleware.py file

Every middleware has two important methods – the init and the call

  • Our init must accept the get_response parameter to initialize the middleware only once when the server starts. 

  • The call method is called every time a request is made. In the call method, we use our DescopeAuthentication authenticate function to authenticate the request. 

We then check the user and call the login function that Django provides (see import statement).  

Congrats!

Whew! That was a lot to take in but hopefully you learned something new about Django authentication or Descope! 

Jim Office GIF-min

To see the Descope Django plugin in action, check out this tutorial where we build a sample app with full authentication and custom admin login.

If reading this tutorial made you curious to try Descope, sign up for a Free Forever account and join AuthTown, our open user community for developers looking to learn about authentication.