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 UIYou 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.
SecondFactor
claim validator#
Create and add the 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 UIFor this guide, we will be showing the second factor UI on /second-factor
.
/second-factor
#
1) Create the second factor UI on 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 buttonCopy / 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 routerFinally, 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 { 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])}
<Route
path="/second-factor"
element={
<SessionAuth key="/second-factor">
<SecondFactor />
</SessionAuth>
}
/>
</Routes>
</div>
</div>
</SuperTokensWrapper>
);
}