back arrowBack to Blog

Developers

Add Descope Authentication to Your Middleware

Descope middleware blog thumbnail

Descope is a drag-and-drop CIAM platform that helps developers easily add customer authentication, authorization, and identity management capabilities to their apps. Using customizable visual workflows, you can create and modify the entire user journey – from login screens and auth logic to MFA and SSO. During the authentication process, your end users will get an access token that will let them access the data they have on your app.

The data and actions that the user can access should undergo some sort of authentication or authorization process to insure that the application is working properly in terms of security and user experience.

Most of the frontend and backend frameworks nowadays support a concept called “middleware” or “interceptors”, that lets you implement a pipeline inside your code to intercept requests and add an authentication and authorization step to every call that is being received from the user. This concept helps automate that process in the application building process, and also integrates seamlessly with any framework.

In this blog, we will briefly introduce the concept of middleware and see how to add Descope authentication to your middleware, whether you are building your own or using out-of-the-box options.

Middleware 101

As previously stated, middleware is a “pipeline” that you add to your code that lets you control some components that are related to requests from your user. By handling the connectivity between different software components, middleware frees up developer time to focus more on business logic and features.

Most of the frontend and backend frameworks let you add a function that is like a callback that the framework will invoke for each request that comes through. It may be before the request reaches the handler code (most commonly referred to as “controller”) that will decipher the body of the request to the right code flow. It also may be after the request has been processed inside the code and is on its way back to the user.

That function lets you check for any information that might be related to the headers of the request, and specifically, for authorization headers.

Here’s an example of an authorization middleware inside a Node.js application:

var express = require(‘express’)
var app = express()

var validateJwt = function(token) {
// validation logic
return true;
} 

var authMiddleware = function (req, res, next) {
req.isValidSession = validateJwt(req.headers.authorization)
next()
}

app.use(authMiddleware)

app.get (‘/’, function (req, res) {
var responseText = ‘Hello World!<br>’
responseText += ‘<small>Session Valid:‘+req.isValidSession + ‘</small>’
res.send (responseText)
})
app.listen(3000)

The above example demonstrates how you can implement simple authentication logic inside a middleware in the app.

De<middleware>scope

Once we understand the concept of middleware, we can use it to our advantage to implement authentication and authorization functionality while utilizing the benefits of middleware.

Using Descope SDKs, we can utilize the functionality of checking the validity of the access token and / or refresh token inside a middleware to save time doing so for each API request or route in our application.

In the example below, we add Descope inside a middleware class and initialize it with the project ID.

[authentication.js]

import DescopeClient from '@descope/node-sdk';

export class DescopeMiddleware {
	
constructor(projectId) {
this.descopeClient = DescopeClient({ projectId: projectId});
}

validate(token) {
try {
  const authInfo = await descopeSdk.validateSession(sessionToken);
  console.log("Successfully validated user session:");
  console.log(authInfo);
} catch (error) {
  console.log ("Could not validate user session " + error);
}
}
}

[app.js]

import express from 'express';
import DescopeMiddleware from './authentication.js';

const app = express();
const authMiddleware = new DescopeMiddleware('__PROJECT_ID__');
app.use(authMiddleware.validate());
app.get('/', (req, res) => { res.send('main'); });
app.listen(3000, () => { 
console.log("Express Running port 3000") });

Decorators in Python

Python decorators serve as advanced enhancements for functions. They encapsulate additional functionality around existing functions, providing a means to augment their behavior seamlessly and efficiently.

In the example below, the “my_decorator” function accepts a function instance as a parameter and creates an entire “wrapper” function inside it. The “wrapper” function prints something before executing “func”, which is the function that was received initially. After executing “func” it prints something else.

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

In order to use the decorator, we just need to annotate any function with the “@decorator” and the decorator will apply that function.

In this example, the output will be:

> Something is happening before the function is called.
> Hello!
> Something is happening after the function is called.

This functionality will let us use Descope as “middleware” in an API based backend (FastAPI, Flask, Django...).

Descope Python decorator 

So, of course you could create your own authentication decorator in the framework you use. But we at Descope understand that decorators are a powerful tool and created one for you! You can use this decorator on any framework’s route:

# This needs authentication
@APP.route("/private")
@descope_validate_auth(
    descope_client
)  # Can add permissions=["Perm 1"], roles=["Role 1"], tenant="t1" conditions
def private():
    return Response("<h1>Restricted page, authentication needed.</h1>")

This decorator lets you check specific permissions and roles, including in specific tenants. If the user’s JWT does not match the terms provided, the response will be 401 not authorized.

Descope also provides the ability to use other decorators, and not just for checking roles and permissions. The example below shows how to trigger a Descope Flow using a decorator.

@APP.route("/login", methods=["GET"])
@descope_full_login(
    project_id=PROJECT_ID,
    flow_id="sign-up-or-in",
    success_redirect_url="http://dev.localhost:9010/private",
)
def login():
    # Nothing to do! this is the MAGIC!
    pass

The example below shows how to logout, aka delete the user’s cookie, when they hit the logout route.

@APP.route("/logout")
@descope_logout(descope_client)
def logout():
    return Response("<h1>Goodbye, logged out.</h1>")

Conclusion

Descope offers you the ability to authenticate and authorize your user base. In the age of automation and the growing popularity of development frameworks, using middleware is an increasingly common choice. Whether you build your own middleware or use our out-of-the-box options, we encourage you to build your application the right way.

Sign up for a Free Forever Descope account to get started with adding auth functionalities to your middleware. If you have questions about our platform, book time with our auth experts.