Script Valley
Authentication From Scratch
Password Reset and MFALesson 6.5

How to use backup codes for MFA account recovery

backup code generation, hashing backup codes, one-time use enforcement, backup code redemption endpoint, code regeneration, user education

Generating and Storing Backup Codes

Backup codes are single-use fallback codes provided when TOTP is enabled. If a user loses their authenticator device, these codes let them log in and disable MFA.

function generateBackupCodes() {
  return Array.from({ length: 10 }, () =>
    crypto.randomBytes(5).toString('hex') // 10-char hex code
  );
}

// Store hashed codes in DB
async function storeBackupCodes(userId, codes) {
  const hashed = await Promise.all(
    codes.map(code => bcrypt.hash(code, 10))
  );
  await db.insertBackupCodes(userId, hashed);
}
// Redemption endpoint
app.post('/auth/backup-code', async (req, res) => {
  const userId = req.session.pendingUserId;
  const { code } = req.body;

  const storedCodes = await db.getUnusedBackupCodes(userId);
  let matched = null;

  for (const stored of storedCodes) {
    if (await bcrypt.compare(code, stored.hash)) {
      matched = stored;
      break;
    }
  }

  if (!matched) return res.status(401).json({ error: 'Invalid code' });

  await db.markBackupCodeUsed(matched.id);
  delete req.session.pendingUserId;
  req.session.regenerate((err) => {
    req.session.userId = userId;
    res.json({ message: 'Logged in via backup code', codesRemaining: storedCodes.length - 1 });
  });
});

Show backup codes to the user exactly once, immediately after TOTP setup. Never display them again — treat them exactly like a password. Warn users when they have fewer than 3 codes remaining and offer code regeneration (which invalidates all existing codes).