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:
| Action | Description |
|---|---|
mfa.recovery_requested | User requested recovery email |
mfa.recovery_used | Recovery token was used |
mfa.factor_removed_by_admin | Admin removed a factor |
mfa.backup_codes_generated | New backup codes generated |
mfa.backup_code_used | A 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