Skip to main content

Allow users to change their email

caution

SuperTokens does not provide the UI for users to update their email, you will need to create the UI and setup a route on your backend to have this functionality.

In this section we will go over how you can create a route on your backend which can update a user's email. Calling this route will check if the new email is valid and not already in use and proceed to update the user's account with the new email. There are two types of flows here:

Flow 1: Update email without verifying the new email.#

In this flow a user is allowed to update their accounts email without verifying the new email id.

Step 1: Creating the /change-email route#

  • You will need to create a route on your backend which is protected by the session verification middleware, this will ensure that only a authenticated user can access the protected route.
  • To learn more about how to use the session verification middleware for other frameworks click here
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express"
import express from "express";

let app = express();

app.post("/change-email", verifySession(), async (req: SessionRequest, res: express.Response) => {
// TODO: see next steps
})

Step 2: Validate the new email and update the account#

  • Validate the input email.
  • Check that the account you are trying to update is not social account.
  • Update the account with the input email.
// the following example uses express
import ThirdPartyPasswordless from "supertokens-node/recipe/thirdpartypasswordless";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express"
import express from "express";

let app = express();

app.post("/change-email", verifySession(), async (req: SessionRequest, res: express.Response) => {

let session = req.session!;
let email = req.body.email;

// Validate the input email
if (!isValidEmail(email)) {
// TODO: handle invalid email error
return
}



// Check that the account to be updated is not a social account
{
let userId = session!.getUserId();
let userAccount = await ThirdPartyPasswordless.getUserById(userId!);

if ("thirdParty" in userAccount!) {
// TODO: handle error, cannot update email for third party users.
return
}
}

// Update the email
let resp = await ThirdPartyPasswordless.updatePasswordlessUser({
userId: session.getUserId(),
email: email,
});

if (resp.status === "OK") {
// TODO: send successfully updated email response
return
}
if (resp.status === "EMAIL_ALREADY_EXISTS_ERROR") {
// TODO: handle error that email exists with another account.
return
}
throw new Error("Should never come here");

})

function isValidEmail(email: string) {
let regexp = new RegExp(
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
);
return regexp.test(email);
}

Flow 2: Updating email after verifying the new email.#

In this flow the user's account is updated once they have verified the new email.

Step 1: Creating the /change-email route#

  • You will need to create a route on your backend which is protected by the session verification middleware, this will ensure that only a authenticated user can access the protected route.
  • To learn more about how to use the session verification middleware for other frameworks click here
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express"
import express from "express";

let app = express();

app.post("/change-email", verifySession(), async (req: SessionRequest, res: express.Response) => {
// TODO: see next steps
})

Step 2: Validate the email and initiate the email verification flow#

  • Validate the input email
  • Check that the user's account is not a social account.
  • Check if the input email is associated with an account.
  • Check if the input email is already verified.
  • If the email is NOT verified, create and send the verification email.
  • If the email is verified, update the account with the new email.
// the following example uses express
import ThirdPartyPasswordless from "supertokens-node/recipe/thirdpartypasswordless";
import EmailVerification from "supertokens-node/recipe/emailverification";
import { verifySession } from "supertokens-node/recipe/session/framework/express";
import { SessionRequest } from "supertokens-node/framework/express"
import express from "express";

let app = express();

app.post("/change-email", verifySession(), async (req: SessionRequest, res: express.Response) => {

let session = req.session!;
let email = req.body.email;

// validate the input email
if (!isValidEmail(email)) {
// TODO: handle error, email is invalid
return
}

// Check if the user's account is not a third party account
{
let sessionId = session!.getUserId();
let userAccount = await ThirdPartyPasswordless.getUserById(sessionId!);
if ( "thirdParty" in userAccount!) {
// TODO handle error, cannot update password for third party users.
return
}
}

// Check if the new email is already associated with another email-password user.
// If it is, then we throw an error. If it's already associated with this user,
// then we return a success response with an appropriate message.
let existingUsers = await ThirdPartyPasswordless.getUsersByEmail(email);

if (existingUsers.length != 0) {
for (let i = 0; i < existingUsers.length; i++) {
if (existingUsers[i].id === session.getUserId()) {
// TODO: send successful response, email already belongs to this account.
return
}
}
// TODO: handle error, email already belongs to another account
return
}

// Then, we check if the email is verified for this user ID or not.
// It is important to understand that SuperTokens stores email verification
// status based on the user ID AND the email, and not just the email.
let isVerified = await EmailVerification.isEmailVerified(session.getUserId(), email);

if (!isVerified) {
// Now we create and send the email verification link to the user for the new email.
let tokenInfo = await EmailVerification.createEmailVerificationToken(session.getUserId(), email);

if (tokenInfo.status === "OK") {
let link = "<YOUR_WEBSITE_DOMAIN>/auth/verify-email?token=" + tokenInfo.token;

await EmailVerification.sendEmail({
emailVerifyLink: link,
type: "EMAIL_VERIFICATION",
user: {
id: session.getUserId(),
email: email,
},
});
// TODO: send successful response that verification email has been sent
return
}
// else case is that the email is already verified (which can happen cause
// of some race condition). So we continue below..
}

// Since the email is verified, we try and do an update
let resp = await ThirdPartyPasswordless.updatePasswordlessUser({
userId: session.getUserId(),
email: email,
});

if (resp.status === "OK") {
// TODO: send successful response that email has been updated
return
}
if (resp.status === "EMAIL_ALREADY_EXISTS_ERROR") {
// Technically it should never come here cause we have
// checked for this above already, but just in case (some sort of race condition).
// TODO: handle error, email already exists for another account
return
}
throw new Error("Should never come here");

})

function isValidEmail(email: string) {
let regexp = new RegExp(
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
);
return regexp.test(email);
}

Step 3: Override the verifyEmailPost API to update the user's account on successful email verification#

  • Update the accounts email on successful email verification.
import SuperTokens from "supertokens-node";
import ThirdPartyPasswordless from "supertokens-node/recipe/thirdpartypasswordless";
import EmailVerification from "supertokens-node/recipe/emailverification";
import Session from "supertokens-node/recipe/session";

SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "...",
},
recipeList: [
ThirdPartyPasswordless.init({
flowType: "USER_INPUT_CODE_AND_MAGIC_LINK",
contactMethod: "EMAIL_OR_PHONE"
}),
EmailVerification.init({
mode: "REQUIRED",
override: {
apis: (oI) => {
return {
...oI,
verifyEmailPOST: async function (input) {
let response = await oI.verifyEmailPOST!(input);
if (response.status === "OK") {
// This will update the email of the user to the one
// that was just marked as verified by the token.
await ThirdPartyPasswordless.updatePasswordlessUser({
userId: response.user.id,
email: response.user.email,
});
}
return response;
},
};
},
},
}),
Session.init(),
],
});