Skip to content
Esc
navigateopen⌘Jpreview
Dashboard
On this page

OTP required for all users

Implement a multi-factor authentication policy requiring all users to complete an OTP challenge.

Overview

This page shows how to implement an MFA policy that requires all users to complete an OTP challenge before accessing your application. The OTP can be sent via email or phone.

Enable paid features

This feature is only available to paid users. Follow the instructions below to enable it.

Managed Service

  1. Sign in to the SuperTokens dashboard.
  2. Select the managed service option from the service type select component.
  3. Select your core instance from the next elemenet or create a new one.
  4. Open Features sub-page and enable the required ones.

Self Hosted

  1. Sign in to the SuperTokens dashboard.
  2. Select the self-hosted option from the service type select component.
  3. Select your license key from the next elemenet or create a new one. Then enable the required features.
  4. If the key is not yet configured, add it to your Core service. If your Core already uses this key, no configuration changes are required.

Single tenant setup

Backend setup

To start with, configure the backend in the following way:

import supertokens, { User, RecipeUserId } from "supertokens-node";
import { UserContext } from "supertokens-node/types";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import EmailPassword from "supertokens-node/recipe/emailpassword";
import MultiFactorAuth from "supertokens-node/recipe/multifactorauth";
import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";
import AccountLinking from "supertokens-node/recipe/accountlinking";
import { AccountInfoWithRecipeId } from "supertokens-node/recipe/accountlinking/types";
import { SessionContainerInterface } from "supertokens-node/recipe/session/types";

supertokens.init({
  supertokens: {
    connectionURI: "...",
  },
  appInfo: {
    appName: "...",
    apiDomain: "...",
    websiteDomain: "...",
  },
  recipeList: [
    Session.init(),
    ThirdParty.init({
      //...
    }),
    EmailPassword.init({
      //...
    }),
    Passwordless.init({
      contactMethod: "EMAIL",
      flowType: "USER_INPUT_CODE",
    }),
    AccountLinking.init({
      shouldDoAutomaticAccountLinking: async (
        newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId },
        user: User | undefined,
        session: SessionContainerInterface | undefined,
        tenantId: string,
        userContext: UserContext,
      ) => {
        if (session === undefined) {
          // we do not want to do first factor account linking by default. To enable that,
          // please see the automatic account linking docs in the recipe docs for your first factor.
          return {
            shouldAutomaticallyLink: false,
          };
        }
        if (user === undefined || session.getUserId() === user.id) {
          // if it comes here, it means that a session exists, and we are trying to link the
          // newAccountInfo to the session user, which means it's an MFA flow, so we enable
          // linking here.
          return {
            shouldAutomaticallyLink: true,
            shouldRequireVerification: true,
          };
        }
        return {
          shouldAutomaticallyLink: false,
        };
      },
    }),
    MultiFactorAuth.init({
      firstFactors: [MultiFactorAuth.FactorIds.EMAILPASSWORD, MultiFactorAuth.FactorIds.THIRDPARTY],
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,
            getMFARequirementsForAuth: async function (input) {
              return [MultiFactorAuth.FactorIds.OTP_EMAIL];
            },
          };
        },
      },
    }),
  ],
});
from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import (
    accountlinking,
    emailpassword,
    multifactorauth,
    passwordless,
    session,
    thirdparty,
)
from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig
from supertokens_python.recipe.multifactorauth.types import FactorIds, OverrideConfig, MFARequirementList
from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface
from supertokens_python.recipe.session.interfaces import SessionContainer
from supertokens_python.recipe.accountlinking.types import (
    AccountInfoWithRecipeIdAndUserId,
    ShouldNotAutomaticallyLink,
    ShouldAutomaticallyLink,
)
from supertokens_python.types import User
from typing import Dict, Any, Callable, Awaitable, List, Optional, Union


async def should_do_automatic_account_linking(
    new_account_info: AccountInfoWithRecipeIdAndUserId,
    user: Optional[User],
    session: Optional[SessionContainer],
    tenant_id: str,
    user_context: Dict[str, Any]
) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]:
    if session is None:
        # We do not want to do first factor account linking by default.
        # To enable that, please see the automatic account linking docs
        # in the recipe docs for your first factor.
        return ShouldNotAutomaticallyLink()
    
    if user is None or session.get_user_id() == user.id:
        # If it comes here, it means that a session exists, and we are trying to link the 
        # new_account_info to the session user, which means it's an MFA flow, so we enable 
        # linking here.
        return ShouldAutomaticallyLink(should_require_verification=True)
    
    return ShouldNotAutomaticallyLink()


def override_functions(original_implementation: RecipeInterface):
    async def get_mfa_requirements_for_auth(
        tenant_id: str,
        access_token_payload: Dict[str, Any],
        completed_factors: Dict[str, int],
        user: Callable[[], Awaitable[User]],
        factors_set_up_for_user: Callable[[], Awaitable[List[str]]],
        required_secondary_factors_for_user: Callable[[], Awaitable[List[str]]],
        required_secondary_factors_for_tenant: Callable[[], Awaitable[List[str]]],
        user_context: Dict[str, Any],
    ) -> MFARequirementList:
        return [FactorIds.OTP_EMAIL]

    original_implementation.get_mfa_requirements_for_auth = (
        get_mfa_requirements_for_auth
    )
    return original_implementation


init(
    app_info=InputAppInfo(
        app_name="...",
        api_domain="...",
        website_domain="...",
    ),
    supertokens_config=SupertokensConfig(
        connection_uri="...",
    ),
    framework="...",  
    recipe_list=[
        session.init(),
        emailpassword.init(),
        thirdparty.init(),
        passwordless.init(
            contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE"
        ),
        accountlinking.init(
            should_do_automatic_account_linking=should_do_automatic_account_linking
        ),
        multifactorauth.init(
            first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY],
            override=OverrideConfig(functions=override_functions),
        ),
    ],
)
  • Notice that the Passwordless recipe initializes in the recipeList. In this example, only email-based OTP is enabled, with contactMethod set to EMAIL and flowType to USER_INPUT_CODE (that is, otp). If you want to use phone SMS-based OTP, set the contact method to PHONE. If you want to give users both options, or for some users use email, and for others use phone, set contactMethod to EMAIL_OR_PHONE.

  • We have also enabled the account linking feature since it’s required for MFA to work. The above enables account linking for second factor only, but if you also want to enable it for first factor, see this section.

  • shouldRequireVerification: true prevents an unverified login method from being linked. Passwordless OTP completion verifies the email address or phone number before the SDK attempts second-factor linking, so this does not block the OTP flow. Keep the callback session-bound as shown; do not return automatic linking for first-factor requests without a session.

  • The getMFARequirementsForAuth function is overridden to indicate that otp-email must be completed before the user can access the app. Notice that userId is not checked there, and otp-email is returned for all users. You can also return otp-phone instead if you want users to complete the OTP challenge via a phone SMS. Finally, if you want to give users an option for email or phone, you can return the following array from the function:

    [
      {
        "oneOf": ["otp-email", "otp-phone"]
      }
    ]

Once the user finishes the first factor (for example, with emailpassword), their session access token payload looks like this:

{
  "st-mfa": {
    "c": {
      "emailpassword": 1702877939
    },
    "v": false
  }
}

The v being false indicates that there are still factors that are pending. After the user has finished otp-email, the payload looks like:

{
  "st-mfa": {
    "c": {
      "emailpassword": 1702877939,
      "otp-email": 1702877999
    },
    "v": true
  }
}

This indicates that the user has finished all required factors and should be allowed to access the app.

Frontend setup

UI type

We start by modifying the init function call on the frontend like this:

You have to make changes to the auth route configuration, as well as to the supertokens-web-js SDK configuration at the root of your application:

This change is in your auth route configuration.

import supertokens from "supertokens-auth-react";
import ThirdParty from "supertokens-auth-react/recipe/thirdparty";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Passwordless from "supertokens-auth-react/recipe/passwordless";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";

supertokens.init({
  appInfo: {
    appName: "...",
    apiDomain: "...",
    websiteDomain: "...",
  },
  recipeList: [
    ThirdParty.init(/* ... */),
    EmailPassword.init(/* ... */),
    Passwordless.init({
      contactMethod: "EMAIL",
    }),
    MultiFactorAuth.init({
      firstFactors: [MultiFactorAuth.FactorIds.EMAILPASSWORD, MultiFactorAuth.FactorIds.THIRDPARTY],
    }),
  ],
});
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)

supertokensUIInit({
  appInfo: {
    appName: "...",
    apiDomain: "...",
    websiteDomain: "...",
  },
  recipeList: [
    supertokensUIThirdParty.init(/* ... */),
    supertokensUIEmailPassword.init(/* ... */),
    supertokensUIPasswordless.init({
      contactMethod: "EMAIL",
    }),
    supertokensUIMultiFactorAuth.init({
      firstFactors: [
        supertokensUIMultiFactorAuth.FactorIds.EMAILPASSWORD,
        supertokensUIMultiFactorAuth.FactorIds.THIRDPARTY,
      ],
    }),
  ],
});

This change goes in the supertokens-web-js SDK configuration at the root of your application:

  • Like on the backend, the passwordless recipe initializes in the recipeList. The contactMethod needs to be consistent with the backend setting.
  • The MultiFactorAuth recipe is also initialized, and the first factors to use are included. In this case, that would be emailpassword and thirdparty - same as the backend.

Next, add the Passwordless pre-built UI when rendering the SuperTokens component:

import { SuperTokensWrapper } from "supertokens-auth-react";
import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui";
import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui";
import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui";
import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifactorauth/prebuiltui";
import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui";
import reactRouterDOM, { Routes, BrowserRouter as Router, Route } from "react-router-dom";

function App() {
  return (
    <SuperTokensWrapper>
      <div className="App">
        <Router>
          <div className="fill">
            <Routes>
              {getSuperTokensRoutesForReactRouterDom(reactRouterDOM, [
                EmailPasswordPreBuiltUI,
                ThirdPartyPreBuiltUI,
                PasswordlessPreBuiltUI,
                MultiFactorAuthPreBuiltUI,
              ])}
              // ... other routes
            </Routes>
          </div>
        </Router>
      </div>
    </SuperTokensWrapper>
  );
}
import { SuperTokensWrapper } from "supertokens-auth-react";
import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui";
import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui";
import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui";
import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifactorauth/prebuiltui";
import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui";

function App() {
  if (
    canHandleRoute([EmailPasswordPreBuiltUI, ThirdPartyPreBuiltUI, PasswordlessPreBuiltUI, MultiFactorAuthPreBuiltUI])
  ) {
    return getRoutingComponent([
      EmailPasswordPreBuiltUI,
      ThirdPartyPreBuiltUI,
      PasswordlessPreBuiltUI,
      MultiFactorAuthPreBuiltUI,
    ]);
  }
  return <SuperTokensWrapper>{/*Your app*/}</SuperTokensWrapper>;
}

With the above configuration, users see emailpassword or social login UI when they visit the auth page. After completing that, users redirect to /auth/mfa/otp-email (assuming that the websiteBasePath is /auth) where they are asked to complete the OTP challenge. The UI for this screen looks like:

  • Factor Setup UI (This is in case the first factor doesn’t provide an email for the user. In this example, the first factor does provide an email since it’s email password or social login).
  • Verification UI.

Multi tenant setup

In a multi-tenancy setup, you may want to enable email / phone OTP for all users, across all tenants, or for all users within specific tenants. For enabling for all users across all tenants, it’s the same steps as in the single tenant setup section above, so in this section, we will focus on enabling OTP for all users within specific tenants.

Backend setup

To start, initialize the Passwordless and the MultiFactorAuth recipes in the following way:

import supertokens, { User, RecipeUserId } from "supertokens-node";
import { UserContext } from "supertokens-node/types";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import EmailPassword from "supertokens-node/recipe/emailpassword";
import MultiFactorAuth from "supertokens-node/recipe/multifactorauth";
import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";
import { AccountInfoWithRecipeId } from "supertokens-node/recipe/accountlinking/types";
import { SessionContainerInterface } from "supertokens-node/recipe/session/types";
import AccountLinking from "supertokens-node/recipe/accountlinking";

supertokens.init({
  supertokens: {
    connectionURI: "...",
  },
  appInfo: {
    appName: "...",
    apiDomain: "...",
    websiteDomain: "...",
  },
  recipeList: [
    Session.init(),
    ThirdParty.init({
      //...
    }),
    EmailPassword.init({
      //...
    }),
    Passwordless.init({
      contactMethod: "EMAIL",
      flowType: "USER_INPUT_CODE",
    }),
    AccountLinking.init({
      shouldDoAutomaticAccountLinking: async (
        newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId },
        user: User | undefined,
        session: SessionContainerInterface | undefined,
        tenantId: string,
        userContext: UserContext,
      ) => {
        if (session === undefined) {
          // we do not want to do first factor account linking by default. To enable that,
          // please see the automatic account linking docs in the recipe docs for your first factor.
          return {
            shouldAutomaticallyLink: false,
          };
        }
        if (user === undefined || session.getUserId() === user.id) {
          // if it comes here, it means that a session exists, and we are trying to link the
          // newAccountInfo to the session user, which means it's an MFA flow, so we enable
          // linking here.
          return {
            shouldAutomaticallyLink: true,
            shouldRequireVerification: true,
          };
        }
        return {
          shouldAutomaticallyLink: false,
        };
      },
    }),
    MultiFactorAuth.init(),
  ],
});
from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import (
    accountlinking,
    emailpassword,
    multifactorauth,
    passwordless,
    session,
    thirdparty,
)
from supertokens_python.recipe.passwordless import ContactEmailOnlyConfig
from supertokens_python.recipe.session.interfaces import SessionContainer
from supertokens_python.recipe.accountlinking.types import (
    AccountInfoWithRecipeIdAndUserId,
    ShouldNotAutomaticallyLink,
    ShouldAutomaticallyLink,
)
from supertokens_python.types import User
from typing import Dict, Any, Optional, Union


async def should_do_automatic_account_linking(
    new_account_info: AccountInfoWithRecipeIdAndUserId,
    user: Optional[User],
    session: Optional[SessionContainer],
    tenant_id: str,
    user_context: Dict[str, Any]
) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]:
    if session is None:
        # We do not want to do first factor account linking by default.
        # To enable that, please see the automatic account linking docs
        # in the recipe docs for your first factor.
        return ShouldNotAutomaticallyLink()
    
    if user is None or session.get_user_id() == user.id:
        # If it comes here, it means that a session exists, and we are trying to link the 
        # new_account_info to the session user, which means it's an MFA flow, so we enable 
        # linking here.
        return ShouldAutomaticallyLink(should_require_verification=True)
    
    return ShouldNotAutomaticallyLink()


init(
    app_info=InputAppInfo(
        app_name="...",
        api_domain="...",
        website_domain="...",
    ),
    supertokens_config=SupertokensConfig(
        connection_uri="...",
    ),
    framework="...",  
    recipe_list=[
        session.init(),
        emailpassword.init(),
        thirdparty.init(),
        passwordless.init(
            contact_config=ContactEmailOnlyConfig(), flow_type="USER_INPUT_CODE"
        ),
        accountlinking.init(
            should_do_automatic_account_linking=should_do_automatic_account_linking
        ),
        multifactorauth.init(),
    ],
)

Unlike the single tenant setup, no configuration is provided to the MultiFactorAuth recipe because all the necessary configuration is done on a tenant level.

To configure otp-email requirement for a tenant, the following API can be called:

To configure otp-email requirement for a tenant, the following API can be called:

Enable EmailPassword and ThirdParty, OTP-Email

As shown above, enable Email Password and Third Party in the Login methods section and enable OTP - Email in the Secondary Factors Section.

import Multitenancy from "supertokens-node/recipe/multitenancy";
import MultiFactorAuth from "supertokens-node/recipe/multifactorauth";

async function createNewTenant() {
  let resp = await Multitenancy.createOrUpdateTenant("customer1", {
    firstFactors: [MultiFactorAuth.FactorIds.EMAILPASSWORD, MultiFactorAuth.FactorIds.THIRDPARTY],
    requiredSecondaryFactors: [MultiFactorAuth.FactorIds.OTP_EMAIL],
  });

  if (resp.createdNew) {
    // Tenant created successfully
  } else {
    // Existing tenant's config was modified.
  }
}
from supertokens_python.recipe.multitenancy.asyncio import create_or_update_tenant
from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate
from supertokens_python.recipe.multifactorauth.types import FactorIds


async def create_new_tenant():
    resp = await create_or_update_tenant(
        "customer1",
        TenantConfigCreateOrUpdate(
            first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY],
            required_secondary_factors=[FactorIds.OTP_EMAIL],
        ),
    )

    if resp.created_new:
        # Tenant created successfully
        pass
    else:
        # Existing tenant's config was modified
        pass
from supertokens_python.recipe.multitenancy.syncio import create_or_update_tenant
from supertokens_python.recipe.multitenancy.interfaces import TenantConfigCreateOrUpdate
from supertokens_python.recipe.multifactorauth.types import FactorIds


def create_new_tenant():
    resp = create_or_update_tenant(
        "customer1",
        TenantConfigCreateOrUpdate(
            first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY],
            required_secondary_factors=[FactorIds.OTP_EMAIL],
        ),
    )
    

    if resp.created_new:
        # Tenant created successfully
        pass
    else:
        # Existing tenant's config was modified
        pass
curl --location --request PUT 'http://localhost:3567/recipe/multitenancy/tenant/v2' \
--header 'api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
    "tenantId": "customer1",
    "firstFactors": ["emailpassword", "thirdparty"],
    "requiredSecondaryFactors": ["otp-email"]
}'
  • In the above, the firstFactors are set to ["emailpassword", "thirdparty"] to indicate that the first factor can be either emailpassword or thirdparty.
  • The requiredSecondaryFactors is set to ["otp-email"] to indicate that OTP email is required for all users in this tenant. The default implementation of getMFARequirementsForAuth in the MultiFactorAuth takes this into account.
  • In the above, the firstFactors are set to ["emailpassword", "thirdparty"] to indicate that the first factor can be either emailpassword or thirdparty.
  • The requiredSecondaryFactors is set to ["otp-email"] to indicate that OTP email is required for all users in this tenant. The default implementation of getMFARequirementsForAuth in the MultiFactorAuth takes this into account.

Once the user finishes the first factor (for example, with emailpassword), their session access token payload looks like this:

{
  "st-mfa": {
    "c": {
      "emailpassword": 1702877939
    },
    "v": false
  }
}

The v being false indicates that there are still factors that are pending. After the user has finished otp-email challenge, the payload looks like:

{
  "st-mfa": {
    "c": {
      "emailpassword": 1702877939,
      "otp-email": 1702877999
    },
    "v": true
  }
}

This indicates that the user has finished all required factors and should be allowed to access the app.

Frontend setup

We start by modifying the init function call on the frontend like this:

You have to make changes to the auth route configuration, as well as to the supertokens-web-js SDK configuration at the root of your application:

This change is in your auth route configuration.

import supertokens from "supertokens-auth-react";
import ThirdParty from "supertokens-auth-react/recipe/thirdparty";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import Passwordless from "supertokens-auth-react/recipe/passwordless";
import Multitenancy from "supertokens-auth-react/recipe/multitenancy";

supertokens.init({
  appInfo: {
    appName: "...",
    apiDomain: "...",
    websiteDomain: "...",
  },
  usesDynamicLoginMethods: true,
  recipeList: [
    ThirdParty.init({
      //...
    }),
    EmailPassword.init({
      //...
    }),
    Passwordless.init({
      contactMethod: "EMAIL",
    }),
    MultiFactorAuth.init(),
    Multitenancy.init({
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,
            getTenantId: async (context) => {
              return "TODO";
            },
          };
        },
      },
    }),
  ],
});
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)

supertokensUIInit({
  appInfo: {
    appName: "...",
    apiDomain: "...",
    websiteDomain: "...",
  },
  usesDynamicLoginMethods: true,
  recipeList: [
    supertokensUIThirdParty.init({
      //...
    }),
    supertokensUIEmailPassword.init({
      //...
    }),
    supertokensUIPasswordless.init({
      contactMethod: "EMAIL",
    }),
    supertokensUIMultiFactorAuth.init(),
    supertokensUIMultitenancy.init({
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,
            getTenantId: async (context) => {
              return "TODO";
            },
          };
        },
      },
    }),
  ],
});

This change goes in the supertokens-web-js SDK configuration at the root of your application:

  • Like on the backend, the Passwordless recipe initializes in the recipeList. Make sure that the configuration for it is consistent with what’s on the backend.
  • The MultiFactorAuth recipe is also initialized. Notice that unlike the single tenant setup, the firstFactors are not specified here. That information is fetched based on the tenantId you provide the SDK with.
  • usesDynamicLoginMethods: true is set so that the SDK knows to fetch the login methods dynamically based on the tenantId.
  • Finally, the multi-tenancy recipe initializes and a method for getting the tenantId is provided.

Next, add the Passwordless pre-built UI when rendering the SuperTokens component:

import { SuperTokensWrapper } from "supertokens-auth-react";
import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui";
import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui";
import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui";
import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifactorauth/prebuiltui";
import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui";
import reactRouterDOM, { Routes, BrowserRouter as Router, Route } from "react-router-dom";

function App() {
  return (
    <SuperTokensWrapper>
      <div className="App">
        <Router>
          <div className="fill">
            <Routes>
              {getSuperTokensRoutesForReactRouterDom(reactRouterDOM, [
                EmailPasswordPreBuiltUI,
                ThirdPartyPreBuiltUI,
                PasswordlessPreBuiltUI,
                MultiFactorAuthPreBuiltUI,
              ])}
              // ... other routes
            </Routes>
          </div>
        </Router>
      </div>
    </SuperTokensWrapper>
  );
}
import { SuperTokensWrapper } from "supertokens-auth-react";
import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui";
import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui";
import { ThirdPartyPreBuiltUI } from "supertokens-auth-react/recipe/thirdparty/prebuiltui";
import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifactorauth/prebuiltui";
import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui";

function App() {
  if (
    canHandleRoute([EmailPasswordPreBuiltUI, ThirdPartyPreBuiltUI, PasswordlessPreBuiltUI, MultiFactorAuthPreBuiltUI])
  ) {
    return getRoutingComponent([
      EmailPasswordPreBuiltUI,
      ThirdPartyPreBuiltUI,
      PasswordlessPreBuiltUI,
      MultiFactorAuthPreBuiltUI,
    ]);
  }
  return <SuperTokensWrapper>{/*Your app*/}</SuperTokensWrapper>;
}

With the above configuration, users see the first and second factor based on the tenant configuration. For the tenant configured above, users see email password or social login first. After completing that, users redirect to /auth/mfa/otp-email (assuming that the websiteBasePath is /auth) where they are asked to complete the OTP challenge. The UI for this screen looks like:

  • Factor Setup UI (This is in case the first factor doesn’t provide an email for the user. In this example, the first factor does provide an email since it’s email password or social login).
  • Verification UI.

See also

API reference

API schema and response details