DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Accelerating HCM Cloud Implementation With RPA
  • API Implementation on AWS Serverless Architecture
  • Building and Integrating REST APIs With AWS RDS Databases: A Node.js Example
  • Implementation Best Practices: Microservice API With Spring Boot

Trending

  • Key Considerations in Cross-Model Migration
  • Transforming AI-Driven Data Analytics with DeepSeek: A New Era of Intelligent Insights
  • Debugging Core Dump Files on Linux - A Detailed Guide
  • Automatic Code Transformation With OpenRewrite
  1. DZone
  2. Data Engineering
  3. Databases
  4. How to Customise SuperTokens APIs

How to Customise SuperTokens APIs

Any auth solution must provide the ability to customise their APIs. In this blog we discuss how to customise the auth APIs provided by SuperTokens using its “Override” feature"

By 
Advait Ruia user avatar
Advait Ruia
·
Mar. 19, 22 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
4.8K Views

Join the DZone community and get the full member experience.

Join For Free

Auth requirements are quite varied. Therefore any auth solution must provide the ability to customise their APIs. Each solution uses its own terminology for this feature:

  • Keycloak uses “Implementing an SPI”

  • Auth0 calls these “Auth0 actions”

  • Firebase calls these “Extend using cloud functions”

  • AWS Cognito uses the term “Lambda triggers & Custom challenge”

  • SuperTokens calls this feature “Overrides”

These features allow you to change the default behaviour of the auth APIs by:

  • Creating an HTTP webhook in your API layer which is then called by the auth provider

  • Uploading code to the auth provider (for example JS code for Auth0, or Java interface implementation for Keycloak) which run at specific points in the API’s logic.

  • Uploading code to the auth provider which can replace the existing API’s logic entirely (as opposed to just running at specific points in the API)

How powerful these solutions are, depends on:

  • The auth provider providing the right “hook points” in their API, where your custom code can run.

  • Your familiarity with the programming language you need to use to write the custom code.

  • How easily your custom code can integrate with your existing infrastructure code (for example database connection setup), and how easily it can be maintained (for example, you may need to maintain the custom code snippets in your git repo as well as on the auth provider’s dashboard).

In this article, we will be talking about how to customise the auth APIs provided by SuperTokens using its “Override” feature. In order to understand that, we must first understand how SuperTokens fits within an app.

SuperTokens’ Architecture


Here we can see the architecture diagram for the self-hosted version of SuperTokens. On the left, we have the client (browser, mobile app) which talks to your APIs. Your API layer has your application APIs (shown as /api1/, /api2/, ..) and also APIs automatically exposed by the SuperTokens backend SDKs via our middleware function (shown as /auth/signin, /auth/signout, ...).

The SuperTokens APIs talk to the SuperTokens Core (HTTP microservice) to persist data in the database. Your application APIs can also talk to the core if needed.

Keeping this in mind, the concept of override is that you can change the behaviour of the SuperTokens APIs (exposed to the frontend) as per your requirements (all within your API layer, in the language you already use). Think of this being similar to overrides in object-oriented programming where you have an original implementation, and you can modify its behaviour by overriding the existing functions. You can even call the “super” class implementation of that function in your override function. 

Overriding in SuperTokens


Whilst this article is focused on a NodeJS backend, the concepts here are very similar to all the other backend SDKs provided by SuperTokens.

To override the default implementation, we must use the override config value when calling supertokens.init. Each recipe inside the recipeList, accepts an override config that can be used to change the behaviour of that recipe:

JavaScript
 
supertokens.init({
   appInfo: {...},
   recipeList: [
       EmailPassword.init({
           override: {
               functions: (originalImplementation) => {
                   return originalImplementation
               },
               apis: (originalImplementation) => {
                   return originalImplementation
               }
           }
       }),
       Session.init({...})
   ]
})


In the code above, we have defined the skeleton code for how to override the behaviour of the EmailPassword recipe. A very similar skeleton is applicable for overriding the Session (or any other) recipe.

There are two types of override:

  • APIs: These govern how the APIs exposed by that recipe behave. For EmailPassword, these are the sign in / sign up, reset password and email verification APIs. By overriding these, you can change how these APIs behave when they are called from the frontend.

  • Functions: These are the functions that govern how the recipe itself behaves. They can be called by you manually in your APIs and they are also used in the APIs we expose to the frontend. By default, they query the SuperTokens core and return its response.

  • The difference between the two are:

    • API functions have access to the request and response objects depending on the web framework being used

    • API functions can call several recipe functions or even call functions from multiple recipes. For example, the signInPOST API function in the EmailPassword recipe, calls the signIn recipe function from EmailPassword recipe and the createNewSession function from the Session recipe.

You always want to try and use the override.functions config since that will make the minimum change to the default behaviour. If the inputs to those functions don’t suffice for your use case, then you should override the APIs.

In both these types of overrides, they accept the originalImplementation variable as an input and the return is an object that has the same type as the originalImplementaion.

For EmailPassword recipe, the originalImplementation object contains:

  • For function override (see full type def here):

    • signIn

    • signUp

    • updateEmailOrPassword

    • createResetPasswordToken

    • resetPasswordUsingToken

    • getUserByEmail

    • getUserById

  • For API override (see full type def here)

    • signInPOST

    • signUpPOST

    • emailExistsGET

    • generatePasswordResetTokenPOST

    • passwordResetPOST

For Session recipe, the originalImplementation object contains:

  • For function override (See full type def here)

    • createNewSession

    • getAccessTokenLifeTimeMS

    • getAllSessionHandlesForUser

    • getRefreshTokenLifeTimeMS

    • getSession

    • getSessionInformation

    • refreshSession

    • revokeAllSessionsForUser

    • revokeMultipleSessions

    • revokeSession

    • updateAccessTokenPayload

    • updateSessionData

  • For API override (see full type def here)

    • refreshPOST

    • signOutPOST

In the code snippet above, we are not modifying the default behaviour of any of these functions since we are simply returning the originalImplementation object. If you want to modify the signIn function, then we can do so like this:

```

JavaScript
 
EmailPassword.init({
   override: {
       functions: (originalImplementation) => {
           return {
               ...originalImplementation,
               signIn: async function (input) {
                   let email = input.email;
                   let password = input.password;
 
                   // TODO: some custom logic before
 
                   let originalResponse = await originalImplementation.signIn(input);
 
                   // TODO: some custom logic after
 
                   return originalResponse;
               }
           }
       },
       apis: (originalImplementation) => {
           return {
               ...originalImplementation
           }
       }
   }
})
```

In the above code snippet, we have provided a custom signIn function that uses the original implementation’s signIn function. As marked above (in TODO comments), we can write custom logic before or after calling the original implementation.

If we wish, we can even avoid calling the original implementation entirely and define our own logic. For example, if we wanted to use a different password hashing algorithm that is not supported by SuperTokens.

Special Cases for Modifying APIs
Sometimes, you may want to modify the default API to:

Access the request object, for example, to read the origin header

Send a custom reply to your frontend UI that deviates from our predefined output types

Disable an API we have provided entirely. For example, you may want to do this if you do not want users to self sign up in your application.

The function signature of all the API interface functions has an options parameter that contains the original request and response objects. You can read from the request object and write to the response object as you normally would in your own APIs.

For example, if you want to read the request’s origin header during the sign up API, you can do it as follows:

```

EmailPassword.init({
   override: {
       apis: (originalImplementation) => {
           return {
               ...originalImplementation,
               signUpPOST: async function (input) {
                   if (originalImplementation.signUpPOST === undefined) {
                       throw new Error("should never come here.");
                   }
 
                   let origin = input.options.req.getHeaderValue("origin");
 
                   // TODO: do something with the origin
 
                   return originalImplementation.signUpPOST(input);
               }
 
           }
       }
   }
})

```

As you can see above, we can access the request object using input.options.req.

Likewise, if we want to send a custom response to the frontend, we can access the response object via input.options.res.

Finally, to disable an API that we provide, you can set it to undefined as follows:

```

JavaScript
 
EmailPassword.init({
   override: {
       apis: (originalImplementation) => {
           return {
               ...originalImplementation,
               signUpPOST: undefined
 
           }
       }
   }
})

```

This will disable the sign up API, and requests to /auth/signup will be passed along to your APIs or yield a 404.

Advantages of the Override Method

  • Make modifications in the language and web framework you are already familiar with, within your own backend layer. This allows you to reuse your code for connecting to your database, sending a custom reply, logging requests and responses, sending analytics events, handling errors etc. Furthermore, since you already know the language and the web framework, the learning curve is minimal.

  • Easier maintainability: Some auth providers require you to upload code onto their dashboard. This means you need to make sure that changes to that version of the code in your git repo are reflected on the auth provider’s dashboard (and vice versa). This can be a headache, especially with larger team sizes. With SuperTokens, all the mods you will ever need will live in the same codebase as all of your other backend code - SuperTokens is just another library you use.

  • Flexibility in customisations: If you noticed, we don’t provide any special “hook” points (like pre-sign up or post sign up callbacks). You simply create your own implementation based on the original implementation. In fact, you can even copy the original implementation’s code and paste that in your own implementation if required. Hence, your modifications can be at any point in the API logic. In turn, this provides maximum flexibility.

  • Flexibility in integrations: Auth APIs have to interact with several other services like those used for sending emails or SMSs, spam/anomaly detection or rate-limiting. Since the APIs are all within your own backend layer, you can use any such service(s) in the APIs we provide - you are not limited to the ones we (eventually will) support.

Conclusion

In the post, we saw how we can use the Overrides feature to modify the behaviour of any of the auth APIs exposed by SuperTokens. Whilst this blog focuses on NodeJS, the concept is the same in all the other SDKs we provide.

API Database connection Implementation

Published at DZone with permission of Advait Ruia. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Accelerating HCM Cloud Implementation With RPA
  • API Implementation on AWS Serverless Architecture
  • Building and Integrating REST APIs With AWS RDS Databases: A Node.js Example
  • Implementation Best Practices: Microservice API With Spring Boot

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!