Skip to main content
Paid Feature

This is a paid feature.

For self hosted users, Sign up to get a license key and follow the instructions sent to you by email. Using the dev license key is free. We only start charging you once you enable the feature in production using the provided production license key.

For managed service users, you can click on the "enable paid features" button on our dashboard, and follow the steps from there on. Once enabled, this feature is free on the provided development environment.

Migration from an older SuperTokens SDK to a newer one

This section is applicable to those who want to enable MFA for the first time and are already using SuperTokens in production. The following are the steps you need to take:

  • Make sure that you have updated your SuperTokens core, backend SDK and frontend SDK that supports MFA (you can find these versions in the files in their respective github repository). Make sure to read the migration guide for each of the breaking version upgrades. You should aim to get your existing feature set working with the new version of SuperTokens before you enable MFA.
  • Follow the backend and frontend setup we have in this guide along with the factor specific setup (TOTP or email / sms OTP).

If enabling MFA for all users at once#

If you have enabled MFA for all users, then existing logged in users will asked to complete the secondary factor as soon as they visit your app / website once you have pushed the changes to production. This happens because their existing session is modified to add the MFA claim into it, however, the v value in the claim will be false since they have not completed MFA yet. This would fail the validators on the frontend which would redirect the user to the MFA login screen.

If you have clients like mobile apps, which take time to upgrade across your entire user base, then you may want to remove the global MFA validator on the backend and make it run only when you know that the request is coming from an updated client:

import Session from "supertokens-node/recipe/session";
import { ClaimValidationResult } from "supertokens-node/recipe/session/types";
import SuperTokens, { getRequestFromUserContext } from "supertokens-node";
import MultiFactorAuth from "supertokens-node/recipe/multifactorauth";
import { UserContext } from "supertokens-node/types"

appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
recipeList: [
// other recipes..
override: {
functions: (originalImplementation) => {
return {
getGlobalClaimValidators: (input) => {
// We remove the existing default MFA validator which checks that
// the client has finished MFA.
let newValidatorsArray = input.claimValidatorsAddedByOtherRecipes.filter(v => !== MultiFactorAuth.MultiFactorAuthClaim.key);

// We create an instance of the default validator that is added
// by SuperTokens so that we can make modificationts to it
let originalValidator = MultiFactorAuth.MultiFactorAuthClaim.validators.hasCompletedMFARequirementsForAuth();

// We create a custom validator based on the default validator
let customValidator = {
validate: async (payload: any, userContext: UserContext): Promise<ClaimValidationResult> => {
// We only want to run the validation check
// if we know that the client is not an older one.
// In this examlpe, we do this based on the header
// in the API request, but the logic here can be
// anything that you like.
let request = getRequestFromUserContext(userContext);
if (request !== undefined) {
let isOlderClient = request.getHeaderValue("clilent-version") !== "2.0";
if (isOlderClient) {
// we return true early for older clients.
return {
isValid: true

// for newer clients, we call the original validate function
// which will check the claim value in the session.
return originalValidator.validate(payload, userContext);

return [customValidator, ...newValidatorsArray];

The code above will still result in the MFA claim being added to all sessions, with the v boolean in it being false, however, for older clients, we will not run the validator that checks if the v value is true on the backend since those clients have no way to show the MFA UI without updating the app.


You want to add the above exception only for a certain amount of time until you force all users to update their mobile app. This is because the exception method can be used as a way for malicious users to bypass MFA by spoofing that they are using an older client.

Looking for older versions of the documentation?
Which UI do you use?
Custom UI
Pre built UI