---
title: Link social accounts
description: Link social accounts using the account linking feature.
sidebar:
  order: 6
---

## 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

<PaidFeatureCallout />

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

<DependentContent passive group="backend-language">
<ContentOption title="Go" value="go">
:::note[At the moment this feature is not supported through the Go SDK.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
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 && session.getTenantId() === tenantId) {
          return {
            shouldAutomaticallyLink: true,
            shouldRequireVerification: true,
          };
        }
        return {
          shouldAutomaticallyLink: false,
        };
      },
    }),
  ],
});
```
</Tab>
<Tab title="Go" value="go">

</Tab>
<Tab title="Python" value="python">
```python
from typing import Any, Dict, Optional, Union

from supertokens_python.recipe import accountlinking
from supertokens_python.recipe.accountlinking.types import (
    AccountInfoWithRecipeIdAndUserId,
    ShouldAutomaticallyLink,
    ShouldNotAutomaticallyLink,
)
from supertokens_python.recipe.session.interfaces import SessionContainer
from supertokens_python.types import User


async def should_do_automatic_account_linking(
    new_account_info: AccountInfoWithRecipeIdAndUserId,
    user: Optional[User],
    session: Optional[SessionContainer],
    tenant_id: str,
    user_context: Dict[str, Any],
) -> Union[ShouldNotAutomaticallyLink, ShouldAutomaticallyLink]:
    if user is None:
        return ShouldAutomaticallyLink(should_require_verification=True)

    if (
        session is not None
        and session.get_user_id() == user.id
        and session.get_tenant_id() == tenant_id
    ):
        return ShouldAutomaticallyLink(should_require_verification=True)

    return ShouldNotAutomaticallyLink()


accountlinking.init(
    should_do_automatic_account_linking=should_do_automatic_account_linking
)
```
</Tab>
</CodeGroup>

The callback allows a new user to become a primary user when `user` is absent. It links to an existing user only when
the session user and tenant match the proposed primary user and current tenant. It therefore does not enable linking
between existing users during first-factor authentication. To enable that behavior, see
[the automatic account linking page](./automatic-account-linking).

### 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](/references/backend-sdks/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](/authentication/social/initial-setup). 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](./automatic-account-linking#error-status-codes) (> `ERR_CODE_008`).

### 3. Access the social login access token and user profile 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:

<DependentContent passive group="backend-language">
<ContentOption title="Go" value="go">
:::note[At the moment this feature is not supported through the Go SDK.]
:::
</ContentOption>
</DependentContent>

<CodeGroup group="backend-language">
<Tab title="Node.js" value="nodejs">
```tsx
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;
              if (input.session !== undefined && input.session.getTenantId() === input.tenantId) {
                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 &&
                  input.session.getTenantId() === input.tenantId &&
                  response.user.id === input.session.getUserId() &&
                  existingUser !== undefined
                ) {
                  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({
      /* ... */
    }),
  ],
});
```
</Tab>
<Tab title="Go" value="go">

</Tab>
<Tab title="Python" value="python">
```python
from typing import Any, Dict, Optional

from supertokens_python.recipe import thirdparty
from supertokens_python.recipe.session.interfaces import SessionContainer
from supertokens_python.recipe.thirdparty.interfaces import (
    RecipeInterface,
    SignInUpOkResult,
)
from supertokens_python.recipe.thirdparty.types import RawUserInfoFromProvider


def override_thirdparty_functions(
    original_implementation: RecipeInterface,
) -> RecipeInterface:
    original_sign_in_up = original_implementation.sign_in_up

    async def sign_in_up(
        third_party_id: str,
        third_party_user_id: str,
        email: str,
        is_verified: bool,
        oauth_tokens: Dict[str, Any],
        raw_user_info_from_provider: RawUserInfoFromProvider,
        session: Optional[SessionContainer],
        should_try_linking_with_session_user: Optional[bool],
        tenant_id: str,
        user_context: Dict[str, Any],
    ):
        existing_login_method_count = None
        if session is not None and session.get_tenant_id() == tenant_id:
            from supertokens_python.asyncio import get_user

            existing_user = await get_user(session.get_user_id(), user_context)
            if existing_user is not None:
                existing_login_method_count = len(existing_user.login_methods)

        result = await original_sign_in_up(
            third_party_id,
            third_party_user_id,
            email,
            is_verified,
            oauth_tokens,
            raw_user_info_from_provider,
            session,
            should_try_linking_with_session_user,
            tenant_id,
            user_context,
        )

        if (
            isinstance(result, SignInUpOkResult)
            and session is not None
            and session.get_tenant_id() == tenant_id
            and result.user.id == session.get_user_id()
            and existing_login_method_count is not None
        ):
            _access_token = result.oauth_tokens.get("access_token")
            _provider_profile = result.raw_user_info_from_provider.from_user_info_api

            if len(result.user.login_methods) == existing_login_method_count + 1:
                pass  # The provider account was linked to this session user.
            else:
                pass  # The provider account was already linked to this session user.

        return result

    original_implementation.sign_in_up = sign_in_up
    return original_implementation


thirdparty.init(
    override=thirdparty.ThirdPartyOverrideConfig(
        functions=override_thirdparty_functions
    )
)
```
</Tab>
</CodeGroup>

The checks bind custom logic to the same session user and tenant. The provider identifiers, tokens, and profile in these
function results come from the backend-verified OAuth exchange; never use client-submitted provider identifiers as proof
that the current user owns a social account. A conflict leaves the provider login method linked to its existing primary
user and returns a linking error instead of moving it to the session user.

---

## See also

<CardGroup cols={3}>
  <Card title="Automatic Account Linking" href="/post-authentication/account-linking/automatic-account-linking" />
  <Card title="Add Passwords to an Account" href="/post-authentication/account-linking/add-passwords-to-an-existing-account" />
  <Card title="Manual Account Linking" href="/post-authentication/account-linking/manual-account-linking" />
  <Card title="Social Login Setup" href="/authentication/social/initial-setup" />
  <Card title="User Management Actions" href="/post-authentication/user-management/common-actions" />
</CardGroup>
