Setting Up Multi-Factor Authentication
With Smart Wallets, 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:
- 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.
- 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 Smart Wallets.
Step 1: Checking if Multi-Factor Authentication is Available
First, check if the user is logged in and able to edit their MFA settings:
import React from "react";
import { function useMFA(): UseMFAResultHook that provides functions and state for managing multi-factor authentication (MFA) operations. Handles adding, verifying, removing, and getting MFA factors for an authenticated account.
The hook checks if the signer is connected before allowing MFA operations and provides an isReady
flag to indicate whether MFA operations can be performed.
useMFA } from "@account-kit/react";
// Inside your component
const { const isReady: booleanisReady } = function useMFA(): UseMFAResultHook that provides functions and state for managing multi-factor authentication (MFA) operations. Handles adding, verifying, removing, and getting MFA factors for an authenticated account.
The hook checks if the signer is connected before allowing MFA operations and provides an isReady
flag to indicate whether MFA operations can be performed.
useMFA();
// Only show MFA setup options if available
if (const isReady: booleanisReady) {
// Render MFA setup UI
} else {
// User needs to authenticate first
}
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.
import React, { function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.
useState } from "react";
import { const QRCodeSVG: React.ForwardRefExoticComponent<QRProps & React.SVGAttributes<SVGSVGElement> & React.RefAttributes<SVGSVGElement>>QRCodeSVG } from "qrcode.react";
import { function useMFA(): UseMFAResultHook that provides functions and state for managing multi-factor authentication (MFA) operations. Handles adding, verifying, removing, and getting MFA factors for an authenticated account.
The hook checks if the signer is connected before allowing MFA operations and provides an isReady
flag to indicate whether MFA operations can be performed.
useMFA } from "@account-kit/react";
function function AuthenticatorSetupComponent(): JSX.ElementAuthenticatorSetupComponent() {
const { const addMFA: UseMutationResult<AddMfaResult, Error, AddMfaParams>addMFA } = function useMFA(): UseMFAResultHook that provides functions and state for managing multi-factor authentication (MFA) operations. Handles adding, verifying, removing, and getting MFA factors for an authenticated account.
The hook checks if the signer is connected before allowing MFA operations and provides an isReady
flag to indicate whether MFA operations can be performed.
useMFA();
const [const qrCodeUrl: stringqrCodeUrl, const setQrCodeUrl: React.Dispatch<React.SetStateAction<string>>setQrCodeUrl] = useState<string>(initialState: string | (() => string)): [string, React.Dispatch<React.SetStateAction<string>>] (+1 overload)Returns a stateful value, and a function to update it.
useState("");
const [const factorId: stringfactorId, const setFactorId: React.Dispatch<React.SetStateAction<string>>setFactorId] = useState<string>(initialState: string | (() => string)): [string, React.Dispatch<React.SetStateAction<string>>] (+1 overload)Returns a stateful value, and a function to update it.
useState("");
const const handleSetupAuthenticator: () => voidhandleSetupAuthenticator = () => {
// Use the mutate method from the mutation result
const addMFA: UseMutationResult<AddMfaResult, Error, AddMfaParams>addMFA.mutate: (variables: AddMfaParams, options?: MutateOptions<AddMfaResult, Error, AddMfaParams, unknown> | undefined) => voidThe mutation function you can call with variables to trigger the mutation and optionally hooks on additional callback options.
mutate(
{
multiFactorType: "totp"multiFactorType: "totp", // Technical name for authenticator apps
},
{
MutateOptions<AddMfaResult, Error, AddMfaParams, unknown>.onSuccess?: ((data: AddMfaResult, variables: AddMfaParams, context: unknown) => void) | undefinedonSuccess: (result: AddMfaResultresult) => {
// Store the QR code URL and factor ID
const setQrCodeUrl: (value: React.SetStateAction<string>) => voidsetQrCodeUrl(result: AddMfaResultresult.multiFactorTotpUrl: stringmultiFactorTotpUrl);
// Store the factor ID which will be needed for verification in the next step
// This ID uniquely identifies the MFA factor being configured
const setFactorId: (value: React.SetStateAction<string>) => voidsetFactorId(result: AddMfaResultresult.multiFactorId: stringmultiFactorId);
},
MutateOptions<AddMfaResult, Error, AddMfaParams, unknown>.onError?: ((error: Error, variables: AddMfaParams, context: unknown) => void) | undefinedonError: (error: Errorerror) => {
var console: ConsoleThe console
module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream. * A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O
for more information.
Example using the global console
:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.error(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stderr
with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3)
(the arguments are all passed to util.format()
).
js const code = 5; console.error('error #%d', code); // Prints: error #5, to stderr console.error('error', code); // Prints: error 5, to stderr
If formatting elements (e.g. %d
) are not found in the first string then util.inspect()
is called on each argument and the resulting string values are concatenated. See util.format()
for more information.
error("Failed to set up authenticator app:", error: Errorerror);
},
},
);
};
// You can also check loading state directly
const const isLoading: booleanisLoading = const addMFA: UseMutationResult<AddMfaResult, Error, AddMfaParams>addMFA.isPending: booleanA boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
isPending;
return (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={const handleSetupAuthenticator: () => voidhandleSetupAuthenticator} React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefineddisabled={const isLoading: booleanisLoading}>
{const isLoading: booleanisLoading ? "Setting up..." : "Set Up Authenticator App"}
</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
{const qrCodeUrl: stringqrCodeUrl && (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="qr-container">
<React.JSX.IntrinsicElements.h3: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h3>Scan this QR code with your authenticator app</React.JSX.IntrinsicElements.h3: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h3>
<const QRCodeSVG: React.ForwardRefExoticComponent<QRProps & React.SVGAttributes<SVGSVGElement> & React.RefAttributes<SVGSVGElement>>QRCodeSVG value: string | string[]The value to encode into the QR Code. An array of strings can be passed in to represent multiple segments to further optimize the QR Code.
value={const qrCodeUrl: stringqrCodeUrl} size?: number | undefinedThe size, in pixels, to render the QR Code.
size={200} />
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
After scanning, enter the 6-digit code from your authenticator app
to complete setup.
</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
)}
{/* Display errors if they occur */}
{const addMFA: UseMutationResult<AddMfaResult, Error, AddMfaParams>addMFA.isError: booleanA boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
A boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
A boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
A boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
isError && (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="error">Error: {const addMFA: Override<MutationObserverErrorResult<AddMfaResult, Error, AddMfaParams, unknown>, {
mutate: UseMutateFunction<AddMfaResult, Error, AddMfaParams, unknown>;
}> & {
...;
}addMFA.error: ErrorThe error object for the mutation, if an error was encountered.
Defaults to null
.
error.Error.message: stringmessage}</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
)}
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
}
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:
import React, { function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.
useState } from "react";
import { function useMFA(): UseMFAResultHook that provides functions and state for managing multi-factor authentication (MFA) operations. Handles adding, verifying, removing, and getting MFA factors for an authenticated account.
The hook checks if the signer is connected before allowing MFA operations and provides an isReady
flag to indicate whether MFA operations can be performed.
useMFA } from "@account-kit/react";
function function VerifyAuthenticatorComponent({ multiFactorId, }: {
multiFactorId: string;
}): JSX.ElementVerifyAuthenticatorComponent({
multiFactorId: stringmultiFactorId,
}: {
multiFactorId: stringmultiFactorId: string;
}) {
const { const verifyMFA: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, VerifyMfaParams>verifyMFA } = function useMFA(): UseMFAResultHook that provides functions and state for managing multi-factor authentication (MFA) operations. Handles adding, verifying, removing, and getting MFA factors for an authenticated account.
The hook checks if the signer is connected before allowing MFA operations and provides an isReady
flag to indicate whether MFA operations can be performed.
useMFA();
const [const code: stringcode, const setCode: React.Dispatch<React.SetStateAction<string>>setCode] = useState<string>(initialState: string | (() => string)): [string, React.Dispatch<React.SetStateAction<string>>] (+1 overload)Returns a stateful value, and a function to update it.
useState("");
const const handleVerifyAuthenticator: () => voidhandleVerifyAuthenticator = () => {
const verifyMFA: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, VerifyMfaParams>verifyMFA.mutate: (variables: VerifyMfaParams, options?: MutateOptions<{
multiFactors: MfaFactor[];
}, Error, VerifyMfaParams, unknown> | undefined) => voidThe mutation function you can call with variables to trigger the mutation and optionally hooks on additional callback options.
mutate(
{
multiFactorId: stringmultiFactorId,
multiFactorCode: stringmultiFactorCode: const code: stringcode, // The TOTP code from the user's authenticator app
},
{
MutateOptions<{ multiFactors: MfaFactor[]; }, Error, VerifyMfaParams, unknown>.onSuccess?: ((data: {
multiFactors: MfaFactor[];
}, variables: VerifyMfaParams, context: unknown) => void) | undefinedonSuccess: () => {
// Authenticator setup successful
var console: ConsoleThe console
module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream. * A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O
for more information.
Example using the global console
:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stdout
with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3)
(the arguments are all passed to util.format()
).
js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout
See util.format()
for more information.
log("MFA setup complete!");
},
MutateOptions<{ multiFactors: MfaFactor[]; }, Error, VerifyMfaParams, unknown>.onError?: ((error: Error, variables: VerifyMfaParams, context: unknown) => void) | undefinedonError: (error: Errorerror) => {
// Handle error
var console: ConsoleThe console
module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream. * A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O
for more information.
Example using the global console
:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.error(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stderr
with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3)
(the arguments are all passed to util.format()
).
js const code = 5; console.error('error #%d', code); // Prints: error #5, to stderr console.error('error', code); // Prints: error 5, to stderr
If formatting elements (e.g. %d
) are not found in the first string then util.inspect()
is called on each argument and the resulting string values are concatenated. See util.format()
for more information.
error("Verification failed:", error: Errorerror);
},
},
);
};
// For async/await pattern, you can use mutateAsync
const const handleVerifyAsync: () => Promise<void>handleVerifyAsync = async () => {
try {
const const result: {
multiFactors: MfaFactor[];
}result = await const verifyMFA: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, VerifyMfaParams>verifyMFA.mutateAsync: (variables: VerifyMfaParams, options?: MutateOptions<{
multiFactors: MfaFactor[];
}, Error, VerifyMfaParams, unknown> | undefined) => Promise<...>mutateAsync({
multiFactorId: stringmultiFactorId,
multiFactorCode: stringmultiFactorCode: const code: stringcode,
});
var console: ConsoleThe console
module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream. * A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O
for more information.
Example using the global console
:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stdout
with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3)
(the arguments are all passed to util.format()
).
js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout
See util.format()
for more information.
log("MFA setup complete!", const result: {
multiFactors: MfaFactor[];
}result);
} catch (function (local var) error: unknownerror) {
var console: ConsoleThe console
module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream. * A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O
for more information.
Example using the global console
:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.error(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stderr
with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3)
(the arguments are all passed to util.format()
).
js const code = 5; console.error('error #%d', code); // Prints: error #5, to stderr console.error('error', code); // Prints: error 5, to stderr
If formatting elements (e.g. %d
) are not found in the first string then util.inspect()
is called on each argument and the resulting string values are concatenated. See util.format()
for more information.
error("Verification failed:", function (local var) error: unknownerror);
}
};
return (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
<React.JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>input
React.InputHTMLAttributes<HTMLInputElement>.type?: React.HTMLInputTypeAttribute | undefinedtype="text"
React.InputHTMLAttributes<HTMLInputElement>.value?: string | number | readonly string[] | undefinedvalue={const code: stringcode}
React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefinedonChange={(e: React.ChangeEvent<HTMLInputElement>e) => const setCode: (value: React.SetStateAction<string>) => voidsetCode(e: React.ChangeEvent<HTMLInputElement>e.React.ChangeEvent<HTMLInputElement>.target: EventTarget & HTMLInputElementtarget.HTMLInputElement.value: stringReturns the value of the data at the cursor's current position.
value)}
React.InputHTMLAttributes<HTMLInputElement>.placeholder?: string | undefinedplaceholder="Enter 6-digit code"
React.InputHTMLAttributes<HTMLInputElement>.maxLength?: number | undefinedmaxLength={6}
/>
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button
React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={const handleVerifyAuthenticator: () => voidhandleVerifyAuthenticator}
React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefineddisabled={const verifyMFA: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, VerifyMfaParams>verifyMFA.isPending: booleanA boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
isPending}
>
{const verifyMFA: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, VerifyMfaParams>verifyMFA.isPending: booleanA boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
isPending ? "Verifying..." : "Verify Code"}
</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
{const verifyMFA: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, VerifyMfaParams>verifyMFA.isError: booleanA boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
A boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
A boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
A boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
isError && (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="error">Invalid code. Please try again.</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
)}
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
}
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.
import React, { function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
useEffect, function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.
useState } from "react";
import { function useMFA(): UseMFAResultHook that provides functions and state for managing multi-factor authentication (MFA) operations. Handles adding, verifying, removing, and getting MFA factors for an authenticated account.
The hook checks if the signer is connected before allowing MFA operations and provides an isReady
flag to indicate whether MFA operations can be performed.
useMFA } from "@account-kit/react";
import type { type MfaFactor = {
multiFactorId: string;
multiFactorType: string;
}MfaFactor } from "@account-kit/signer";
function function ManageMfaComponent(): JSX.ElementManageMfaComponent() {
const { const getMFAFactors: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, void>getMFAFactors, const removeMFA: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, RemoveMfaParams>removeMFA } = function useMFA(): UseMFAResultHook that provides functions and state for managing multi-factor authentication (MFA) operations. Handles adding, verifying, removing, and getting MFA factors for an authenticated account.
The hook checks if the signer is connected before allowing MFA operations and provides an isReady
flag to indicate whether MFA operations can be performed.
useMFA();
const [const factors: MfaFactor[]factors, const setFactors: React.Dispatch<React.SetStateAction<MfaFactor[]>>setFactors] = useState<MfaFactor[]>(initialState: MfaFactor[] | (() => MfaFactor[])): [MfaFactor[], React.Dispatch<React.SetStateAction<MfaFactor[]>>] (+1 overload)Returns a stateful value, and a function to update it.
useState<type MfaFactor = {
multiFactorId: string;
multiFactorType: string;
}MfaFactor[]>([]);
// Fetch all MFA verification methods (factors) for the current user
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.
useEffect(() => {
// Only fetch when component mounts and we're ready
const getMFAFactors: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, void>getMFAFactors.mutate: (variables: void, options?: MutateOptions<{
multiFactors: MfaFactor[];
}, Error, void, unknown> | undefined) => voidThe mutation function you can call with variables to trigger the mutation and optionally hooks on additional callback options.
mutate(var undefinedundefined, {
MutateOptions<{ multiFactors: MfaFactor[]; }, Error, void, unknown>.onSuccess?: ((data: {
multiFactors: MfaFactor[];
}, variables: void, context: unknown) => void) | undefinedonSuccess: (result: {
multiFactors: MfaFactor[];
}result) => {
// factors.multiFactors is an array of verification methods
const setFactors: (value: React.SetStateAction<MfaFactor[]>) => voidsetFactors(result: {
multiFactors: MfaFactor[];
}result.multiFactors: MfaFactor[]multiFactors);
},
});
}, [const getMFAFactors: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, void>getMFAFactors]);
// Remove a TOTP authenticator app by its multiFactorId
const const handleRemoveAuthenticator: (multiFactorId: string) => voidhandleRemoveAuthenticator = (multiFactorId: stringmultiFactorId: string) => {
const removeMFA: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, RemoveMfaParams>removeMFA.mutate: (variables: RemoveMfaParams, options?: MutateOptions<{
multiFactors: MfaFactor[];
}, Error, RemoveMfaParams, unknown> | undefined) => voidThe mutation function you can call with variables to trigger the mutation and optionally hooks on additional callback options.
mutate(
{ multiFactorIds: string[]multiFactorIds: [multiFactorId: stringmultiFactorId] },
{
MutateOptions<{ multiFactors: MfaFactor[]; }, Error, RemoveMfaParams, unknown>.onSuccess?: ((data: {
multiFactors: MfaFactor[];
}, variables: RemoveMfaParams, context: unknown) => void) | undefinedonSuccess: () => {
var console: ConsoleThe console
module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream. * A global console
instance configured to write to process.stdout
and process.stderr
. The global console
can be used without importing the node:console
module.
Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O
for more information.
Example using the global console
:
const name = 'Will Robinson'; console.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ```
Example using the `Console` class:
```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err);
myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson'; myConsole.warn(`Danger $name! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)Prints to stdout
with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3)
(the arguments are all passed to util.format()
).
js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout
See util.format()
for more information.
log("Authenticator removed successfully!");
// Update local state to reflect the removal
const setFactors: (value: React.SetStateAction<MfaFactor[]>) => voidsetFactors(const factors: MfaFactor[]factors.Array<MfaFactor>.filter(predicate: (value: MfaFactor, index: number, array: MfaFactor[]) => unknown, thisArg?: any): MfaFactor[] (+1 overload)Returns the elements of an array that meet the condition specified in a callback function.
filter((f: MfaFactorf) => f: MfaFactorf.multiFactorId: stringmultiFactorId !== multiFactorId: stringmultiFactorId));
},
},
);
};
// Loading states are available directly from the mutation objects
if (const getMFAFactors: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, void>getMFAFactors.isPending: booleanA boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
isPending) return <React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>Loading MFA settings...</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>;
return (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
<React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>Your Authentication Methods</React.JSX.IntrinsicElements.h2: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>h2>
{const factors: MfaFactor[]factors.Array<MfaFactor>.length: numberGets or sets the length of the array. This is a number one higher than the highest index in the array.
length === 0 ? (
<React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>No authenticator apps configured.</React.JSX.IntrinsicElements.p: React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>p>
) : (
<React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul>
{const factors: MfaFactor[]factors.Array<MfaFactor>.map<JSX.Element>(callbackfn: (value: MfaFactor, index: number, array: MfaFactor[]) => JSX.Element, thisArg?: any): JSX.Element[]Calls a defined callback function on each element of an array, and returns an array that contains the results.
map((factor: MfaFactorfactor) => (
<React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li React.Attributes.key?: React.Key | null | undefinedkey={factor: MfaFactorfactor.multiFactorId: stringmultiFactorId}>
{factor: MfaFactorfactor.multiFactorType: stringmultiFactorType === "totp"
? "Authenticator App"
: factor: MfaFactorfactor.multiFactorType: stringmultiFactorType}
<React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button
React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefinedonClick={() => const handleRemoveAuthenticator: (multiFactorId: string) => voidhandleRemoveAuthenticator(factor: MfaFactorfactor.multiFactorId: stringmultiFactorId)}
React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefineddisabled={const removeMFA: UseMutationResult<{
multiFactors: MfaFactor[];
}, Error, RemoveMfaParams>removeMFA.isPending: booleanA boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
A boolean variable derived from status
.
true
if the mutation is currently executing.
isPending}
>
Remove
</React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>button>
</React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>li>
))}
</React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>ul>
)}
{const getMFAFactors: (Override<MutationObserverIdleResult<{
multiFactors: MfaFactor[];
}, Error, void, unknown>, {
mutate: UseMutateFunction<{
multiFactors: MfaFactor[];
}, Error, void, unknown>;
}> & {
...;
}) | (Override<...> & {
...;
}) | (Override<...> & {
...;
})getMFAFactors.isError: booleanA boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
A boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
A boolean variable derived from status
.
true
if the last mutation attempt resulted in an error.
isError && (
<React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div React.HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="error">
Error loading MFA settings: {const getMFAFactors: Override<MutationObserverErrorResult<{
multiFactors: MfaFactor[];
}, Error, void, unknown>, {
mutate: UseMutateFunction<{
multiFactors: MfaFactor[];
}, Error, void, unknown>;
}> & {
...;
}getMFAFactors.error: ErrorThe error object for the mutation, if an error was encountered.
Defaults to null
.
error.Error.message: stringmessage}
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
)}
</React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
}
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:
- Email OTP with Multi-Factor Authentication - See how to implement the two-step verification
- Email Magic Link with Multi-Factor Authentication - Learn how to handle magic links with MFA
- Social Login with Multi-Factor Authentication - Social login with MFA is handled in the OAuth callback
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.