---
title: Require TOTP for all users
description: Implement a TOTP-based MFA policy for all users to enhance application security.
sidebar:
  order: 1
---

## 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.

<PaidFeatureCallout />

## Steps

<TenantTypeSwitch />

<VariantContent storageKey="tenant-type" value="single">

### 1. Configure the backend

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

<DependentContent passive group="backend-language">
<ContentOption title="Go" value="go">
:::note[At the moment this feature is not supported through the Go SDK.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```ts
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];
            },
          };
        },
      },
    }),
  ],
});
```
</Tab>
<Tab title="Go" value="go">

</Tab>
<Tab title="Python" value="python">
```python check=false reason="framework placeholder must be replaced for the target Python server"
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),
        ),
    ],
)
```
</Tab>
</CodeGroup>

- 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:
```json
{
  "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:

```json
{
  "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

<UITypeSwitch />

<VariantContent storageKey="ui-type" value="prebuilt">

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

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Angular" value="angular">
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.
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
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],
    }),
  ],
});
```
</Tab>
<Tab title="Angular" value="angular">
```tsx check=false reason="script-tag example relies on globals provided by loaded SuperTokens bundles"
// 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,
      ],
    }),
  ],
});
```
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Angular" value="angular">
This change goes in the `supertokens-web-js` SDK config at the root of your application:
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-prebuilt-ui">
<Tab title="Angular" value="angular">
```tsx
import SuperTokens from "supertokens-web-js";
import MultiFactorAuth from "supertokens-web-js/recipe/multifactorauth";
import Totp from "supertokens-web-js/recipe/totp";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    apiBasePath: "...",
    appName: "...",
  },
  recipeList: [
    // other recipes...
    MultiFactorAuth.init(),
    Totp.init(),
  ],
});
```
</Tab>
</CodeGroup>

- 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:

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Angular" value="angular">
:::success[This step is not required for non React apps, since all the pre-built UI components are already added into the bundle.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
<DependentContent group="react-router" label="Do you use react-router-dom?">
<ContentOption title="With React Router" value="yes">
```tsx
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>
  );
}
```
</ContentOption>
<ContentOption title="Without React Router" value="no">
```tsx
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>;
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Angular" value="angular">

</Tab>
</CodeGroup>

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:
- [Factor Setup UI](https://6571be2867f75556541fde98-xieqfaxuuo.chromatic.com/?path=/story/totp-mfa--device-setup-with-single-next-option)
- [Verification UI](https://6571be2867f75556541fde98-xieqfaxuuo.chromatic.com/?path=/story/totp-mfa--verification-with-single-next-option) (In case the factor is already setup before).

</VariantContent>

<VariantContent storageKey="ui-type" value="custom">


We start by initialising the MFA and TOTP recipe on the frontend like so:



<DependentContent passive group="frontend-custom-ui">
<ContentOption title="Mobile" value="mobile">
:::success[This step is not applicable for mobile apps. Please continue reading.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-custom-ui">
<Tab title="Web" value="web">
<DependentContent group="install-method" label="Installation method">
<ContentOption title="npm" value="npm">
```tsx
import SuperTokens from "supertokens-web-js";
import MultiFactorAuth from "supertokens-web-js/recipe/multifactorauth";
import Totp from "supertokens-web-js/recipe/totp";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    apiBasePath: "...",
    appName: "...",
  },
  recipeList: [
    // other recipes...
    MultiFactorAuth.init(),
    Totp.init(),
  ],
});
```
</ContentOption>
<ContentOption title="Script tag" value="script-tag">
```tsx check=false reason="script-tag example relies on globals provided by loaded SuperTokens bundles"
supertokens.init({
  appInfo: {
    apiDomain: "...",
    apiBasePath: "...",
    appName: "...",
  },
  recipeList: [
    // other recipes...
    supertokensMultiFactorAuth.init(),
    supertokensTotp.init(),
  ],
});
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Mobile" value="mobile">

</Tab>
</CodeGroup>



After the first factor login, you should start by [checking the access token payload and see if the MFA claim's `v` boolean is `false`](/additional-verification/mfa/initial-setup#12-add-the-mfa-flow). If it's not, then we can redirect the user to the application page.

If it's `false`, the frontend then needs to [call the MFA endpoint](/additional-verification/mfa/initial-setup#the-mfa-info-endpoint) to get information about which factor the user should be asked to complete next. Based on the backend config in this page, the `next` array will contain `["totp"]`.

Two possibilities exist here:
- Case 1: The user needs to setup a TOTP device cause they don't have any.
- Case 2: The user already has a verified device setup and needs to complete the TOTP challenge.

We can know which case it is by checking if `"totp"` is one of the items in the `factorsThatAreAlreadySetup` array that is returned from the API call above. If it is in the array, then it's case 2, otherwise it's case 1.

#### Case 1 implementation: User needs to setup a new TOTP device
In this case, we do two things:
- Call an API on the backend to create a device. This returns the device secret that can be displayed to the user. The user is supposed to scan this using their authenticator app, to add a new entry for your app in their authenticator app.
- Then the user needs to enter the TOTP code that's displayed to them in the app, and this needs to be sent to the backend to mark the device as verified. Once a device is marked as verified, only then will the `factorsThatAreAlreadySetup` array contain `"totp"` the next time they login.

To create a new device, call the following API:



<DependentContent passive group="frontend-custom-ui">
<ContentOption title="Mobile" value="mobile">
The above API call returns the following response:
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-custom-ui">
<Tab title="Web" value="web">
<DependentContent group="install-method" label="Installation method">
<ContentOption title="npm" value="npm">
```tsx
import Totp from "supertokens-web-js/recipe/totp";
import Session from "supertokens-web-js/recipe/session";

async function createNewTotpDevice() {
  if (await Session.doesSessionExist()) {
    try {
      let deviceResponse = await Totp.createDevice();
      if (deviceResponse.status === "DEVICE_ALREADY_EXISTS_ERROR") {
        // this should only come here if you are passing a custom device name when calling the above function.
        throw new Error("Should never come here");
        // device created successfully
      }
      // device created successfully
      let qrCodeString = deviceResponse.qrCodeString;
      let secret = deviceResponse.secret;

      // TODO: display a QR code based on qrCodeString, and also an option to view
      // the secret if the user is unable to scan the QR code.
    } catch (err: any) {
      if (err.isSuperTokensGeneralError === true) {
        // this may be a custom error message sent from the API by you.
        window.alert(err.message);
      } else {
        window.alert("Oops! Something went wrong.");
      }
    }
  } else {
    throw new Error(
      "TOTP device creation can only happen after the first factor is complete and when a session exists",
    );
  }
}
```
</ContentOption>
<ContentOption title="Script tag" value="script-tag">
```tsx check=false reason="script-tag example relies on globals provided by loaded SuperTokens bundles"
async function createNewTotpDevice() {
  if (await supertokensSession.doesSessionExist()) {
    try {
      let deviceResponse = await supertokensTotp.createDevice();
      if (deviceResponse.status === "DEVICE_ALREADY_EXISTS_ERROR") {
        // this should only come here if you are passing a custom device name when calling the above function.
        throw new Error("Should never come here");
        // device created successfully
      }
      // device created successfully
      let qrCodeString = deviceResponse.qrCodeString;
      let secret = deviceResponse.secret;

      // TODO: display a QR code based on qrCodeString, and also an option to view
      // the secret if the user is unable to scan the QR code.
    } catch (err: any) {
      if (err.isSuperTokensGeneralError === true) {
        // this may be a custom error message sent from the API by you.
        window.alert(err.message);
      } else {
        window.alert("Oops! Something went wrong.");
      }
    }
  } else {
    throw new Error(
      "TOTP device creation can only happen after the first factor is complete and when a session exists",
    );
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Mobile" value="mobile">
```json check=false reason="response alternatives are shown as a JSON-like union"
{
    "status": "OK",
    "issuerName": "...",
    "deviceName": "TOTP Device 1",
    "secret": "....",
    "userIdentifier": "user@example.com",
    "qrCodeString": "..."    
} | {
    "status": "DEVICE_ALREADY_EXISTS_ERROR" | "GENERAL_ERROR"
}
```
</Tab>
</CodeGroup>



- When device registration is successful, the API returns:
    - The `secret` and `qrCodeString` which are to be displayed to the user. For React apps, we recommend using the [react-qr-code library](https://github.com/rosskhanas/react-qr-code) to display the QR code.
    - The `issuerName` is the name will show up on the TOTP app for the user. By default, this is equal to the `appName` config on the backend SDK, however, you can change it to something else in the backend `totp.init` config.
    - The `userIdentifier` is the email / phone number of the user based on the first factor. This will also be shown in the TOTP app along with the `issuerName`.
- The API call can also take a `deviceName` (as a POST body prop) which attempts to create a TOTP device with the provided name. A status of `"DEVICE_ALREADY_EXISTS_ERROR"` is returned in case a verified device with the input name already exists. In this case, you should ask the user to enter a different name. Note that this status is only returned in case you are passing in a custom device name. The default naming strategy is to name the device "TOTP Device N", where we start N from 1, and keep increasing it. This value can be used to identify a device from the backend point of view, for operations like deleting a device.
- A status of `"GENERAL_ERROR"` is returned in case you specifically return that from a backend API override.

Once a device has been created, and scanned, you need to ask the user to enter the TOTP and call the API below to verify it:



<DependentContent passive group="frontend-custom-ui">
<ContentOption title="Mobile" value="mobile">
The above API call returns the following response:
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-custom-ui">
<Tab title="Web" value="web">
<DependentContent group="install-method" label="Installation method">
<ContentOption title="npm" value="npm">
```tsx
import Totp from "supertokens-web-js/recipe/totp";
import Session from "supertokens-web-js/recipe/session";

async function verifyTotpDevice(deviceName: string, userInputTotp: string) {
  if (await Session.doesSessionExist()) {
    try {
      let verifyResponse = await Totp.verifyDevice({
        deviceName,
        totp: userInputTotp,
      });
      if (verifyResponse.status === "UNKNOWN_DEVICE_ERROR") {
        // this can happen due to a race condition wherein the device is deleted before verifying.
        window.alert("Something went wrong. Please reload and try again");
      } else if (verifyResponse.status === "LIMIT_REACHED_ERROR") {
        // this can happen if the user has entered a wrong TOTP too many times.
        window.alert("Totp incorrect. Please try again in " + verifyResponse.retryAfterMs / 1000 + " seconds");
      } else if (verifyResponse.status === "INVALID_TOTP_ERROR") {
        window.alert("Totp incorrect. Please try again");
      } else {
        // Device verified successfully
      }
    } catch (err: any) {
      if (err.isSuperTokensGeneralError === true) {
        // this may be a custom error message sent from the API by you.
        window.alert(err.message);
      } else {
        window.alert("Oops! Something went wrong.");
      }
    }
  } else {
    throw new Error(
      "TOTP device verification can only happen after the first factor is complete and when a session exists",
    );
  }
}
```
</ContentOption>
<ContentOption title="Script tag" value="script-tag">
```tsx check=false reason="script-tag example relies on globals provided by loaded SuperTokens bundles"
async function verifyTotpDevice(deviceName: string, userInputTotp: string) {
  if (await supertokensSession.doesSessionExist()) {
    try {
      let verifyResponse = await supertokensTotp.verifyDevice({
        deviceName,
        totp: userInputTotp,
      });
      if (verifyResponse.status === "UNKNOWN_DEVICE_ERROR") {
        // this can happen due to a race condition wherein the device is deleted before verifying.
        window.alert("Something went wrong. Please reload and try again");
      } else if (verifyResponse.status === "LIMIT_REACHED_ERROR") {
        // this can happen if the user has entered a wrong TOTP too many times.
        window.alert("Totp incorrect. Please try again in " + verifyResponse.retryAfterMs / 1000 + " seconds");
      } else if (verifyResponse.status === "INVALID_TOTP_ERROR") {
        window.alert("Totp incorrect. Please try again");
      } else {
        // Device verified successfully
      }
    } catch (err: any) {
      if (err.isSuperTokensGeneralError === true) {
        // this may be a custom error message sent from the API by you.
        window.alert(err.message);
      } else {
        window.alert("Oops! Something went wrong.");
      }
    }
  } else {
    throw new Error(
      "TOTP device verification can only happen after the first factor is complete and when a session exists",
    );
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Mobile" value="mobile">
```json check=false reason="response alternatives are shown as a JSON-like union"
{
        "status": "OK",
        "wasAlreadyVerified": false
} | {
        "status": "INVALID_TOTP_ERROR",
        "currentNumberOfFailedAttempts": 1,
        "maxNumberOfFailedAttempts": 5
} | {
        "status": "LIMIT_REACHED_ERROR",
        "retryAfterMs": 900000
} | {
        "status": "UNKNOWN_DEVICE_ERROR" | "GENERAL_ERROR"
}
```
</Tab>
</CodeGroup>



- The `deviceName`, which is an input to the API is one of the props returned from the previous API call to create a device.
- When verification is successful (`status: "OK"`), the device is marked as verified in the database and can be used for the TOTP challenge next time around. The boolean `wasAlreadyVerified` indicates if the device was already verified before this call was made.
- A status of `INVALID_TOTP_ERROR` means that the user has entered an incorrect TOTP and needs to retry. The response contains two other props:
    - `currentNumberOfFailedAttempts`: The number of times the user has entered an incorrect TOTP so far.
    - `maxNumberOfFailedAttempts`: The maximum number of times the user can enter an incorrect TOTP before they are asked to wait (see `status: LIMIT_REACHED_ERROR`). This is set to 5 by default in the core. You can change this value by setting the `totp_max_attempts` in the core config.
- A status of `LIMIT_REACHED_ERROR` indicates that the user has entered an incorrect TOTP too many times and must wait before trying again (otherwise valid TOTPs will fail). The waiting period is indicated by the `retryAfterMs` prop in the response body. By default, it is 15 minutes, but it can be changed by setting the value for `totp_rate_limit_cooldown_sec` in the core config.
- A status of `UNKNOWN_DEVICE_ERROR` is possible due to a race condition in which the device is somehow deleted before the verification call is made.
- A status of `GENERAL_ERROR` is possible if you specifically return that from a backend API override.

On successful verification of a device, the `totp` factor is marked as completed and the `v` value is updated in the session based on if there are any more factors that the user needs to complete. The next step would be to check this `v` value in the MFA claim and redirect the user to the application page, or get information about the next factor using the [MFA info endpoint](/additional-verification/mfa/initial-setup#the-mfa-info-endpoint).

#### Case 2 implementation: User needs to complete the TOTP challenge

This case is when the user already has a device setup (`totp` is in `factorsThatAreAlreadySetup`), and needs to complete the TOTP challenge. In this case, you should show the user an input box asking them to enter their TOTP from the authenticator app and then call the following API:



<DependentContent passive group="frontend-custom-ui">
<ContentOption title="Mobile" value="mobile">
The above API call returns the following response:
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-custom-ui">
<Tab title="Web" value="web">
<DependentContent group="install-method" label="Installation method">
<ContentOption title="npm" value="npm">
```tsx
import Totp from "supertokens-web-js/recipe/totp";
import Session from "supertokens-web-js/recipe/session";

async function verifyTotpCode(userInputTotp: string) {
  if (await Session.doesSessionExist()) {
    try {
      let verifyResponse = await Totp.verifyCode({
        totp: userInputTotp,
      });
      if (verifyResponse.status === "LIMIT_REACHED_ERROR") {
        // this can happen if the user has entered a wrong TOTP too many times.
        window.alert("Totp incorrect. Please try again in " + verifyResponse.retryAfterMs / 1000 + " seconds");
      } else if (verifyResponse.status === "INVALID_TOTP_ERROR") {
        window.alert("Totp incorrect. Please try again");
      } else {
        // Code verified successfully
      }
    } catch (err: any) {
      if (err.isSuperTokensGeneralError === true) {
        // this may be a custom error message sent from the API by you.
        window.alert(err.message);
      } else {
        window.alert("Oops! Something went wrong.");
      }
    }
  } else {
    throw new Error(
      "TOTP code verification can only happen after the first factor is complete and when a session exists",
    );
  }
}
```
</ContentOption>
<ContentOption title="Script tag" value="script-tag">
```tsx check=false reason="script-tag example relies on globals provided by loaded SuperTokens bundles"
async function verifyTotpCode(userInputTotp: string) {
  if (await supertokensSession.doesSessionExist()) {
    try {
      let verifyResponse = await supertokensTotp.verifyCode({
        totp: userInputTotp,
      });
      if (verifyResponse.status === "LIMIT_REACHED_ERROR") {
        // this can happen if the user has entered a wrong TOTP too many times.
        window.alert("Totp incorrect. Please try again in " + verifyResponse.retryAfterMs / 1000 + " seconds");
      } else if (verifyResponse.status === "INVALID_TOTP_ERROR") {
        window.alert("Totp incorrect. Please try again");
      } else {
        // Code verified successfully
      }
    } catch (err: any) {
      if (err.isSuperTokensGeneralError === true) {
        // this may be a custom error message sent from the API by you.
        window.alert(err.message);
      } else {
        window.alert("Oops! Something went wrong.");
      }
    }
  } else {
    throw new Error(
      "TOTP code verification can only happen after the first factor is complete and when a session exists",
    );
  }
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Mobile" value="mobile">
```json check=false reason="response alternatives are shown as a JSON-like union"
{
        "status": "OK" | "UNKNOWN_USER_ID_ERROR"
} | {
        "status": "INVALID_TOTP_ERROR",
        "currentNumberOfFailedAttempts": 1,
        "maxNumberOfFailedAttempts": 5,
} | {
        "status": "LIMIT_REACHED_ERROR",
        "retryAfterMs": 900000,
} | {
        "status": "GENERAL_ERROR"
}
```
</Tab>
</CodeGroup>



- A `status: OK` indicates that verification was successful. SuperTokens tries and verifies the input TOTP against all verified devices that belong to this user.
- A status of `INVALID_TOTP_ERROR` means that the user has entered the an incorrect TOTP and needs to retry. The response contains two other props:
    - `currentNumberOfFailedAttempts`: The number of times the user has entered an incorrect TOTP so far.
    - `maxNumberOfFailedAttempts`: The maximum number of times the user can enter an incorrect TOTP before they are asked to wait (see `status: LIMIT_REACHED_ERROR`). This is set to 5 by default in the core. You can change this value by setting the `totp_max_attempts` in the core config.
- A status of `LIMIT_REACHED_ERROR` indicates that the user has entered an incorrect TOTP too many times and must wait before trying again (otherwise even value TOTPs will fail). The waiting period is indicated by the `retryAfterMs` prop in the response body. By default, it is 15 minutes, but it can be changed by setting the value for `totp_rate_limit_cooldown_sec` in the core config.
- A status of `UNKNOWN_USER_ID_ERROR` is possible due to a race condition in which all devices that the user had are deleted by the time this API is called. In this case, you can ask users to setup a new device.
- A status of `GENERAL_ERROR` is possible if you specifically return that from a backend API override.

On successful verification of the code, the `totp` factor is marked as completed and the `v` value is updated in the session based on if there are any more factors that the user needs to complete. The next step would be to check this `v` value in the MFA claim and redirect the user to the application page, or get information about the next factor using the [MFA info endpoint](/additional-verification/mfa/initial-setup#the-mfa-info-endpoint).

</VariantContent>


</VariantContent>

<VariantContent storageKey="tenant-type" value="multi">

In a multi tenancy setup, you may want to enable TOTP 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](#1-configure-the-backend) section above, so in this section, we will focus on enabling TOTP for all users within specific tenants.

### 1. Configure the backend 

To start, we will initialise the TOTP and the MultiFactorAuth recipes in the following way:

<DependentContent passive group="backend-language">
<ContentOption title="Go" value="go">
:::note[At the moment this feature is not supported through the Go SDK.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```ts
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(),
  ],
});
```
</Tab>
<Tab title="Go" value="go">

</Tab>
<Tab title="Python" value="python">
```python check=false reason="framework placeholder must be replaced for the target Python server"
from supertokens_python import init, InputAppInfo, SupertokensConfig
from supertokens_python.recipe import multifactorauth, totp


init(
    app_info=InputAppInfo(
        app_name="...",
        api_domain="...",
        website_domain="...",
    ),
    supertokens_config=SupertokensConfig(
        connection_uri="...",
    ),
    framework="...",  
    recipe_list=[
        totp.init(),
        multifactorauth.init(),
    ],
)
```
</Tab>
</CodeGroup>

Unlike the single tenant setup, we do not provide any config to the `MultiFactorAuth` recipe cause all the necessary configuration will be done on a tenant level.

<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
To configure TOTP requirement for a tenant, we can call the following API:
</ContentOption>
<ContentOption title="Go" value="go">
:::note[At the moment this feature is not supported through the Go SDK.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
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.TOTP],
  });

  if (resp.createdNew) {
    // Tenant created successfully
  } else {
    // Existing tenant's config was modified.
  }
}
```
</Tab>
<Tab title="Go" value="go">

</Tab>
<Tab title="Python" value="python">
<DependentContent group="python-io-style" label="I/O style">
<ContentOption title="Asyncio" value="asyncio">
```python
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.TOTP],
        )
    )

    if resp.created_new:
        # Tenant created successfully
        pass
    else:
        # Existing tenant's config was modified
        pass
```
</ContentOption>
<ContentOption title="Syncio" value="syncio">
```python
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.TOTP],
        )
    )

    if resp.created_new:
        # Tenant created successfully
        pass
    else:
        # Existing tenant's config was modified
        pass
```
</ContentOption>
</DependentContent>
</Tab>
</CodeGroup>

<DependentContent passive group="backend-language">
<ContentOption title="Node.js" value="nodejs">
- In the above, we set the `firstFactors` to `["emailpassword", "thirdparty"]` to indicate that the first factor can be either `emailpassword` or `thirdparty`.
- We set the `requiredSecondaryFactors` to `["totp"]` to indicate that TOTP is required for all users in this tenant. The default implementation of `getMFARequirementsForAuth` in the `MultiFactorAuth` takes this into account.
</ContentOption>
</DependentContent>

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

```json
{
  "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


<VariantContent storageKey="ui-type" value="prebuilt">

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

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Angular" value="angular">
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.
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
import supertokens from "supertokens-auth-react";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import totp from "supertokens-auth-react/recipe/totp";
import Multitenancy from "supertokens-auth-react/recipe/multitenancy";

supertokens.init({
  appInfo: {
    appName: "...",
    apiDomain: "...",
    websiteDomain: "...",
  },
  usesDynamicLoginMethods: true,
  recipeList: [
    // other recipes...
    totp.init(),
    MultiFactorAuth.init(),
    Multitenancy.init({
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,
            getTenantId: async (context) => {
              return "TODO";
            },
          };
        },
      },
    }),
  ],
});
```
</Tab>
<Tab title="Angular" value="angular">
```tsx check=false reason="script-tag example relies on globals provided by loaded SuperTokens bundles"
// 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: [
    // other recipes...
    supertokensUITOTP.init(),
    supertokensUIMultiFactorAuth.init(),
    supertokensUIMultitenancy.init({
      override: {
        functions: (originalImplementation) => {
          return {
            ...originalImplementation,
            getTenantId: async (context) => {
              return "TODO";
            },
          };
        },
      },
    }),
  ],
});
```
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Angular" value="angular">
This change goes in the `supertokens-web-js` SDK config at the root of your application:
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-prebuilt-ui">
<Tab title="Angular" value="angular">
```tsx check=false reason="script-tag example relies on globals provided by loaded SuperTokens bundles"
supertokens.init({
  appInfo: {
    apiDomain: "...",
    apiBasePath: "...",
    appName: "...",
  },
  recipeList: [Session.init(), MultiFactorAuth.init()],
});
```
</Tab>
</CodeGroup>

- Just like on the backend, we init the `totp` recipe in the `recipeList`.
- We also init the `MultiFactorAuth` recipe. Notice that unlike the single tenant setup, we do not specify the `firstFactors` here. That information is fetched based on the tenantId you provide the SDK with.
- We have set `usesDynamicLoginMethods: true` so that the SDK knows to fetch the login methods dynamically based on the tenantId.
- Finally, we init the multi tenancy recipe and provide a method for getting the tenantId. 

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

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Angular" value="angular">
:::success[This step is not required for non React apps, since all the pre-built UI components are already added into the bundle.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
<DependentContent group="react-router" label="Do you use react-router-dom?">
<ContentOption title="With React Router" value="yes">
```tsx
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>
  );
}
```
</ContentOption>
<ContentOption title="Without React Router" value="no">
```tsx
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>;
}
```
</ContentOption>
</DependentContent>
</Tab>
<Tab title="Angular" value="angular">

</Tab>
</CodeGroup>

With the above configuration, users will see the first and second factor based on the tenant configuration. For the tenant we configured above, users will see email password or social login first. 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:
- [Factor Setup UI](https://6571be2867f75556541fde98-xieqfaxuuo.chromatic.com/?path=/story/totp-mfa--device-setup-with-single-next-option)
- [Verification UI](https://6571be2867f75556541fde98-xieqfaxuuo.chromatic.com/?path=/story/totp-mfa--verification-with-single-next-option) (In case the factor is already setup before).

</VariantContent>

<VariantContent storageKey="ui-type" value="custom">

The steps here are the same as in [the single tenant setup above](#2-configure-the-frontend).

</VariantContent>

</VariantContent>
