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:

  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 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(): UseMFAResult

Hook 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: boolean
isReady
} =
function useMFA(): UseMFAResult

Hook 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: boolean
isReady
) {
// 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(): UseMFAResult

Hook 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.Element
AuthenticatorSetupComponent
() {
const {
const addMFA: UseMutationResult<AddMfaResult, Error, AddMfaParams>
addMFA
} =
function useMFA(): UseMFAResult

Hook 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: string
qrCodeUrl
,
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: string
factorId
,
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: () => void
handleSetupAuthenticator
= () => {
// 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) => void

The 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) | undefined
onSuccess
: (
result: AddMfaResult
result
) => {
// Store the QR code URL and factor ID
const setQrCodeUrl: (value: React.SetStateAction<string>) => void
setQrCodeUrl
(
result: AddMfaResult
result
.
multiFactorTotpUrl: string
multiFactorTotpUrl
);
// 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>) => void
setFactorId
(
result: AddMfaResult
result
.
multiFactorId: string
multiFactorId
);
},
MutateOptions<AddMfaResult, Error, AddMfaParams, unknown>.onError?: ((error: Error, variables: AddMfaParams, context: unknown) => void) | undefined
onError
: (
error: Error
error
) => {
var console: Console

The 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: Error
error
);
}, }, ); }; // You can also check loading state directly const
const isLoading: boolean
isLoading
=
const addMFA: UseMutationResult<AddMfaResult, Error, AddMfaParams>
addMFA
.
isPending: boolean

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.

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> | undefined
onClick
={
const handleSetupAuthenticator: () => void
handleSetupAuthenticator
}
React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefined
disabled
={
const isLoading: boolean
isLoading
}>
{
const isLoading: boolean
isLoading
? "Setting up..." : "Set Up Authenticator App"}
</
React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
>
{
const qrCodeUrl: string
qrCodeUrl
&& (
<
React.JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div
React.HTMLAttributes<HTMLDivElement>.className?: string | undefined
className
="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: string
qrCodeUrl
}
size?: number | undefined

The 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: boolean

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.

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 | undefined
className
="error">Error: {
const addMFA: Override<MutationObserverErrorResult<AddMfaResult, Error, AddMfaParams, unknown>, { mutate: UseMutateFunction<AddMfaResult, Error, AddMfaParams, unknown>; }> & { ...; }
addMFA
.
error: Error

The error object for the mutation, if an error was encountered. Defaults to null.

error
.
Error.message: string
message
}</
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(): UseMFAResult

Hook 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.Element
VerifyAuthenticatorComponent
({
multiFactorId: string
multiFactorId
,
}: {
multiFactorId: string
multiFactorId
: string;
}) { const {
const verifyMFA: UseMutationResult<{ multiFactors: MfaFactor[]; }, Error, VerifyMfaParams>
verifyMFA
} =
function useMFA(): UseMFAResult

Hook 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: string
code
,
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: () => void
handleVerifyAuthenticator
= () => {
const verifyMFA: UseMutationResult<{ multiFactors: MfaFactor[]; }, Error, VerifyMfaParams>
verifyMFA
.
mutate: (variables: VerifyMfaParams, options?: MutateOptions<{ multiFactors: MfaFactor[]; }, Error, VerifyMfaParams, unknown> | undefined) => void

The mutation function you can call with variables to trigger the mutation and optionally hooks on additional callback options.

mutate
(
{
multiFactorId: string
multiFactorId
,
multiFactorCode: string
multiFactorCode
:
const code: string
code
, // The TOTP code from the user's authenticator app
}, {
MutateOptions<{ multiFactors: MfaFactor[]; }, Error, VerifyMfaParams, unknown>.onSuccess?: ((data: { multiFactors: MfaFactor[]; }, variables: VerifyMfaParams, context: unknown) => void) | undefined
onSuccess
: () => {
// Authenticator setup successful
var console: Console

The 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) | undefined
onError
: (
error: Error
error
) => {
// Handle error
var console: Console

The 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: Error
error
);
}, }, ); }; // 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: string
multiFactorId
,
multiFactorCode: string
multiFactorCode
:
const code: string
code
,
});
var console: Console

The 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: unknown
error
) {
var console: Console

The 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: unknown
error
);
} }; 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 | undefined
type
="text"
React.InputHTMLAttributes<HTMLInputElement>.value?: string | number | readonly string[] | undefined
value
={
const code: string
code
}
React.InputHTMLAttributes<HTMLInputElement>.onChange?: React.ChangeEventHandler<HTMLInputElement> | undefined
onChange
={(
e: React.ChangeEvent<HTMLInputElement>
e
) =>
const setCode: (value: React.SetStateAction<string>) => void
setCode
(
e: React.ChangeEvent<HTMLInputElement>
e
.
React.ChangeEvent<HTMLInputElement>.target: EventTarget & HTMLInputElement
target
.
HTMLInputElement.value: string

Returns the value of the data at the cursor's current position.

MDN Reference

value
)}
React.InputHTMLAttributes<HTMLInputElement>.placeholder?: string | undefined
placeholder
="Enter 6-digit code"
React.InputHTMLAttributes<HTMLInputElement>.maxLength?: number | undefined
maxLength
={6}
/> <
React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined
onClick
={
const handleVerifyAuthenticator: () => void
handleVerifyAuthenticator
}
React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefined
disabled
={
const verifyMFA: UseMutationResult<{ multiFactors: MfaFactor[]; }, Error, VerifyMfaParams>
verifyMFA
.
isPending: boolean

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.

A boolean variable derived from status. true if the mutation is currently executing.

isPending
}
> {
const verifyMFA: UseMutationResult<{ multiFactors: MfaFactor[]; }, Error, VerifyMfaParams>
verifyMFA
.
isPending: boolean

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.

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: boolean

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.

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 | undefined
className
="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): void

Accepts 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(): UseMFAResult

Hook 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.Element
ManageMfaComponent
() {
const {
const getMFAFactors: UseMutationResult<{ multiFactors: MfaFactor[]; }, Error, void>
getMFAFactors
,
const removeMFA: UseMutationResult<{ multiFactors: MfaFactor[]; }, Error, RemoveMfaParams>
removeMFA
} =
function useMFA(): UseMFAResult

Hook 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): void

Accepts 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) => void

The mutation function you can call with variables to trigger the mutation and optionally hooks on additional callback options.

mutate
(
var undefined
undefined
, {
MutateOptions<{ multiFactors: MfaFactor[]; }, Error, void, unknown>.onSuccess?: ((data: { multiFactors: MfaFactor[]; }, variables: void, context: unknown) => void) | undefined
onSuccess
: (
result: { multiFactors: MfaFactor[]; }
result
) => {
// factors.multiFactors is an array of verification methods
const setFactors: (value: React.SetStateAction<MfaFactor[]>) => void
setFactors
(
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) => void
handleRemoveAuthenticator
= (
multiFactorId: string
multiFactorId
: string) => {
const removeMFA: UseMutationResult<{ multiFactors: MfaFactor[]; }, Error, RemoveMfaParams>
removeMFA
.
mutate: (variables: RemoveMfaParams, options?: MutateOptions<{ multiFactors: MfaFactor[]; }, Error, RemoveMfaParams, unknown> | undefined) => void

The mutation function you can call with variables to trigger the mutation and optionally hooks on additional callback options.

mutate
(
{
multiFactorIds: string[]
multiFactorIds
: [
multiFactorId: string
multiFactorId
] },
{
MutateOptions<{ multiFactors: MfaFactor[]; }, Error, RemoveMfaParams, unknown>.onSuccess?: ((data: { multiFactors: MfaFactor[]; }, variables: RemoveMfaParams, context: unknown) => void) | undefined
onSuccess
: () => {
var console: Console

The 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[]>) => void
setFactors
(
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: MfaFactor
f
) =>
f: MfaFactor
f
.
multiFactorId: string
multiFactorId
!==
multiFactorId: string
multiFactorId
));
}, }, ); }; // Loading states are available directly from the mutation objects if (
const getMFAFactors: UseMutationResult<{ multiFactors: MfaFactor[]; }, Error, void>
getMFAFactors
.
isPending: boolean

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.

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: number

Gets 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: MfaFactor
factor
) => (
<
React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
li
React.Attributes.key?: React.Key | null | undefined
key
={
factor: MfaFactor
factor
.
multiFactorId: string
multiFactorId
}>
{
factor: MfaFactor
factor
.
multiFactorType: string
multiFactorType
=== "totp"
? "Authenticator App" :
factor: MfaFactor
factor
.
multiFactorType: string
multiFactorType
}
<
React.JSX.IntrinsicElements.button: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button
React.DOMAttributes<HTMLButtonElement>.onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined
onClick
={() =>
const handleRemoveAuthenticator: (multiFactorId: string) => void
handleRemoveAuthenticator
(
factor: MfaFactor
factor
.
multiFactorId: string
multiFactorId
)}
React.ButtonHTMLAttributes<HTMLButtonElement>.disabled?: boolean | undefined
disabled
={
const removeMFA: UseMutationResult<{ multiFactors: MfaFactor[]; }, Error, RemoveMfaParams>
removeMFA
.
isPending: boolean

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.

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: boolean

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 | undefined
className
="error">
Error loading MFA settings: {
const getMFAFactors: Override<MutationObserverErrorResult<{ multiFactors: MfaFactor[]; }, Error, void, unknown>, { mutate: UseMutateFunction<{ multiFactors: MfaFactor[]; }, Error, void, unknown>; }> & { ...; }
getMFAFactors
.
error: Error

The error object for the mutation, if an error was encountered. Defaults to null.

error
.
Error.message: string
message
}
</
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:

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.