Browse the docs
Guides

Add MFA

Turn on multi-factor authentication, require it for the people who need it, and give every user a safe way back in if they lose a device.

OrthID supports time-based one-time passwords (TOTP) from any authenticator app and one-time passcodes (OTP) sent over SMS or email. You can offer MFA as optional, or enforce it across an organisation or for a specific role. This guide covers enabling factors, enforcing a policy, recovery codes, and the user enrolment flow.

1. Enable factors

Factors are configured per environment. Enable the ones you want to offer, then set a policy to control who must use them. The example below enables TOTP and email OTP, and turns off SMS.

scripts/configure-mfa.ts
import { orthid } from "@orthid/sdk";

// Enable the factors users may enrol.
await orthid.mfa.setFactors({
  totp: true,        // authenticator apps (recommended)
  otpEmail: true,    // one-time passcode by email
  otpSms: false,     // disabled: SMS is the weakest factor
});

2. Enforce a policy

A policy decides who is required to have MFA. Scope it to an organisation, to one or more roles, or to everyone in the environment. When a policy applies to a user, OrthID blocks any sensitive session step until at least one factor is enrolled.

scripts/enforce-mfa.ts
import { orthid } from "@orthid/sdk";

// Require MFA for every admin in this organisation.
await orthid.mfa.setPolicy({
  organizationId: "org_clinic_42",
  enforce: "required",        // "optional" | "required"
  appliesToRoles: ["admin", "clinician"],
  // Give existing users a window to enrol before they are locked out.
  gracePeriodHours: 72,
});

// Or require MFA for everyone in the environment.
await orthid.mfa.setPolicy({ enforce: "required" });

Policies are evaluated at session time. A user who matches a required policy and has not enrolled is sent into the enrolment flow on their next sign-in; until they finish, OrthID issues a restricted session that cannot reach protected routes.

3. Recovery codes

When a user enrols their first factor, OrthID generates a set of single-use recovery codes. Each code logs the user in once and is then burned. Generate or regenerate them through the API if you build your own enrolment UI.

scripts/recovery-codes.ts
import { orthid } from "@orthid/sdk";

// Issue a fresh set of 10 single-use recovery codes.
const { codes } = await orthid.mfa.generateRecoveryCodes({
  userId: "user_8sQ1",
  count: 10,
});

// Show these to the user ONCE. OrthID only stores their hashes.
console.log(codes);
// => ["7P2K-9QXM", "4LWD-1FZB", ...]
Recovery codes are shown once
OrthID stores only a hash of each recovery code, so they cannot be retrieved later. Prompt the user to download or print the codes during enrolment, and let them regenerate the set at any time. Regenerating invalidates every previous code.

4. The user enrolment flow

With the prebuilt components, enrolment is automatic. The hosted <UserButton/> profile exposes a security panel where users can add a factor, and the sign-in flow steps a user through enrolment whenever a required policy applies. The flow is:

  1. The user scans the TOTP QR code, or requests an email passcode.
  2. They confirm the factor by entering one live code.
  3. OrthID shows the recovery codes and asks the user to save them.
  4. On every later sign-in the user completes the second step after their password or passkey.

If you build a custom UI, drive the same steps with the MFA enrolment API.

app/enroll-mfa.ts
import { orthid } from "@orthid/sdk";

// Start TOTP enrolment: returns a secret and an otpauth:// URI for the QR code.
const enrolment = await orthid.mfa.startEnrollment({
  userId: "user_8sQ1",
  factor: "totp",
});
// enrolment.qrCodeUri -> render as a QR image
// enrolment.secret    -> offer as a manual fallback

// Confirm with the first live code from the user's app.
await orthid.mfa.confirmEnrollment({
  userId: "user_8sQ1",
  enrollmentId: enrolment.id,
  code: "204815",
});
Test before you enforce
Roll out with enforce: "optional" and a small pilot role first. Once enrolment is healthy, switch to required with a grace period so existing users are not locked out mid-session.

Next steps

  • Enable SSO so enterprise tenants bring their own identity provider and MFA.
  • Sessions explains how OrthID tracks a signed-in actor and when a step-up is required.