Link social accounts
Overview
The following guide shows you how to link a social account to an existing user account.
The idea here is to reuse the existing sign up APIs, but call them with a session's access token. The APIs then create a new recipe user for that login method based on the input, and then link that to the session user. Of course, there are security checks done to ensure there is no account takeover risk, and this guide goes through them as well.
Before you start
This feature is only available to paid users.
We do not provide pre-built UI for this flow since it's probably something you want to add in your settings page or during the sign up process. This guide focuses on which APIs to call from your own UI.
The frontend code snippets below refer to the supertokens-web-js
SDK. You can continue to use this even if you have initialised the supertokens-auth-react
SDK, on the frontend.
Steps
1. Enable account linking 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 (user === undefined) {
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true
}
}
if (session !== undefined && session.getUserId() === user.id) {
return {
shouldAutomaticallyLink: true,
shouldRequireVerification: true
}
}
return {
shouldAutomaticallyLink: false
}
}
})
]
});
In the above implementation of shouldDoAutomaticAccountLinking
, account linking is only allowed if the input session is present. This means that the process is trying to link a social login account to an existing session user. Otherwise, account linking is not allowed, which means that first factor account linking does not occur. If you want to enable that too, you can see the automatic account linking page.
2. Create a UI to show social login buttons and handle login
First, you need to detect which social login methods are already linked to the user. You can do this by inspecting the user object on the backend and checking the thirdParty.id
property (the values are like google
, facebook
etc).
Then you have to create your own UI which asks the user to pick a social login provider to connect to. Once they click on one, redirect them to that provider's page. After login, the provider redirects the user back to your application (on the same path as the first factor login). You then call the APIs to consume the OAuth tokens and link the user.
The exact implementation of the above is available in the initial setup documentation. The two big differences in the implementation are:
- When you call the
signinup
API, you need to provide the session's access token in the request. If you are using the frontend SDK, the frontend network interceptors automatically handle this. The access token enables the backend to get a session and then link the social login account to session user. - New types of failure scenarios exist when calling the
signinup
API which are impossible during first factor login. To learn more about them, see the error codes section (>ERR_CODE_008
).
3. Remove the social login access token and user profile info on the backend
Once you call the signinup
API from the frontend, SuperTokens verifies the OAuth tokens and fetches the user's profile info from the third party provider. SuperTokens also links the newly created recipe user to the session user.
To fetch the new user object and also the third party profile, you can override the signinup
recipe function:
import SuperTokens, { User } from "supertokens-node";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import Session from "supertokens-node/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
ThirdParty.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
// override the thirdparty sign in / up function
signInUp: async function (input) {
let existingUser: User | undefined = undefined;
if (input.session !== undefined) {
existingUser = await SuperTokens.getUser(input.session.getUserId());
}
let response = await originalImplementation.signInUp(input);
if (response.status === "OK") {
let accessToken = response.oAuthTokens["access_token"];
let firstName = response.rawUserInfoFromProvider.fromUserInfoAPI!["first_name"];
if (input.session !== undefined && response.user.id === input.session.getUserId()) {
if (response.user.loginMethods.length === existingUser!.loginMethods.length + 1) {
// new social account was linked to session user
} else {
// social account was already linked to the session
// user from before
}
}
}
return response;
}
}
}
}
}),
Session.init({ /* ... */ })
]
});
Notice in the above snippet that the check is for input.session !== undefined && response.user.id === input.session.getUserId()
. This ensures that the custom logic runs only if it's linking a social account to your session account, and not during first factor login.