Skip to content
Esc
navigateopen⌘Jpreview
Dashboard
On this page

Require TOTP for all users

Implement a TOTP-based MFA policy for all users to enhance application security.

Overview

This guide shows you how to implement an MFA policy that requires all users to use TOTP before they get access to your application.

Before you start

The tutorial assumes that the first factor is email password or social login, but the same set of steps are applicable for other first factor types.

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.

Steps

Tenant setup

1. Configure the backend

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

import supertokens from "supertokens-node";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import EmailPassword from "supertokens-node/recipe/emailpassword";
import MultiFactorAuth from "supertokens-node/recipe/multifactorauth";
import totp from "supertokens-node/recipe/totp";
import Session from "supertokens-node/recipe/session";

supertokens.init({
  supertokens: {
    connectionURI: "...",
  },
  appInfo: {
    appName: "...",
    apiDomain: "...",
    websiteDomain: "...",
  },
  recipeList: [
    Session.init(),
    ThirdParty.init({
      //...
    }),
    EmailPassword.init({
      //...
    }),
    totp.init(),
    MultiFactorAuth.init({
      firstFactors: [MultiFactorAuth.FactorIds.EMAILPASSWORD, MultiFactorAuth.FactorIds.THIRDPARTY],
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,
            getMFARequirementsForAuth: async function (input) {
              return [MultiFactorAuth.FactorIds.TOTP];
            },
          };
        },
      },
    }),
  ],
});
from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import multifactorauth, totp
from supertokens_python.recipe.multifactorauth.types import (
    FactorIds,
    OverrideConfig,
    MFARequirementList,
)
from supertokens_python.recipe.multifactorauth.interfaces import RecipeInterface
from supertokens_python.types import User
from typing import Dict, Any, Callable, Awaitable, List


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:
        # Get roles for the user
        return [FactorIds.TOTP]

    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=[
        totp.init(),
        multifactorauth.init(
            first_factors=[FactorIds.EMAILPASSWORD, FactorIds.THIRDPARTY],
            override=OverrideConfig(functions=override_functions),
        ),
    ],
)
  • Notice that we have initialised the TOTP recipe in the recipeList. By default, no configs are required for it, but you can provide:
    • issuer: This is the name that will show up in the TOTP app for the user. By default, this is equal to the appName config, however, you can change it to something else using this property.
    • defaultSkew: The default value of this is 1, which means that TOTP codes that were generated 1 tick before, and that will be generated 1 tick after from the current tick will be accepted at any given time (including the TOTP of the current tick, of course).
    • defaultPeriod: The default value of this is 30, which means that the current tick is value for 30 seconds. So by default, a TOTP code that’s just shown to the user, is valid for 60 seconds (defaultPeriod + defaultSkew*defaultPeriod seconds)
  • We also override the getMFARequirementsForAuth function to indicate that totp must be completed before the user can access the app. Notice that we do not check for the userId there, and return totp for all users.

Once the user finishes the first factor (for example, with emailpassword), their session access token payload will look 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 totp, the payload will look like:

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

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

2. Configure the frontend

UI type

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

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

This change is in your auth route config.

import supertokens from "supertokens-auth-react";
import Passwordless from "supertokens-auth-react/recipe/passwordless";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import totp from "supertokens-auth-react/recipe/totp";

supertokens.init({
  appInfo: {
    appName: "...",
    apiDomain: "...",
    websiteDomain: "...",
  },
  recipeList: [
    // other recipes..
    totp.init(),
    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: [
    // other recipes..
    supertokensUITOTP.init(),
    supertokensUIMultiFactorAuth.init({
      firstFactors: [
        supertokensUIMultiFactorAuth.FactorIds.EMAILPASSWORD,
        supertokensUIMultiFactorAuth.FactorIds.THIRDPARTY,
      ],
    }),
  ],
});

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

  • Just like on the backend, we init the totp recipe in the recipeList.
  • We also init the MultiFactorAuth recipe, and pass in the first factors that we want to use. In this case, that would be emailpassword and thirdparty - same as the backend.

Next, we need to add the TOTP pre-built UI when rendering the SuperTokens component:

import { SuperTokensWrapper } from "supertokens-auth-react";
import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui";
import { TOTPPreBuiltUI } from "supertokens-auth-react/recipe/totp/prebuiltui";
import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifactorauth/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, [
                /* ... */ TOTPPreBuiltUI,
                MultiFactorAuthPreBuiltUI,
              ])}
              // ... other routes
            </Routes>
          </div>
        </Router>
      </div>
    </SuperTokensWrapper>
  );
}
import { SuperTokensWrapper } from "supertokens-auth-react";
import { canHandleRoute, getRoutingComponent } from "supertokens-auth-react/ui";
import { TOTPPreBuiltUI } from "supertokens-auth-react/recipe/totp/prebuiltui";
import { MultiFactorAuthPreBuiltUI } from "supertokens-auth-react/recipe/multifactorauth/prebuiltui";

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

With the above configuration, users will see emailpassword or social login UI when they visit the auth page. After completing that, users will be redirected to /auth/mfa/totp (assuming that the websiteBasePath is /auth) where they will be asked to setup the factor, or complete the TOTP challenge if they have already setup the factor before. The UI for this screen looks like:

API reference

API schema and response details