Skip to main content
Early access — hidden. Login MFA isn’t in the Para Modal yet, so there’s no built-in UI for it. You integrate it with your own UI today (React hooks or fully custom). Reach out to your Para contact to enable it on your project.
Login MFA adds a TOTP second factor at sign-in: after a user authenticates with their first factor (email, phone, OAuth, your Custom OIDC provider, etc.), Para can require them to set up and present a time-based one-time code from an authenticator app before the login completes and their wallet unlocks.
This is login-time MFA — a second factor on every sign-in. It’s distinct from Para’s recovery 2FA (useSetup2fa / useVerify2fa), which gates account recovery. The hooks and methods below (enrollMfa / verifyMfa) are login MFA only.

How it works

When login MFA is owed, the SDK pauses the login on a challenge and surfaces it through the auth state. There are two challenge phases: Your UI watches for these phases, drives enrollment/verification, and submits the code. On a correct code the SDK re-polls the login and advances on its own toward the connected wallet — you don’t need to restart the flow.

Before you start

  • Para has enabled login MFA on your project (see Enabling login MFA).
  • The Para React SDK (@getpara/react-sdk) or Web SDK (@getpara/web-sdk), v3.
  • A custom login UI — login MFA has no Para Modal UI yet, so you render the challenge yourself.

Enabling login MFA

Login MFA is configured on your project’s partner record by the Para team — it isn’t self-serve while the feature is in early access. Reach out to your Para contact and tell them which mode you want:
The CLI/Developer Portal “Enable two-factor authentication” toggle controls recovery 2FA, not login MFA — don’t use it for this. Login MFA is enabled separately by Para.

Integrate it

Pick the path that matches how you build your login UI. Both drive the same enrollMfa / verifyMfa flow — the hooks just wrap it in React Query state.
Use useEnrollMfa and useVerifyMfa (from @getpara/react-sdk) together with the auth-state subscription. This mirrors the Custom OIDC example — a small hook surfaces the challenge from the SDK state and drives it.

A challenge hook

Subscribe to the auth state to detect the hold, fetch the enrollment secret once, and expose a verify function:

Render the challenge

Render a QR from the uri (any QR component works), show the backup codes once during enrollment, and collect a 6-digit code:
Each hook returns the standard React Query mutation fields, with mutate/mutateAsync renamed: useEnrollMfa(){ enrollMfa, enrollMfaAsync, isPending, ... }, useVerifyMfa(){ verifyMfa, verifyMfaAsync, isPending, ... }.

Backup codes

enrollMfa returns a set of one-time backup codes alongside the QR uri. Show them to the user once, during setup, and tell them to store the codes somewhere safe — they’re how a user gets in if they lose their authenticator. Para stores only hashes and never shows them again. A backup code is accepted anywhere a TOTP code is — pass it to verifyMfa({ code }) exactly the same way. Each backup code works once.

Handling wrong codes and lockout

verifyMfa returns { ok: false, attemptsRemaining } for an incorrect code. Surface attemptsRemaining and let the user try again — don’t treat it as a fatal error. After too many wrong codes the session is locked out; the user must restart the login (a fresh sign-in resets the budget). When attemptsRemaining reaches 0, prompt the user to sign in again.

Security

  • The TOTP secret is generated server-side and stored encrypted — your UI only ever receives the otpauth:// provisioning uri to render.
  • Backup codes are single-use and stored as hashes; they’re returned in plaintext only once, at enrollment.
  • A correct factor is what releases the user’s wallet keyshares for the session — the gate is enforced on Para’s backend, not in your UI, so it can’t be bypassed client-side.

Troubleshooting

Confirm Para has enabled login MFA on your project (optional or required). In optional mode, only users who have already enrolled a factor are challenged — a brand-new user won’t be unless the mode is required. Also make sure your UI subscribes to onStatePhaseChange and renders on awaiting_2fa / awaiting_2fa_enrollment.
Each enrollMfa() call mints a fresh secret and a new set of backup codes. Call it once per enrollment challenge (guard it, as the example does with a ref) so the QR and backup codes are stable while the user scans them.
On { ok: true } the SDK re-polls the login itself — don’t also re-trigger authentication. If you’re keeping the auth popup/window open, make sure it stays open until the wallet connects.
TOTP codes are time-based — make sure the user’s device clock is accurate. Para accepts a small skew window. If they’ve lost their authenticator, have them use a backup code instead.

Next steps

Build a Custom UI

The full custom-login flow the MFA challenge plugs into.

Custom OIDC

Add your own identity provider as a first factor.