Skip to main content

2) Showing the first and second factor UI

caution
  • SuperTokens is not yet optimised for 2FA implementation, so you have to add a lot of customisations for it to work. We are working on improving the development experience for 2FA as well as adding more factors like TOTP. Stay tuned.
  • A demo app that uses the pre built UI can be found on our GitHub.

First factor UI#

If you have correctly followed the frontend setup guide in the ThirdPartyEmailPassword recipe, you should see the login UI when you visit <YOUR_WEBSITE_DOMAIN>/auth?rid=thirdpartyemailpassword.

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
},
});

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";

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 [...claimValidatorsAddedByOtherRecipes, SecondFactorClaim.validators.isTrue()];
},
}),
},
})
]
});

Second factor UI#

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

1) Disable the default UI#

import SuperTokens from "supertokens-auth-react";
import Passwordless from "supertokens-auth-react/recipe/passwordless";

SuperTokens.init({
appInfo: {
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth"
},
recipeList: [
Passwordless.init({
contactMethod: "PHONE",
signInUpFeature: {
// this will prevent the passwordless UI from being rendered when you
// visit <YOUR_WEBSITE_DOMAIN>/auth?rid=passwordless
disableDefaultUI: true,
},
})
],
})

2) 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 Passwordless.SignInUpTheme component to:

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

3) 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 SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import Passwordless, { PasswordlessComponentsOverrideProvider } from "supertokens-auth-react/recipe/passwordless";
import { useSessionContext } from "supertokens-auth-react/recipe/session";

SuperTokens.init({
appInfo: {
appName: "<YOUR_APP_NAME>",
apiDomain: "<YOUR_API_DOMAIN>",
websiteDomain: "<YOUR_WEBSITE_DOMAIN>",
apiBasePath: "/auth",
websiteBasePath: "/auth"
},
recipeList: [
Passwordless.init({
contactMethod: "PHONE",
signInUpFeature: {
// this will prevent the passwordless UI from being rendered when you
// visit <YOUR_WEBSITE_DOMAIN>/auth?rid=passwordless
disableDefaultUI: true,
},
})
],
})

function App() {
return (
<SuperTokensWrapper>
<PasswordlessComponentsOverrideProvider
components={{
PasswordlessSignInUpHeader_Override: () => {
return (
<div
style={{
fontSize: "30px",
marginBottom: "10px",
}}>
Second factor auth
</div>
);
},
// 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>
</SuperTokensWrapper>
);
}
export default App;

4) Disaplay 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 { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui";
import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui";
import SecondFactor from "./SecondFactor";

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