Skip to main content

Automatic account linking

Overview

Automatic account linking is a feature that allows users to automatically sign in to their existing account using more than one login method. On a high level, SuperTokens automatically links the accounts for the different login methods provided that:

  • Their emails or phone numbers are the same.
  • They have verified their emails or phone numbers.

SuperTokens ensures that accounts are automatically linked only if there is no risk of account takeover.

Before you start

This feature is only available to paid users.

Steps

1. Enable the recipe

You can enable this feature by providing the following callback implementation on the backend SDK:

import supertokens, { User, RecipeUserId } from "supertokens-node";
import AccountLinking from "supertokens-node/recipe/accountlinking";
import { AccountInfoWithRecipeId } from "supertokens-node/recipe/accountlinking/types";
import { SessionContainerInterface } from "supertokens-node/recipe/session/types";

supertokens.init({
supertokens: {
connectionURI: "...",
apiKey: "..."
},
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
AccountLinking.init({
shouldDoAutomaticAccountLinking: async (newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: any) => {
if (session !== undefined) {
return {
shouldAutomaticallyLink: false
}
}
if (newAccountInfo.recipeUserId !== undefined && user !== undefined) {
let userId = newAccountInfo.recipeUserId.getAsString();
let hasInfoAssociatedWithUserId = false // TODO: add your own implementation here.
if (hasInfoAssociatedWithUserId) {
return {
shouldAutomaticallyLink: false
}
}
}
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true
}
}
})
]
});


important

If you are returning shouldRequireVerification as true, then you need to also enable the email verification recipe in REQUIRED mode. This means that if the login method does not inherently verify the email (like for email password login), SuperTokens requires the user to go through the email verification flow first. Then, it attempts auto linking of the account. For other login methods like sign in with Google, the email is already verified during login. The user does not need to verify the email again, and account linking occurs immediately.

If you enable email verification in OPTIONAL mode, the user can access the account after email password login. However, account linking only occurs after they verify their email later on. This is risky because while the user had access to their email password account after sign up, they could lose access after verification and account linking completes due to the change in the primary user ID. A callback is available to help migrate data from one user ID to another.

You can use the input of the function to dynamically decide if you want to do account linking for a particular user and / or login method or not.

References

Automatic account linking scenarios

During sign up

If there exists another account with the same email or phone number within the current tenant, the new account links to the existing account if:

  • The existing account is a primary user
  • If shouldRequireVerification is true, the new account needs creation via a method that has the email as verified (for example via passwordless or google login). If the new method doesn't inherently verify the email (like in email password login), the accounts link post email verification.
  • Your implementation for shouldDoAutomaticAccountLinking returns true for the shouldAutomaticallyLink boolean.

During sign in

If the current user is not already linked and if there exists another user with the same email or phone number within the current tenant, the accounts link if:

  • The user signing into is not a primary user, and the other user with the same email / phone number is a primary user
  • If shouldRequireVerification is true, the current account (that's signing into) has its email as verified.
  • Your implementation for shouldDoAutomaticAccountLinking returns true for the shouldAutomaticallyLink boolean.

After email verification

If the current user whose email got verified is not a primary user, and there exists another primary user in the same tenant with the same email, then the two accounts link if:

  • Your implementation for shouldDoAutomaticAccountLinking returns true for the shouldAutomaticallyLink boolean.

For a primary user, if two login methods (L1 & L2) share the same email, but L1's email verifies and L2's does not, SuperTokens automatically verifies L2's email under these conditions:

  • The user logs in with L2.
  • The updateEmailOrPassword (email password recipe) or updateUser (passwordless recipe) function calls to update L2's email to match L1's.

During the password reset flow

If there already exists a user with the same email in a non email password recipe (social login for example), and the user is doing a password reset flow, a new email password user creates and links to the existing account if:

  • The non email password user is a primary user.
  • Your implementation for shouldDoAutomaticAccountLinking returns true for the shouldAutomaticallyLink boolean.
Email update implications

When updating a user's login email, SuperTokens ensures account linking conditions remain valid. A primary user's email cannot update to match another primary user's email.

User A has login methods AL1 (email e1) and AL2 (email e1). User B has login methods BL1 (email e2) and BL2 (email e3). Updating AL1's email to e2 or e3 is not allowed, as it would create two primary users with the same email.

Email updates occur in these scenarios:

  • updateEmailOrPassword function (email password recipe)
  • updateUser function (passwordless recipe)
  • Social login (if email from provider has changed)

If the update violates account linking rules, the operation fails:

  • Function calls return a status indicating the update was impossible.
  • Social login API calls return a status prompting the user to contact support.

User data changes during account linking

When two accounts link, the primary user ID of the non primary user changes. For example, if User A has a primary user ID p1 and user B, which is a non primary user, has a user ID of p2, and they link, then the primary user ID of User B changes to p1.

This has an effect that if the user logs in with login method from User B, the session.getUserId() returns p1. If there was any older data associated with User B (against user ID p2), in your database, that data essentially becomes "lost".

To prevent this scenario, you should:

  • Make sure that you return false for shouldAutomaticallyLink boolean in the shouldDoAutomaticAccountLinking function implementation if there exists a recipeUserId in the newAccountInfo object, and if you have some information related to that user ID in your own database. This appears in the code snippet above.
  • If you do not want to return false in this case, and want the accounts to link, then make sure to implement the onAccountLinked callback:

import supertokens, { User, RecipeUserId } from "supertokens-node"
import AccountLinking from "supertokens-node/recipe/accountlinking";
import { AccountInfoWithRecipeId, RecipeLevelUser } from "supertokens-node/recipe/accountlinking/types";
import { SessionContainerInterface } from "supertokens-node/recipe/session/types";

supertokens.init({
supertokens: {
connectionURI: "...",
apiKey: "..."
},
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
AccountLinking.init({
shouldDoAutomaticAccountLinking: async (newAccountInfo: AccountInfoWithRecipeId & { recipeUserId?: RecipeUserId }, user: User | undefined, session: SessionContainerInterface | undefined, tenantId: string, userContext: any) => {
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true
}
},
onAccountLinked: async (user: User, newAccountInfo: RecipeLevelUser, userContext: any) => {
let olderUserId = newAccountInfo.recipeUserId.getAsString()
let newUserId = user.id;

// TODO: migrate data from olderUserId to newUserId in your database...
}
})
]
});
caution

If your logic in onAccountLinked throws an error, then it is not called again, and still results in linking the accounts. However, the end user would see an error on the UI as the API returns a 500 status code. They can retry the login action and log into the primary user's account as expected.

Error status codes

The following is a list of error status codes that the end user might see during their interaction with the login UI (as a general error message in the pre-built UI).

Changing the error message on the frontend

If you want to display a different message to the user, or use a different status code, you can change them on the frontend via the language translation feature.

See also