Skip to main content

Passwordless login via invite link

In this flow, the admin of the app will call an API to sign up a user and send them on invite link. Once the user clicks on that, they will be logged in and can access the app. If a user has not been invited before, their sign in attempt will fail.

We start by overriding the createCodePOST API to check if the input email / phone number was already invited before. If not, we send back a user friendly message to the frontend. We can check if a user was invited before by checking if they already exist in SuperTokens - cause users are created in SuperTokens when they successfully complete the invite flow.

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 existingPasswordlessUser = existingUsers.find(user => user.loginMethods.find(lM => lM.hasSameEmailAs(input.email) && lM.recipeId === "passwordless") !== undefined);
if (existingPasswordlessUser === undefined) {
// this is sign up attempt
return {
status: "GENERAL_ERROR",
message: "Sign up disabled. Please contact the admin."
}
}
} else {
let existingUsers = await supertokens.listUsersByAccountInfo(input.tenantId, {
phoneNumber: input.phoneNumber
});
let existingPasswordlessUser = existingUsers.find(user => user.loginMethods.find(lM => lM.hasSamePhoneNumberAs(input.phoneNumber) && lM.recipeId === "passwordless") !== undefined);
if (existingPasswordlessUser === undefined) {
// this is sign up attempt
return {
status: "GENERAL_ERROR",
message: "Sign up disabled. Please contact the admin."
}
}
}
return await originalImplementation.createCodePOST!(input);
}
}
}
}
})

createCodePOST is called when the user enters their email or phone number to login. We override it to check:

  • If there exists a user with the input email or phone number, it means they are signing in and so we allow the operation.
  • Otherwise it means that the user has not been invited to the app and we return an appropriate message to the frontend.

Now we will see how to make the API in which the admin of the app can create new users and invite them:

import express from "express";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express";
import UserRoles from "supertokens-node/recipe/userroles";
import Passwordless from "supertokens-node/recipe/passwordless";

let app = express();

app.post("/create-user", verifySession({
overrideGlobalClaimValidators: async function (globalClaimValidators) {
return [...globalClaimValidators,
UserRoles.UserRoleClaim.validators.includes("admin")]
}
}), async (req: SessionRequest, res) => {
let email = req.body.email;

// this will create the user in supertokens if they don't already exist.
await Passwordless.signInUp({
tenantId: "public",
email
})

let inviteLink = await Passwordless.createMagicLink({
tenantId: "public",
email
});

// TODO: send inviteLink to user's email
res.send("Success");
});
  • We guard the above API such that only signed in users with the "admin" role can call it. Feel free to change that part of the API.
  • The code above uses the default magic link path for the invite link (/auth/verify). If you are using the pre built UI, our frontend SDK will automatically log the user in. If you want to show a different UI to the user, then you can use a different path in the link (by modifying the inviteLink string) and make your own UI on that path. If you are making your own UI, you can use the consumeCode function provided by our frontend SDK to call the passwordless API that verifies the code in the URL and creates the user.
  • You can change the lifetime of the magic link, and therefore the invite link, by following this guide.
Multi Tenancy

In the above code snippets, we pass in the "public" tenantId when calling the functions - this is the default tenantId. If you are using our multi tenancy feature, you can pass in a different tenantId and this will ensure that the user with that email is added only to that tenant.

You will also need to pass in the tenantId to the createMagicLink function which will add the tenantId to the generated magic link. The resulting link will use the websiteDomain that is configured in the appInfo object in supertokens.init, but you can change the link's domain to match that of the tenant before sending it.

Looking for older version of the documentation?
Which UI do you use?
Custom UI
Pre built UI