Skip to main content
Your app’s server can enroll users in MFA and verify them with the Privy REST API or a server SDK. Server-side MFA is useful for apps that authenticate users on their own backend, such as apps using JWT-based authentication. Every MFA method follows the same flow:
1

Start the MFA ceremony

Call the MFA method’s init route to begin MFA enrollment or verification. For SMS, Privy sends a 6-digit code to the user. For passkeys, Privy returns WebAuthn options for the user’s browser.
2

Complete the MFA ceremony

Send the user’s code or passkey credential to the MFA method’s verify route to complete MFA enrollment or verification. Privy returns an MFA token.
3

Use the MFA token

Send the MFA token on requests that require MFA, such as authorizing a wallet action.

Authentication

Every server-side MFA request needs three credentials:
  • Your app ID and app secret, as Basic auth. See REST API setup.
  • The privy-app-id header.
  • The user’s access token or custom auth JWT, in the privy-user-token header.
Server SDKs send your app credentials from the client configuration. Pass the user’s JWT on each MFA call. The JWT must belong to the user in the request path. Otherwise, Privy rejects the request with a 401.

SMS

With SMS MFA, Privy texts a 6-digit code to the user’s phone number. Each code works once.
Users with a phone number login account cannot enroll in SMS MFA. A phone number can secure login or MFA, but not both.

Enrollment

To enroll a user in SMS MFA, call init with the user’s phone number. Privy sends an enrollment code to that number. Call verify with the code the user received to complete MFA enrollment.
If the user already has MFA enrolled, enrollment init requires a valid MFA token. See adding or replacing a method.

Verification

To verify a user’s SMS MFA credential, call init to send a code to the user’s enrolled phone number. Call verify with the code the user received to complete MFA verification.

TOTP

With TOTP MFA, the user reads a 6-digit code from an authenticator app, like Google Authenticator or Authy. Each code works once.

Enrollment

To enroll a user in TOTP MFA, call init to create a secret. It returns the secret and an otpauth:// URI:
Show the URI to the user as a QR code, or show the secret for manual entry. Call verify with a code from the user’s authenticator app to complete MFA enrollment.
If the user already has MFA enrolled, enrollment init requires a valid MFA token. See adding or replacing a method.

Verification

To verify a user’s TOTP MFA credential, call verify with a code from the user’s authenticator app. TOTP has no init call, since the authenticator app generates codes.

Passkeys

With passkey MFA, the user verifies with a passkey in their browser. Your app’s server gets WebAuthn options from Privy and passes them to the browser. The browser runs the WebAuthn ceremony and returns a credential, which your app’s server sends to Privy. Both init calls take a relying_party: the origin where the user’s browser runs the ceremony. If your app has allowed domains, the origin must be one of them.
Privy uses snake_case keys in options and credential, such as pub_key_cred_params and raw_id. Browser WebAuthn libraries, like @simplewebauthn/browser, use camelCase. Convert the keys in both directions. Note that client_data_json maps to clientDataJSON.

Enrollment

To enroll a user in passkey MFA, call init to get WebAuthn registration options. In the browser, pass the options to navigator.credentials.create() or startRegistration() from @simplewebauthn/browser. Call verify with the resulting credential to complete MFA enrollment.
If the user already has MFA enrolled, enrollment init requires a valid MFA token. See adding or replacing a method.

Verification

To verify a user’s passkey MFA credential, call init to get WebAuthn authentication options. In the browser, pass the options to navigator.credentials.get() or startAuthentication() from @simplewebauthn/browser. Call verify with the resulting credential to complete MFA verification.

MFA token

Every verify call returns the same response:
string
The MFA token, a JWT proving the user completed MFA.
number
Unix timestamp in seconds when the MFA token expires. The app’s MFA verification duration sets the lifetime.
object
The current user object, including mfa_methods.
Completing MFA enrollment also sends the mfa.enabled webhook.

Authorizing a wallet action

Your app’s server authorizes wallet actions on the user’s wallets with a user key. When the user has MFA enrolled, Privy requires an MFA token to issue that key. With a server SDK, pass the MFA token with the user’s JWT in the authorization context. The SDK requests the user key and signs the wallet action. With the REST API, send the MFA token in the privy-mfa-token header when requesting the user key.

Adding or replacing a method

When the user already has MFA enrolled, enrollment init requires an MFA token issued in the last 30 seconds. To add or replace a method, complete MFA verification first. Start MFA enrollment right away with the new token. A new SMS enrollment replaces the phone number. A new TOTP enrollment replaces the secret, so codes from the old authenticator app stop working. A new passkey enrollment adds a passkey.

Errors

Server SDKs raise these as errors with the same status codes. For request and response schemas, see the MFA API reference.