Skip to content
Torii docs

Multi-factor authentication

Torii’s multi-factor authentication (MFA) adds a second factor to sign-in: a time-based one-time password (TOTP) from an authenticator app such as Google Authenticator, 1Password, or Authy. The authenticator app is the counted second factor; email is never used as a factor.

MFA is a Pro plan and up feature.

  1. Enrollment. A signed-in user turns on two-factor from their account. Torii generates a secret, the SDK shows a QR code and a manual key, and the user confirms with a 6-digit code. Torii then reveals a set of single-use recovery codes.
  2. Sign-in challenge. Once a user has a confirmed factor, password sign-in resolves to a challenge step instead of a session. The user enters a code from their app (or a recovery code) to finish signing in.
  3. Lockout recovery. A user who loses both their authenticator and their recovery codes is recovered by an operator who disables the factor from the dashboard. There is no email self-reset of the second factor: that would defeat the point of the factor.

The prebuilt <UserProfile> already includes a two-factor row in its Security section, so most apps get enrollment for free. For a custom account page, drop in <MfaEnrollment>:

import { SignedIn, MfaEnrollment } from '@torii-js/torii-react';
<SignedIn>
<MfaEnrollment />
</SignedIn>;

To build your own UI, use the headless useMfaEnrollment hook: it exposes enroll, confirm, disable, and regenerate, and returns the otpauth:// URI plus the manual secret so you can render your own QR code.

The shared secret and recovery codes are returned once. Torii stores the secret encrypted and the recovery codes hashed; they can’t be read back, so show them to the user immediately and prompt them to save the recovery codes.

No extra wiring is needed: <SignIn> renders the MFA challenge step automatically when an account has a confirmed factor. The user sees a code field with a “use a recovery code instead” toggle, and the session is only created once a valid code is verified. The headless useSignIn hook surfaces the same flow as an mfa-required state with a verifyMfa(code) action.

By default each user chooses whether to enable two-factor. To require it for an entire environment, turn on Require MFA at sign-in in the dashboard (environment settings). With enforcement on, a user without a factor must enroll to finish the ceremony (on both sign-in and sign-up) and no session is minted until they do, so an account can never sit unprotected. <SignIn> and <SignUp> render the enrollment step automatically; the headless useSignIn / useSignUp hooks surface it as an mfa-enrollment-required state with a confirmMfaEnrollment(code) action.

OAuth is gated too. OAuth sign-in and OAuth sign-up enforce the second factor just like the password paths: in an enforced environment the callback returns without a session and Torii renders the same challenge (existing factor) or enrollment (no factor yet) step over your app, applying the session only once the factor is verified. No extra wiring is needed beyond mounting <ToriiProvider>.

Recovery codes are single-use. Each one works in place of an authenticator code at the sign-in challenge. Users can regenerate them at any time (which invalidates the old set) from the same security surface; regenerating requires a current authenticator code.

When a user loses access to both their authenticator and their recovery codes, an operator disables the factor from the dashboard Users screen (“Disable MFA”). This removes the factor and the recovery codes, letting the user sign in with their password and enroll again. The action is recorded in the audit log.

Every string in the enrollment widget and the sign-in challenge is overridable through the label system: the mfa* keys on ToriiSignupLabels. See Labels.