Account Recovery When Everything Is Lost
The impossible tradeoff
The user’s phone is gone. Their recovery codes are gone. They cannot generate a TOTP code, and they have no backup. They are locked out.
This is the hardest problem in authentication. Making recovery easy makes 2FA weaker (an attacker could use the recovery mechanism). Making recovery hard or impossible means legitimate users lose their accounts.
There is no perfect answer. Every option trades security for usability.
Option 1: Admin-initiated reset
An administrator manually disables 2FA after verifying the user’s identity through another channel (phone call, video call, government ID, support ticket with identity verification).
// Admin-only route
route.post("/admin/users/:userId/reset-2fa", {
resolve: (c) => {
const admin = authenticate(c.request);
if (admin instanceof Response) return admin;
// Verify admin role (from the auth course's user.role field,
// or from the authorization course's membership system)
// Adapt this check to match your app's admin model
if ((admin as any).role !== "admin") {
return Response.json({ error: "Forbidden" }, { status: 403 });
}
db.prepare("DELETE FROM totp_secrets WHERE user_id = ?").run(c.params.userId);
db.prepare("DELETE FROM recovery_codes WHERE user_id = ?").run(c.params.userId);
return Response.json({ message: "2FA has been reset for this user." });
},
}); Pros: The user recovers their account. The admin can verify identity through secure channels.
Cons: Social engineering risk — an attacker could impersonate the user to the admin. Support burden. Requires manual processes.
Option 2: Email-based recovery with waiting period
The user requests a 2FA reset via email. The reset does not take effect for 48-72 hours. During the waiting period, the account owner receives notifications. If they did not request the reset, they can cancel it.
Pros: Automated (no admin needed). The waiting period gives the real owner time to notice and cancel.
Cons: Complex to implement. The user waits 48 hours to regain access. An attacker with access to the email could still succeed if the user does not check email during the waiting period.
Option 3: No recovery
If you lose your second factor and recovery codes, your account is permanently inaccessible. This is the most secure option. Some cryptocurrency wallets and high-security services use this approach.
Pros: No attack surface for recovery-based account takeover.
Cons: Users lose accounts forever. Not suitable for most consumer or business apps.
What we recommend
For most apps:
- Recovery codes are the primary backup. Generate them at 2FA setup. Show them exactly once. Encourage the user to save them in a password manager.
- Admin reset is the fallback. An admin can disable 2FA after identity verification. Log the action for audit purposes.
- Email notification on reset. When an admin resets 2FA, email the user: “2FA has been disabled on your account. If you did not request this, contact support immediately.”
This balances security and usability. Recovery codes handle the common case (lost phone). Admin reset handles the edge case (lost everything). Email notification detects unauthorized resets.
What to tell users at setup
When showing recovery codes, be explicit:
- “These codes are your backup if you lose your phone.”
- “Save them in your password manager or print them.”
- “Each code works once.”
- “If you lose your phone AND these codes, you will need to contact support to regain access.”
Exercises
Exercise 1: Think about the apps you use. How does each handle 2FA recovery? Check Google, GitHub, and your bank.
Exercise 2: Implement the admin-initiated reset route. Add logging (log("2fa_reset", { userId, adminId })).
Exercise 3: Design (but do not implement) an email-based recovery with a 48-hour waiting period. What tables and routes would you need?
Why is a waiting period useful for email-based 2FA recovery?