Once users have successfully enrolled in MFA with Privy, they will be required to complete MFA whenever the private key for their embedded wallet must be used. This includes:
- Signing messages and transactions
- Recovering the embedded wallet on new devices
- Exporting the wallet’s private key
- Setting a password on the wallet
- Enrolling another MFA method or unenrolling an existing one
Once a user has completed MFA on a given device, they can continue to use the wallet on that device without needing to complete MFA for 15 minutes.After 15 minutes have elapsed, Privy will require that the user complete MFA again to re-authorize use of the wallet’s private key.
Requirements for custom MFA verification
To ensure users can complete MFA when required, your app must:
- Set up a flow to guide the user through completing MFA when required
- Register an event listener to configure Privy to invoke the flow whenever MFA is required. Read more about the MFA required listener here.
Verification interfaces
To set up a flow to have the user complete MFA, use Privy’s useMfa hook:import {useMfa} from '@privy-io/react-auth';
const {init, submit, cancel} = useMfa();
This flow has three core components:
- Requesting an MFA challenge (
init) - Sends an MFA code to the user’s enrolled method
- Submitting the MFA verification (
submit) - Verifies the code provided by the user
- Cancelling the MFA flow (
cancel) - Cancels an in-progress MFA flow if needed
To set up a flow to have the user complete MFA, use Privy’s useMfa hook:import {useMfa} from '@privy-io/expo';
const {init, submit, cancel} = useMfa();
This flow has three core components:
- Requesting an MFA challenge (
init) - Sends an MFA code to the user’s enrolled method
- Submitting the MFA verification (
submit) - Verifies the code provided by the user
- Cancelling the MFA flow (
cancel) - Cancels an in-progress MFA flow if needed
To set up a flow to have the user complete MFA, access the verification interfaces via the authenticated user.mfa namespace:guard let user = await privy.getUser() else { return }
// SMS verification
try await user.mfa.sms.verify.sendCode()
try await user.mfa.sms.verify.submit(code: mfaCode)
// TOTP verification
try await user.mfa.totp.verify.submit(code: mfaCode)
// Passkey verification
try await user.mfa.passkeys.verify.submit(relyingParty: "https://yourdomain.com")
This flow has two core components:
- Requesting an MFA challenge (e.g.
sendCode for SMS) - Sends an MFA code to the user’s enrolled method
- Submitting the MFA verification (
submit) - Verifies the code provided by the user
If your app uses an MFA required listener, you must call privy.mfa.resumeBlockedActions() after successful verification to unblock any pending wallet operations.
If you do not call this, the initial call that triggered MFA will never resolve.
Cancelling the MFA flow
After init has been called and the corresponding submit call has not yet occurred, the user may cancel their in-progress MFA flow if they wish.To cancel the current MFA flow, call the cancel method from the useMfa hook: After init has been called and the corresponding submit call has not yet occurred, the user may cancel their in-progress MFA flow if they wish.To cancel the current MFA flow, call the cancel method from the useMfa hook: If the user cancels the MFA flow or verification fails, you must still call resumeBlockedActions to unblock any pending wallet operations. Pass an error to indicate the verification was cancelled.
// Cancel the MFA flow by resuming with an error
await privy.mfa.resumeBlockedActions(throwing: MyError.mfaCancelled)
See our MFA required listener guide for more details about this flow.
Next steps