---
title: Embed the pre-built UI component
description: Embed and customize a prebuilt MFA UI component.
sidebar:
  order: 90
---

## Overview

## Before you start

These instructions only apply to interfaces that use the pre-built UI components.
If you are using a custom UI, the embed instructions depend on your implementation details.

The tutorial configures `TOTP` as a secondary factor, but the same set of steps are applicable for other secondary factor types.

--- 

## Render the TOTP Widget in a page

The following example shows the scenario where you have a dedicated route, such as `/totp`, for rendering the TOTP Widget. Upon a successful login, the user will be automatically redirected to the return value of `getRedirectionURL` (defaulting to `/`).

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
#### With React Router

##### React Router v6
</ContentOption>
<ContentOption title="Angular" value="angular">
:::warning[Not applicable to non-react apps. Please build your own custom UI instead.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx check=false reason="application example imports local modules defined elsewhere"
import SuperTokens from "supertokens-auth-react";
import TOTP from "supertokens-auth-react/recipe/totp";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import { MFATOTP } from "supertokens-auth-react/recipe/totp/prebuiltui";
import Header from "./header";
import Footer from "./footer";
import { useNavigate } from "react-router-dom";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [
    TOTP.init({
      totpMFAScreen: {
        disableDefaultUI: true,
      },
    }),
    MultiFactorAuth.init({
      getRedirectionURL: async (context) => {
        if (context.action === "GO_TO_FACTOR") {
          if (context.factorId === "totp") {
            return "/totp";
          }
        }
      },
    }),
    // ...
  ],
});

function TOTPPage() {
  const navigate = useNavigate();
  return (
    <div>
      <Header />
      <MFATOTP navigate={navigate} />
      <Footer />
    </div>
  );
}
```
</Tab>
<Tab title="Angular" value="angular">

</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
##### React Router v5
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx check=false reason="application example imports local modules defined elsewhere"
import React from "react";
import SuperTokens from "supertokens-auth-react";
import TOTP from "supertokens-auth-react/recipe/totp";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import { MFATOTP } from "supertokens-auth-react/recipe/totp/prebuiltui";
import Header from "./header";
import Footer from "./footer";
import { useHistory } from "react-router-dom5";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [
    TOTP.init({
      totpMFAScreen: {
        disableDefaultUI: true,
      },
    }),
    MultiFactorAuth.init({
      getRedirectionURL: async (context) => {
        if (context.action === "GO_TO_FACTOR") {
          if (context.factorId === "totp") {
            return "/totp";
          }
        }
      },
    }),
    // ...
  ],
});

function TOTPPage() {
  const history = useHistory();
  return (
    <div>
      <Header />
      <MFATOTP navigate={history} />
      <Footer />
    </div>
  );
}
```
</Tab>
</CodeGroup>


<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
#### Without React Router
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx check=false reason="application example imports local modules defined elsewhere"
import React from "react";
import SuperTokens from "supertokens-auth-react";
import TOTP from "supertokens-auth-react/recipe/totp";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import { MFATOTP } from "supertokens-auth-react/recipe/totp/prebuiltui";
import Header from "./header";
import Footer from "./footer";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [
    TOTP.init({
      totpMFAScreen: {
        disableDefaultUI: true,
      },
    }),
    MultiFactorAuth.init({
      getRedirectionURL: async (context) => {
        if (context.action === "GO_TO_FACTOR") {
          if (context.factorId === "totp") {
            return "/totp";
          }
        }
      },
    }),
    // ...
  ],
});

function TOTPPage() {
  return (
    <div>
      <Header />
      <MFATOTP />
      <Footer />
    </div>
  );
}
```
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
In the above code snippet, we:

1. Disabled the default TOTP UI by setting `disableDefaultUI` to `true` inside the TOTP recipe config.
2. Overrode the `getRedirectionURL` function inside the MFA recipe config to redirect to `/totp` whenever we want to show the TOTP factor.

Feel free to customize the redirection URLs as needed.
</ContentOption>
</DependentContent>


---

## Render the TOTP Widget in a popup

The following example shows the scenario where you embed the TOTP Widget in a popup, and upon successful login, you aim to close the popup. This is especially useful for step up auth.

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
#### With React Router

##### React Router v6
</ContentOption>
<ContentOption title="Angular" value="angular">
:::warning[Not applicable to non-react apps. Please build your own custom UI instead.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
import React, { useEffect, useRef, useState } from "react";
import Modal from "react-modal";
import SuperTokens from "supertokens-auth-react";
import TOTP from "supertokens-auth-react/recipe/totp";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import { MFATOTP } from "supertokens-auth-react/recipe/totp/prebuiltui";
import Session from "supertokens-auth-react/recipe/session";
import { useLocation, useNavigate } from "react-router-dom";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [
    TOTP.init(/* ... */),
    MultiFactorAuth.init(/* ... */),
    // ...
  ],
});

function TOTPPopup() {
  const sessionContext = Session.useSessionContext();
  const navigate = useNavigate();
  const location = useLocation();
  const retryStarted = useRef(false);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [error, setError] = useState<string>();

  const openModal = () => {
    const url = new URL(window.location.href);
    const returnUrl = new URL(url);
    returnUrl.searchParams.delete("stepUp");
    returnUrl.searchParams.delete("redirectToPath");
    returnUrl.searchParams.set("retryProtectedOperation", "true");

    url.searchParams.delete("retryProtectedOperation");
    url.searchParams.set("stepUp", "true");
    url.searchParams.set("redirectToPath", `${returnUrl.pathname}${returnUrl.search}${returnUrl.hash}`);
    window.history.replaceState(window.history.state, "", url);
    retryStarted.current = false;
    setError(undefined);
    setIsModalOpen(true);
  };

  const cancelModal = () => {
    const url = new URL(window.location.href);
    url.searchParams.delete("stepUp");
    url.searchParams.delete("redirectToPath");
    url.searchParams.delete("retryProtectedOperation");
    window.history.replaceState(window.history.state, "", url);
    setIsModalOpen(false);
  };

  useEffect(() => {
    const params = new URLSearchParams(location.search);
    if (params.get("retryProtectedOperation") !== "true" || retryStarted.current) {
      return;
    }

    retryStarted.current = true;
    void (async () => {
      try {
        await MultiFactorAuth.resyncSessionAndFetchMFAInfo();
        const response = await fetch("/api/sensitive-operation", { method: "POST" });
        if (!response.ok) {
          throw new Error("The protected operation was rejected");
        }

        const url = new URL(window.location.href);
        url.searchParams.delete("retryProtectedOperation");
        navigate(`${url.pathname}${url.search}${url.hash}`, { replace: true });
        setIsModalOpen(false);
      } catch (error) {
        setError(error instanceof Error ? error.message : "The protected operation failed");
      }
    })();
  }, [location.search, navigate]);

  if (sessionContext.loading) {
    return null;
  }

  return (
    <div style={{ textAlign: "center" }}>
      {
        <Session.SessionAuth>
          <h2>You are logged In! </h2>
          <h3>UserId: {sessionContext.userId}</h3>
          <button onClick={openModal}>Verify with TOTP</button>
          <button onClick={() => Session.signOut()}>Sign Out</button>
        </Session.SessionAuth>
      }

      {error !== undefined && <p role="alert">{error}</p>}

      <Modal isOpen={isModalOpen} onRequestClose={cancelModal} contentLabel="TOTP challenge">
        <button onClick={cancelModal}>Cancel</button>
        <MFATOTP navigate={navigate} />
      </Modal>
    </div>
  );
}
```
</Tab>
<Tab title="Angular" value="angular">

</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
##### React Router v5
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
import React, { useEffect, useRef, useState } from "react";
import Modal from "react-modal";
import SuperTokens from "supertokens-auth-react";
import TOTP from "supertokens-auth-react/recipe/totp";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import { MFATOTP } from "supertokens-auth-react/recipe/totp/prebuiltui";
import Session from "supertokens-auth-react/recipe/session";
import { useHistory, useLocation } from "react-router-dom5";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [
    TOTP.init(/* ... */),
    MultiFactorAuth.init(/* ... */),
    // ...
  ],
});

function TOTPPopup() {
  const sessionContext = Session.useSessionContext();
  const history = useHistory();
  const location = useLocation();
  const retryStarted = useRef(false);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [error, setError] = useState<string>();

  const openModal = () => {
    const url = new URL(window.location.href);
    const returnUrl = new URL(url);
    returnUrl.searchParams.delete("stepUp");
    returnUrl.searchParams.delete("redirectToPath");
    returnUrl.searchParams.set("retryProtectedOperation", "true");

    url.searchParams.delete("retryProtectedOperation");
    url.searchParams.set("stepUp", "true");
    url.searchParams.set("redirectToPath", `${returnUrl.pathname}${returnUrl.search}${returnUrl.hash}`);
    window.history.replaceState(window.history.state, "", url);
    retryStarted.current = false;
    setError(undefined);
    setIsModalOpen(true);
  };

  const cancelModal = () => {
    const url = new URL(window.location.href);
    url.searchParams.delete("stepUp");
    url.searchParams.delete("redirectToPath");
    url.searchParams.delete("retryProtectedOperation");
    window.history.replaceState(window.history.state, "", url);
    setIsModalOpen(false);
  };

  useEffect(() => {
    const params = new URLSearchParams(location.search);
    if (params.get("retryProtectedOperation") !== "true" || retryStarted.current) {
      return;
    }

    retryStarted.current = true;
    void (async () => {
      try {
        await MultiFactorAuth.resyncSessionAndFetchMFAInfo();
        const response = await fetch("/api/sensitive-operation", { method: "POST" });
        if (!response.ok) {
          throw new Error("The protected operation was rejected");
        }

        const url = new URL(window.location.href);
        url.searchParams.delete("retryProtectedOperation");
        history.replace(`${url.pathname}${url.search}${url.hash}`);
        setIsModalOpen(false);
      } catch (error) {
        setError(error instanceof Error ? error.message : "The protected operation failed");
      }
    })();
  }, [history, location.search]);

  if (sessionContext.loading) {
    return null;
  }

  return (
    <div style={{ textAlign: "center" }}>
      {
        <Session.SessionAuth>
          <h2>You are logged In! </h2>
          <h3>UserId: {sessionContext.userId}</h3>
          <button onClick={openModal}>Verify with TOTP</button>
          <button onClick={() => Session.signOut()}>Sign Out</button>
        </Session.SessionAuth>
      }

      {error !== undefined && <p role="alert">{error}</p>}

      <Modal isOpen={isModalOpen} onRequestClose={cancelModal} contentLabel="TOTP challenge">
        <button onClick={cancelModal}>Cancel</button>
        <MFATOTP navigate={history} />
      </Modal>
    </div>
  );
}
```
</Tab>
</CodeGroup>

<DependentContent passive group="frontend-prebuilt-ui">
<ContentOption title="Reactjs" value="reactjs">
#### Without React Router
</ContentOption>
</DependentContent>

<CodeGroup passive group="frontend-prebuilt-ui">
<Tab title="Reactjs" value="reactjs">
```tsx
import React, { useEffect, useRef, useState } from "react";
import Modal from "react-modal";
import SuperTokens from "supertokens-auth-react";
import TOTP from "supertokens-auth-react/recipe/totp";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";
import { MFATOTP } from "supertokens-auth-react/recipe/totp/prebuiltui";
import Session from "supertokens-auth-react/recipe/session";

SuperTokens.init({
  appInfo: {
    apiDomain: "...",
    appName: "...",
    websiteDomain: "...",
  },
  recipeList: [
    TOTP.init(/* ... */),
    MultiFactorAuth.init(/* ... */),
    // ...
  ],
});

function TOTPPopup() {
  const sessionContext = Session.useSessionContext();
  const retryStarted = useRef(false);
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [error, setError] = useState<string>();

  const openModal = () => {
    const url = new URL(window.location.href);
    const returnUrl = new URL(url);
    returnUrl.searchParams.delete("stepUp");
    returnUrl.searchParams.delete("redirectToPath");
    returnUrl.searchParams.set("retryProtectedOperation", "true");

    url.searchParams.delete("retryProtectedOperation");
    url.searchParams.set("stepUp", "true");
    url.searchParams.set("redirectToPath", `${returnUrl.pathname}${returnUrl.search}${returnUrl.hash}`);
    window.history.replaceState(window.history.state, "", url);
    retryStarted.current = false;
    setError(undefined);
    setIsModalOpen(true);
  };

  const cancelModal = () => {
    const url = new URL(window.location.href);
    url.searchParams.delete("stepUp");
    url.searchParams.delete("redirectToPath");
    url.searchParams.delete("retryProtectedOperation");
    window.history.replaceState(window.history.state, "", url);
    setIsModalOpen(false);
  };

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    if (params.get("retryProtectedOperation") !== "true" || retryStarted.current) {
      return;
    }

    retryStarted.current = true;
    void (async () => {
      try {
        await MultiFactorAuth.resyncSessionAndFetchMFAInfo();
        const response = await fetch("/api/sensitive-operation", { method: "POST" });
        if (!response.ok) {
          throw new Error("The protected operation was rejected");
        }

        const url = new URL(window.location.href);
        url.searchParams.delete("retryProtectedOperation");
        window.history.replaceState(window.history.state, "", url);
        setIsModalOpen(false);
      } catch (error) {
        setError(error instanceof Error ? error.message : "The protected operation failed");
      }
    })();
  }, []);

  if (sessionContext.loading) {
    return null;
  }

  return (
    <div style={{ textAlign: "center" }}>
      {
        <Session.SessionAuth>
          <h2>You are logged In! </h2>
          <h3>UserId: {sessionContext.userId}</h3>
          <button onClick={openModal}>Verify with TOTP</button>
          <button onClick={() => Session.signOut()}>Sign Out</button>
        </Session.SessionAuth>
      }

      {error !== undefined && <p role="alert">{error}</p>}

      <Modal isOpen={isModalOpen} onRequestClose={cancelModal} contentLabel="TOTP challenge">
        <button onClick={cancelModal}>Cancel</button>
        <MFATOTP />
      </Modal>
    </div>
  );
}
```
</Tab>
</CodeGroup>

The `retryProtectedOperation` return marker is not proof that step-up authentication succeeded. After the factor flow returns, call `MultiFactorAuth.resyncSessionAndFetchMFAInfo()` to synchronize the session and retry the server-protected operation. The MFA freshness validator on the backend is authoritative and must reject the operation if the required factor is missing or too old. The **Cancel** button only cancels the popup; it must not retry or authorize the operation.
