HelloJohn / docs
MFA

MFA Recovery

Backup codes, account recovery flows, and admin overrides for users locked out of MFA.

MFA Recovery

When users lose access to their MFA device, they need a recovery path. HelloJohn provides backup codes, admin override options, and recovery request flows.


Backup Codes

Backup codes are one-time-use codes generated when a user enrolls in MFA. Each code can be used once in place of a regular OTP.

Generating Backup Codes

curl -X POST "https://api.hellojohn.dev/v1/users/usr_01HABCDEF123456/mfa/backup-codes/generate" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

Response:

{
  "codes": [
    "ABCD-EFGH",
    "IJKL-MNOP",
    "QRST-UVWX",
    "YZAB-CDEF",
    "GHIJ-KLMN",
    "OPQR-STUV",
    "WXYZ-0123",
    "4567-89AB"
  ],
  "generated_at": "2024-01-15T10:00:00Z"
}

Important: Codes are shown only once. Users should copy them to a password manager or print them.

Generating new codes invalidates all previous codes.

Presenting Codes to Users

import { useUser } from "@hellojohn/react";

function BackupCodesSetup() {
  const { user } = useUser();
  const [codes, setCodes] = useState<string[]>([]);

  const generate = async () => {
    const result = await user.mfa.generateBackupCodes();
    setCodes(result.codes);
  };

  return (
    <div>
      <button onClick={generate}>Generate backup codes</button>
      {codes.length > 0 && (
        <div>
          <p>Save these codes somewhere safe. Each code can only be used once.</p>
          <ul>
            {codes.map((code) => (
              <li key={code}><code>{code}</code></li>
            ))}
          </ul>
          <button onClick={() => window.print()}>Print</button>
          <button onClick={() => navigator.clipboard.writeText(codes.join("\n"))}>
            Copy all
          </button>
        </div>
      )}
    </div>
  );
}

Using a Backup Code During Sign-In

When prompted for MFA, the user can use a backup code:

// During MFA verification, use backup code instead of OTP
await hj.mfa.verify({
  challenge_id: challengeId,
  factor_id: "backup_codes",
  code: "ABCD-EFGH",
});

After use, the code is invalidated. If the user runs low on codes, prompt them to regenerate.


Admin Recovery Override

When a user can't complete MFA and has no backup codes (lost device, no codes saved), an admin can bypass MFA and issue a recovery session:

Option 1: Disable MFA Factor

Remove the lost factor so the user can sign in without it:

curl -X DELETE "https://api.hellojohn.dev/v1/users/usr_01HABCDEF123456/mfa/factors/fac_01HABCDEF555444" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321"

The user can then sign in normally and re-enroll a new factor.

Option 2: Impersonation

Generate a session for the user without MFA:

curl -X POST "https://api.hellojohn.dev/v1/admin/users/impersonate" \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "usr_01HABCDEF123456",
    "reason": "User locked out — no backup codes",
    "expires_in": "1h"
  }'

Send this session token to the user via a verified email channel to let them back in.


Self-Service Recovery Request Flow

Build a recovery request flow where locked-out users can verify their identity via email and get a recovery link:

1. User clicks "Lost access to MFA?" on sign-in page
2. User enters their email
3. HelloJohn sends a recovery email with a one-time link
4. User clicks link → Temporary session without MFA
5. User re-enrolls a new MFA factor
6. Recovery session expires (1 hour)

Triggering Recovery Email

curl -X POST "https://api.hellojohn.dev/v1/auth/mfa/recovery" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321" \
  -H "Content-Type: application/json" \
  -d '{"email": "alice@example.com"}'

Response: 200 OK — Always returns success (doesn't reveal if email exists).

Verifying the Recovery Token

curl -X POST "https://api.hellojohn.dev/v1/auth/mfa/recovery/verify" \
  -H "X-Tenant-ID: tnt_01HABCDEF654321" \
  -H "Content-Type: application/json" \
  -d '{"token": "rec_tok_01HABCDEF..."}'

Response: 200 OK — Returns a recovery session token.

{
  "access_token": "eyJhbGciOiJFZERTQSJ9...",
  "session_type": "recovery",
  "requires_mfa_reenrollment": true
}

Monitoring Recovery Events

All recovery events are recorded in the audit log:

ActionDescription
mfa.recovery_requestedUser requested recovery email
mfa.recovery_usedRecovery token was used
mfa.factor_removed_by_adminAdmin removed a factor
mfa.backup_codes_generatedNew backup codes generated
mfa.backup_code_usedA backup code was consumed

Best Practices

  • Always prompt users to generate backup codes after enrolling any factor
  • Show remaining codes count — warn when fewer than 3 are left
  • Require identity verification before admin recovery (e.g., video call, government ID)
  • Audit all recovery events — recovery is a high-risk operation

On this page