Email verification
Use mailbox challenges during signup when you want to verify that the user controls the email address before creating or activating their account.
Start the signup challenge
Section titled “Start the signup challenge”import { createMailboxChallenge } from "@passlock/server";
const tenancyId = "myTenancyId";const apiKey = "myApiKey";
const result = await createMailboxChallenge({ tenancyId, apiKey, email: "jdoe@example.com", purpose: "signup", metadata: { signupId: "signup_123", }, invalidateOthers: true,});
// message contains rendered HTML and plain-text email content.// result.challenge.code is also available if you prefer to render your own email.const { challengeId, secret, message, email } = result.challenge;
// save this in the user's session or a secure HTTP only cookieawait savePendingChallenge({ flow: "signup", challengeId, secret, email,});
await emailCodeToUser({ email, message });import { createMailboxChallenge, isChallengeRateLimitedError,} from "@passlock/server/safe";
const tenancyId = "myTenancyId";const apiKey = "myApiKey";
const result = await createMailboxChallenge({ tenancyId, apiKey, email: "jdoe@example.com", purpose: "signup", metadata: { signupId: "signup_123", }, invalidateOthers: true,});
if (result.success) { // message contains rendered HTML and plain-text email content. // result.value.challenge.code is also available if you prefer to render your own email. const { challengeId, secret, message, email } = result.value.challenge;
// save this in the user's session or a secure HTTP only cookie await savePendingChallenge({ flow: "signup", challengeId, secret, email, });
await emailCodeToUser({ email, message });} else if (isChallengeRateLimitedError(result.error)) { return showRateLimit(result.error.retryAfterSeconds);} else { throw new Error(result.error.message);}Verify the submitted code
Section titled “Verify the submitted code”import { verifyMailboxChallenge } from "@passlock/server";
// fetch from the user's session or secure cookieconst pending = await loadPendingChallenge({ flow: "signup" });
const verified = await verifyMailboxChallenge({ tenancyId: "myTenancyId", apiKey: "myApiKey", challengeId: pending.challengeId, secret: pending.secret, code: form.code,});
if (verified.challenge.purpose !== "signup") { throw new Error("Unexpected challenge purpose");}
if (verified.challenge.email !== pending.email) { throw new Error("Email mismatch");}
await createUser({ email: verified.challenge.email,});
await markEmailVerified(verified.challenge.email);await clearPendingChallenge({ flow: "signup" });import { verifyMailboxChallenge } from "@passlock/server/safe";
// fetch from the user's session or secure cookieconst pending = await loadPendingChallenge({ flow: "signup" });
const result = await verifyMailboxChallenge({ tenancyId: "myTenancyId", apiKey: "myApiKey", challengeId: pending.challengeId, secret: pending.secret, code: form.code,});
if (result.success) { const { challenge } = result.value;
if (challenge.purpose !== "signup") { throw new Error("Unexpected challenge purpose"); }
if (challenge.email !== pending.email) { throw new Error("Email mismatch"); }
await createUser({ email: challenge.email }); await markEmailVerified(challenge.email); await clearPendingChallenge({ flow: "signup" });} else { return showVerificationError(result.error);}If you also support non-Node.js backends, the same flow is available through the REST API mailbox challenge endpoints.