Skip to content

Account recovery

If passkeys are your primary sign-in method, you still need a recovery path for users who lose access to them. Common examples include a new device that was never synced, a lost security key, or a wiped password manager.

Mailbox challenges give you an email-based recovery step: prove control of the mailbox on the account, then issue a short-lived recovery session that lets the user re-enrol passkeys or complete other recovery-only actions.

import { createMailboxChallenge } from "@passlock/server";
const user = await findUserByEmail("jdoe@example.com");
if (!user) {
return showUnknownAccount();
}
const result = await createMailboxChallenge({
tenancyId: "myTenancyId",
apiKey: "myApiKey",
email: user.email,
purpose: "account-recovery",
userId: String(user.id),
invalidateOthers: true,
});
// message contains rendered HTML and plain-text email content.
// The raw code is also available if you prefer to render your own email.
const { challengeId, secret, message, email } = result.challenge;
await savePendingRecoveryChallenge({
challengeId,
secret,
userId: user.id,
email,
});
await sendCodeEmail({ email, message });
import { verifyMailboxChallenge } from "@passlock/server";
const pending = await loadPendingRecoveryChallenge();
const verified = await verifyMailboxChallenge({
tenancyId: "myTenancyId",
apiKey: "myApiKey",
challengeId: pending.challengeId,
secret: pending.secret,
code: form.code,
});
if (verified.challenge.purpose !== "account-recovery") {
throw new Error("Unexpected challenge purpose");
}
if (verified.challenge.userId !== String(pending.userId)) {
throw new Error("Unexpected user");
}
if (verified.challenge.email !== pending.email) {
throw new Error("Email mismatch");
}
await clearPendingRecoveryChallenge();
await createRecoverySession({
userId: pending.userId,
expiresInMinutes: 10,
});
return redirectTo("/settings/passkeys/recover");

After recovery succeeds, prompt the user to register a new passkey as soon as possible and clear any temporary recovery state. The same mailbox challenge behavior is also available over raw HTTP in the REST API mailbox challenge reference.