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
}
}
})
]
});
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
istrue
, 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
returnstrue
for theshouldAutomaticallyLink
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
istrue
, the current account (that's signing into) has its email as verified. - Your implementation for
shouldDoAutomaticAccountLinking
returnstrue
for theshouldAutomaticallyLink
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
returnstrue
for theshouldAutomaticallyLink
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) orupdateUser
(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
returnstrue
for theshouldAutomaticallyLink
boolean.
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
forshouldAutomaticallyLink
boolean in theshouldDoAutomaticAccountLinking
function implementation if there exists arecipeUserId
in thenewAccountInfo
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 theonAccountLinked
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...
}
})
]
});
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.