Skip to content

Mailbox verification

Use mailbox challenges during signup to verify ownership of an email address.

backed/signup.ts
import { Passlock, isChallengeRateLimitedError } from "@passlock/server";
const passlock = new Passlock({ ... });
// capture this during signup
const email = "...";
const firstName = "...";
const lastName = "...";
const result = await passlock.createMailboxChallenge({
email,
purpose: "signup",
// invalidate other "signup" challenges for this email
invalidateOthers: true,
// we can store arbitrary data here if required
metadata: {
firstName,
lastName
}
});
if (result.success) {
const { challengeId, secret, message } = result.value.challenge;
// save this in the user's session or a secure HTTP only cookie
await savePendingChallenge({
purpose: "signup"
challengeId,
secret,
});
await emailCodeToUser({ email, message });
} else if (isChallengeRateLimitedError(result.error)) {
return showRateLimitMessage(result.error.retryAfterSeconds);
} else {
throw new Error(result.error.message);
}
backend/signup.ts
import { Passlock } from "@passlock/server";
const passlock = new Passlock({ ... });
// fetch from the user's session or secure cookie
const pendingChallenge = await loadPendingChallenge({
purpose: "signup"
});
const result = await passlock.verifyMailboxChallenge({
challengeId: pendingChallenge.challengeId,
secret: pendingChallenge.secret,
code: form.code, // the user submitted this
});
if (result.success) {
const { challenge } = result.value;
const { email } = challenge;
// use a real type guard /schema for this
const { firstName, lastName } = challenge.metadata;
await createUserAccount({
email,
firstName,
lastName,
emailVerified: true
});
// delete the cookie or session attributes
await clearPendingChallenge({ purpose: "signup" });
} else {
throw new Error(result.error.message);
}

If you also support backends that do not use the JS/TS server library, the same flow is available through the REST API mailbox challenge endpoints.