Skip to main content

Changes to third party flow

We need to start by maintaining an allow list of emails. You can either store this list in your own database, or then use the metadata feature provided by SuperTokens to store this. This may seem like a strange use case of the user metadata recipe we provide, but it works.

You want to implement the following functions on your backend:

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);
}
important

Remember to initialise the user metadata recipe on the backend recipeList during supertokens.init.

Multi Tenancy

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 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.

After that, we override the thirdPartySignInUpPOST API and the thirdPartySignInUp recipe function to check if the input email is allowed durign sign up. If not allowed, we send back a user friendly message to the frontend.

import ThirdPartyEmailPassword from "supertokens-node/recipe/thirdpartyemailpassword";

ThirdPartyEmailPassword.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
thirdPartySignInUp: async function (input) {
let existingUsers = await ThirdPartyEmailPassword.getUsersByEmail(input.tenantId, input.email);
if (existingUsers.length === 0) {
// this means that the email is new and is a sign up
if (!(await isEmailAllowed(input.email))) {
// email is not in allow list, so we disallow
throw new Error("No sign up")
}
}
// We allow the sign in / up operation
return originalImplementation.thirdPartySignInUp(input);
}
}
},
apis: (originalImplementation) => {
return {
...originalImplementation,
thirdPartySignInUpPOST: async function (input) {
try {
return await originalImplementation.thirdPartySignInUpPOST!(input);
} catch (err: any) {
if (err.message === "No sign up") {
// this error was thrown from our function override above.
// so we send a useful message to the user
return {
status: "GENERAL_ERROR",
message: "Sign ups are disabled. Please contact the admin."
}
}
throw err;
}
}
}
}
}
})

thirdPartySignInUpPOST is called when the user is redirected to the app from the third party provider post login. The API calls the thirdPartySignInUp recipe function in which we check:

  • If there exists a user with the input email, it means they are signing in and so we allow the operation.
  • Otherwise, we check if the input email is allowed by calling our isEmailAllowed function (which we implemented above). If not allowed, we throw an error with a custom message.
  • Finally, we override the thirdPartySignInUpPOST API to catch this custom error and return a message to the frontend which will be displayed to the user.

We can add emails to the allow list by calling the addEmailToAllowlist function we implemented above.

Which UI do you use?
Custom UI
Pre built UI