Skip to main content
caution

This is the legacy method of implementing MFA. It has several disadvantages compared to using our MFA recipe.

2) Showing the first and second factor UI

First factor UI#

You should see the third party + email password login UI when you visit <YOUR_WEBSITE_DOMAIN>/auth.

No further step is required for the first factor.

Create and add the SecondFactor claim validator#

We will create a custom claim called SecondFactorClaim which will be responsible to check if the second factor has been completed or not. Then we can use the result in various places to protect frontend routes (in the subsequent steps).

Create a file called SecondFactorClaim.tsx in which you can add the following code:

import { BooleanClaim } from "supertokens-auth-react/recipe/session";

const SecondFactorClaim = new BooleanClaim({
id: "2fa-completed",
refresh: async () => {
// This is something we have no way of refreshing, so this is a no-op
},
onFailureRedirection: () => "/second-factor",
});

export default SecondFactorClaim

Then in the main session.init in the supertokens.init block, add this claim's validator so that it runs the check on each route.

import React from 'react';

import SuperTokens from "supertokens-auth-react";
import Session from "supertokens-auth-react/recipe/session";
import SecondFactorClaim from "./SecondFactorClaim";
import MultiFactorAuth from "supertokens-auth-react/recipe/multifactorauth";

SuperTokens.init({
appInfo: {
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth"
},
recipeList: [
// other recipes..
Session.init({
override: {
functions: (oI) => ({
...oI,
getGlobalClaimValidators: ({ claimValidatorsAddedByOtherRecipes }) => {
return [
SecondFactorClaim.validators.isTrue(),
...claimValidatorsAddedByOtherRecipes.filter(
(v) => v.id !== MultiFactorAuth.MultiFactorAuthClaim.id
),
];
},
}),
},
})
]
});

In the above, we add the SecondFactorClaim.validators.isTrue() validator so that whenever you use SessionAuth, we check that the second factor claim is set to true, if not, it will redirect to /second-factor (as defined in the claim validator). We also remove the in build MultiFactorAuth.MultiFactorAuthClaim since we are not using the full in built MFA recipe (as this is the legacy method).

Second factor UI#

For this guide, we will be showing the second factor UI on /second-factor.

1) Create the second factor UI on /second-factor#

Copy & paste the code for the second-factor UI from our demo app right here.

This component customises the AuthPageTheme component to:

  • Renders the pre built AuthPage with passwordless, otp-phone factor.
  • Add a button to "login with another account" which allows users to redo the first factor.
  • Redirects the user to the / route in case the second factor has already been completed.
  • Auto sends the OTP to the user in case they are signing in and we already know their phone number.

2) Override the passwordless UI components to change the header text and disable the change phone number button#

Copy / Paste the following override customisations in the Passwordless.init function call:

import React from "react";
import { SuperTokensWrapper } from "supertokens-auth-react";
import { PasswordlessComponentsOverrideProvider } from "supertokens-auth-react/recipe/passwordless";
import { useSessionContext } from "supertokens-auth-react/recipe/session";
import { AuthRecipeComponentsOverrideContextProvider } from "supertokens-auth-react/ui";


function App() {
return (
<SuperTokensWrapper>
<AuthRecipeComponentsOverrideContextProvider
components={{
AuthPageHeader_Override: ({ DefaultComponent, ...props }) => {
if (props.factorIds.includes("otp-phone")) {
<div
style={{
fontSize: "30px",
marginBottom: "10px",
}}>
Second factor auth
</div>;
}
return <DefaultComponent {...props} />;
},
}}>
<PasswordlessComponentsOverrideProvider
components={{
// we override the component which shows the change phone number button
PasswordlessUserInputCodeFormFooter_Override: ({ DefaultComponent, ...props }) => {
const session = useSessionContext();

if (session.loading !== true && session.accessTokenPayload.phoneNumber === undefined) {
// this will show the change phone number button
return <DefaultComponent {...props} />;
}

// this will hide the change phone number button
return null;
},
}}>
{/* Rest of the JSX */}
</PasswordlessComponentsOverrideProvider>
</AuthRecipeComponentsOverrideContextProvider>
</SuperTokensWrapper>
);
}
export default App;

3) Display the second factor component on your app's router#

Finally, we add the custom component we copy / pasted before to our router:

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

import SecondFactor from "./SecondFactor";

function App() {
return (
<SuperTokensWrapper>
<div className="App">
<div className="fill">
<Routes>
{getSuperTokensRoutesForReactRouterDom(reactRouterDom, [ThirdPartyPreBuiltUI, EmailPasswordPreBuiltUI, PasswordlessPreBuiltUI, MultiFactorAuthPreBuiltUI])}
<Route
path="/second-factor"
element={
<SessionAuth key="/second-factor">
<SecondFactor />
</SessionAuth>
}
/>
</Routes>
</div>
</div>
</SuperTokensWrapper>
);
}