Passwordless login via allow list
Discover how to implement an allow list based sign up flow with the passwordless recipe.
Overview
In this flow, you create a list of emails or phone numbers that are allowed to sign up. Based on that users can go through the passwordless flow.
Before you start
This guide assumes that you already have a working application integrated with SuperTokens. If you have not, please check the Quickstart Guide.
Prerequisites
This guide uses the UserMetadata
recipe to store the allow list.
You need to enable it in the SDK initialization step.
Steps
1. Add a way to keep track of allowed emails or phone numbers
Start by maintaining an allow list of emails. You can either store this list in your own database, or use the metadata feature provided by SuperTokens to store this. This may seem like a strange use case of the user metadata recipe provided, but it works.
The following code samples show you how to save the allow list in the user metadata.
import UserMetadata from "supertokens-node/recipe/usermetadata"
async function addEmailToAllowlist(email: string) {
let existingData = await UserMetadata.getUserMetadata("emailAllowList");
let allowList: string[] = existingData.metadata.allowList || [];
allowList = [...allowList, email];
await UserMetadata.updateUserMetadata("emailAllowList", {
allowList
});
}
async function isEmailAllowed(email: string) {
let existingData = await UserMetadata.getUserMetadata("emailAllowList");
let allowList: string[] = existingData.metadata.allowList || [];
return allowList.includes(email);
}
async function addPhoneNumberToAllowlist(phoneNumber: string) {
let existingData = await UserMetadata.getUserMetadata("phoneNumberAllowList");
let allowList: string[] = existingData.metadata.allowList || [];
allowList = [...allowList, phoneNumber];
await UserMetadata.updateUserMetadata("phoneNumberAllowList", {
allowList
});
}
async function isPhoneNumberAllowed(phoneNumber: string) {
let existingData = await UserMetadata.getUserMetadata("phoneNumberAllowList");
let allowList: string[] = existingData.metadata.allowList || [];
return allowList.includes(phoneNumber);
}
For a multi tenant setup, you can even store an allow list per tenant. This would allow you to limit sign ups for different emails / phone numbers for different tenants. If you are doing this, then you would also need to pass in the tenantID to the functions above, which you can obtain from the input to the API overrides
shown below.
2. Check if the user is on the allow list
Update the backend SDK API function to only allow sign up requests from users that are on the allow list. To do this you need to use the check functions from the previous code snippet.
import Passwordless from "supertokens-node/recipe/passwordless";
import supertokens from "supertokens-node";
Passwordless.init({
override: {
apis: (originalImplementation) => {
return {
...originalImplementation,
createCodePOST: async function (input) {
if ("email" in input) {
let existingUsers = await supertokens.listUsersByAccountInfo(input.tenantId, {
email: input.email,
});
let userWithPasswordles = existingUsers.find(u => u.loginMethods.find(lM => lM.hasSameEmailAs(input.email) && lM.recipeId === "passwordless") !== undefined);
if (userWithPasswordles === undefined) {
// this is sign up attempt
if (!(await isEmailAllowed(input.email))) {
return {
status: "GENERAL_ERROR",
message: "Sign up disabled. Please contact the admin."
}
}
}
} else {
let existingUsers = await supertokens.listUsersByAccountInfo(input.tenantId, {
phoneNumber: input.phoneNumber,
});
let userWithPasswordles = existingUsers.find(u => u.loginMethods.find(lM => lM.hasSamePhoneNumberAs(input.phoneNumber) && lM.recipeId === "passwordless") !== undefined);
if (userWithPasswordles === undefined) {
// this is sign up attempt
if (!(await isPhoneNumberAllowed(input.phoneNumber))) {
return {
status: "GENERAL_ERROR",
message: "Sign up disabled. Please contact the admin."
}
}
}
}
return await originalImplementation.createCodePOST!(input);
}
}
}
}
})