Setting Up Multi-Factor Authentication

With Account Kit, multi-factor authentication (MFA) uses authenticator apps—like Google Authenticator, Authy, or Microsoft Authenticator—to generate a Time-based One-Time Password (TOTP).

By requiring both a user’s primary login (e.g., email OTP, magic link, or social login) and a TOTP from their authenticator app, your application gains an extra layer of security.

Multi-factor authentication requires a primary authentication method (Email OTP, Email Magic Link, or Social Login) to be already set up. See the React Quickstart guide to set up your primary authentication method first.

Prerequisites

Before implementing MFA, you need to have:

  1. Set up primary authentication - MFA requires a primary authentication method to be already set up. Follow the React Quickstart guide to configure email (OTP or magic link) or social login.
  2. Working authentication flow - Ensure users can successfully sign in with your primary authentication method.

Implementation

To implement authenticator app verification in your React application, you’ll use the useMFA hook from Account Kit.

Step 1: Checking if Multi-Factor Authentication is Available

First, check if the user is logged in and able to edit their MFA settings:

1import React from "react";
2import { useMFA } from "@account-kit/react";
3
4// Inside your component
5const { isReady } = useMFA();
6
7// Only show MFA setup options if available
8if (isReady) {
9 // Render MFA setup UI
10} else {
11 // User needs to authenticate first
12}

Step 2: Setting Up an Authenticator App

In this step, we receive a QR code URL from the backend and display it to the user. This URL contains a TOTP seed and necessary metadata. When displayed as a QR code, it can be scanned by most authenticator apps (Google Authenticator, Authy, 1Password, etc.) to set up 6-digit time-based codes. The backend also provides a unique multiFactorId which we’ll need to store for the verification step.

1import React, { useState } from "react";
2import { QRCodeSVG } from "qrcode.react";
3import { useMFA } from "@account-kit/react";
4
5function AuthenticatorSetupComponent() {
6 const { addMFA } = useMFA();
7 const [qrCodeUrl, setQrCodeUrl] = useState("");
8 const [factorId, setFactorId] = useState("");
9
10 const handleSetupAuthenticator = () => {
11 // Use the mutate method from the mutation result
12 addMFA.mutate(
13 {
14 multiFactorType: "totp", // Technical name for authenticator apps
15 },
16 {
17 onSuccess: (result) => {
18 // Store the QR code URL and factor ID
19 setQrCodeUrl(result.multiFactorTotpUrl);
20 // Store the factor ID which will be needed for verification in the next step
21 // This ID uniquely identifies the MFA factor being configured
22 setFactorId(result.multiFactorId);
23 },
24 onError: (error) => {
25 console.error("Failed to set up authenticator app:", error);
26 },
27 }
28 );
29 };
30
31 // You can also check loading state directly
32 const isLoading = addMFA.isPending;
33
34 return (
35 <div>
36 <button onClick={handleSetupAuthenticator} disabled={isLoading}>
37 {isLoading ? "Setting up..." : "Set Up Authenticator App"}
38 </button>
39
40 {qrCodeUrl && (
41 <div className="qr-container">
42 <h3>Scan this QR code with your authenticator app</h3>
43 <QRCodeSVG value={qrCodeUrl} size={200} />
44 <p>
45 After scanning, enter the 6-digit code from your authenticator app
46 to complete setup.
47 </p>
48 </div>
49 )}
50
51 {/* Display errors if they occur */}
52 {addMFA.isError && (
53 <div className="error">Error: {addMFA.error.message}</div>
54 )}
55 </div>
56 );
57}

This QR code contains the information needed for apps like Google Authenticator or Authy. Once scanned, the app will generate 6-digit codes that users can use as their second verification step.

Step 3: Confirming the Authenticator App Setup

After the user scans the QR code, they need to prove it worked by entering a code:

1import React, { useState } from "react";
2import { useMFA } from "@account-kit/react";
3
4function VerifyAuthenticatorComponent({
5 multiFactorId,
6}: {
7 multiFactorId: string;
8}) {
9 const { verifyMFA } = useMFA();
10 const [code, setCode] = useState("");
11
12 const handleVerifyAuthenticator = () => {
13 verifyMFA.mutate(
14 {
15 multiFactorId,
16 multiFactorCode: code, // The TOTP code from the user's authenticator app
17 },
18 {
19 onSuccess: () => {
20 // Authenticator setup successful
21 console.log("MFA setup complete!");
22 },
23 onError: (error) => {
24 // Handle error
25 console.error("Verification failed:", error);
26 },
27 }
28 );
29 };
30
31 // For async/await pattern, you can use mutateAsync
32 const handleVerifyAsync = async () => {
33 try {
34 const result = await verifyMFA.mutateAsync({
35 multiFactorId,
36 multiFactorCode: code,
37 });
38 console.log("MFA setup complete!", result);
39 } catch (error) {
40 console.error("Verification failed:", error);
41 }
42 };
43
44 return (
45 <div>
46 <input
47 type="text"
48 value={code}
49 onChange={(e) => setCode(e.target.value)}
50 placeholder="Enter 6-digit code"
51 maxLength={6}
52 />
53 <button
54 onClick={handleVerifyAuthenticator}
55 disabled={verifyMFA.isPending}
56 >
57 {verifyMFA.isPending ? "Verifying..." : "Verify Code"}
58 </button>
59
60 {verifyMFA.isError && (
61 <div className="error">Invalid code. Please try again.</div>
62 )}
63 </div>
64 );
65}

Step 4: Managing Authenticator Apps

You can retrieve and remove authenticator app–based MFA from a user’s account by using the useMFA hook. Each verification method (also called a “factor”) is identified by a unique multiFactorId. For example, a TOTP-based authenticator app is one factor.

1import React, { useEffect, useState } from "react";
2import { useMFA } from "@account-kit/react";
3import type { MfaFactor } from "@account-kit/signer";
4
5function ManageMfaComponent() {
6 const { getMFAFactors, removeMFA } = useMFA();
7 const [factors, setFactors] = useState<MfaFactor[]>([]);
8
9 // Fetch all MFA verification methods (factors) for the current user
10 useEffect(() => {
11 // Only fetch when component mounts and we're ready
12 getMFAFactors.mutate(undefined, {
13 onSuccess: (result) => {
14 // factors.multiFactors is an array of verification methods
15 setFactors(result.multiFactors);
16 },
17 });
18 }, [getMFAFactors]);
19
20 // Remove a TOTP authenticator app by its multiFactorId
21 const handleRemoveAuthenticator = (multiFactorId: string) => {
22 removeMFA.mutate(
23 { multiFactorIds: [multiFactorId] },
24 {
25 onSuccess: () => {
26 console.log("Authenticator removed successfully!");
27 // Update local state to reflect the removal
28 setFactors(factors.filter((f) => f.multiFactorId !== multiFactorId));
29 },
30 }
31 );
32 };
33
34 // Loading states are available directly from the mutation objects
35 if (getMFAFactors.isPending) return <div>Loading MFA settings...</div>;
36
37 return (
38 <div>
39 <h2>Your Authentication Methods</h2>
40
41 {factors.length === 0 ? (
42 <p>No authenticator apps configured.</p>
43 ) : (
44 <ul>
45 {factors.map((factor) => (
46 <li key={factor.multiFactorId}>
47 {factor.multiFactorType === "totp"
48 ? "Authenticator App"
49 : factor.multiFactorType}
50 <button
51 onClick={() => handleRemoveAuthenticator(factor.multiFactorId)}
52 disabled={removeMFA.isPending}
53 >
54 Remove
55 </button>
56 </li>
57 ))}
58 </ul>
59 )}
60
61 {getMFAFactors.isError && (
62 <div className="error">
63 Error loading MFA settings: {getMFAFactors.error.message}
64 </div>
65 )}
66 </div>
67 );
68}

Next Steps

After setting up an authenticator app, users will need to provide both their primary authentication method and a 6-digit code when signing in:

Using Pre-built UI Components

If you’re using the pre-built UI components, the MFA verification process is handled automatically:

  • The authentication flow will detect when a user has MFA enabled
  • Users will be prompted for their authenticator app code after providing their primary credentials
  • No additional code is required from you

Using Custom UI with Hooks

If you’re implementing custom UI with hooks, you’ll need to update your authentication code to handle the MFA verification step:

For custom UI implementations, make sure your authentication logic checks for the MFA requirement and provides UI for users to enter their authenticator app code.