Link User Accounts

Link user accounts together to form a primary and secondary relationship. On successful linking, the endpoint returns the new array of the primary account identities.

Auth0 supports the linking of user accounts from various identity providers. This allows a user to authenticate from any of their accounts and still be recognized by your app and associated with the same user profile.

Availability varies by Auth0 plan and login method

Both the login implementation you use and your Auth0 plan or custom agreement affect whether this feature is available. To learn more, read New Universal Login vs. Classic Universal Login and Pricing.

Auth0 treats all identities as separate by default. For example, if a user logs in first against the Auth0 database and then via Google or Facebook, these two attempts would appear to Auth0 as two separate users.

There are three ways to link accounts:

  • Use the Account Link extension

  • Use the Management API

  • Use Auth0.js

Use the tabs below to see the details for each option.

Install and configure the Account Link Extension extension in the Dashboard to prompt users that may have created a second account to link the new account with their old one on their first login. The user can choose to either link the two accounts or keep them separate if it was intentional.

Management API endpoint

The Auth0 Management API provides the Link a user account endpoint, which can be invoked in two ways:

  • User initiated account linking using Access Tokens with the update:current_user_identities scope
  • Server-side account linking using Access Token that contains the update:users scope

User initiated client-side account linking

For user initiated account linking from client-side code, use an Access Token that contains the following items in the payload:

  • update:current_user_identites scope
  • user_id of the primary account as part of the URL
  • ID Token of the secondary account must be signed with RS256 and an aud claim identifying the client that matches the value of the requesting Access Token's azp claim.

An Access Token that contains the update:current_user_identities scope can only be used to update the information of the currently logged-in user. Therefore, this method is suitable for scenarios where the user initiates the linking process.

{
  "method": "POST",
  "url": "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
  "httpVersion": "HTTP/1.1",
  "headers": [{
    "name": "Authorization",
    "value": "Bearer ACCESS_TOKEN"
  },
  {
    "name": "content-type",
    "value": "application/json"
  }],
  "postData" : {
    "mimeType": "application/json",
    "text": "{\"link_with\":\"SECONDARY_ACCOUNT_ID_TOKEN\"}"
  }
}

Was this helpful?

/

Server-side account linking

For server-side account linking, use an Access Token that contains the following items in the payload:

  • update:users scope
  • user_id of the primary account as part of the URL
  • user_id of the secondary account
  • ID Token of the secondary account must be signed with RS256 and an aud claim identifying the client that matches the value of the requesting Access Token's azp claim.

Access Tokens that contain the update:users scope can be used to update the information of any user. Therefore, this method is intended for use in server-side code only.

{
  "method": "POST",
  "url": "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
  "httpVersion": "HTTP/1.1",
  "headers": [{
    "name": "Authorization",
    "value": "Bearer ACCESS_TOKEN"
  },
  {
    "name": "content-type",
    "value": "application/json"
  }],
  "postData" : {
    "mimeType": "application/json",
    "text": "{\"provider\":\"SECONDARY_ACCOUNT_PROVIDER\", \"user_id\": \"SECONDARY_ACCOUNT_USER_ID\"}"
  }
}

Was this helpful?

/

The SECONDARY_ACCOUNT_USER_ID and SECONDARY_ACCOUNT_PROVIDER can be deduced by the unique ID of the user. For example, if the user ID is google-oauth2|108091299999329986433, set the google-oauth2 part as the provider, and the 108091299999329986433 part as the user_id at your request.

Instead of the provider and user_id, you can send the secondary account's ID Token as part of the payload:

{
  "method": "POST",
  "url": "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
  "httpVersion": "HTTP/1.1",
  "headers": [{
    "name": "Authorization",
    "value": "Bearer ACCESS_TOKEN"
  },
  {
    "name": "content-type",
    "value": "application/json"
  }],
  "postData" : {
    "mimeType": "application/json",
    "text": "{\"link_with\":\"SECONDARY_ACCOUNT_ID_TOKEN\"}"
  }
}

Was this helpful?

/

Auth0.js library

You can use the Auth0.js library.

First, you must get an Access Token that can be used to call the Management API. You can do it by specifying the https://{yourDomain}/api/v2/ audience when initializing Auth0.js. You will get the Access Token as part of the authentication flow. Alternatively, you can use the checkSession method.

Once you have the Access Token, you can create a new auth0.Management instance by passing it the account's Auth0 domain, and the Access Token.

To learn more, read Auth0.js > User management.

Add missing information with Rules

When a user logs in, apps receive user information from the primary identity. Auth0 does not attempt to automatically complete missing profile fields with information from the secondary identities. For example, if the primary identity comes from a database connection and is missing the given_name and family_name properties, and the secondary identity comes from a Google social connection that includes the first and last name of the user, then the application will not receive data contained in the second identity.

To add missing information to primary identities with information from secondary identities, you can write a rule like the following example:

function(user, context, callback) {
  
  const propertiesToComplete = ["given_name", "family_name", "name"];

  // go over each property and try to get missing
  // information from secondary identities
  for(var property of propertiesToComplete) {
    if (!user[property]) {
      for(var identity of user.identities) {
        if (identity.profileData && identity.profileData[property]) {
          user[property] = identity.profileData[property];
          break;
        }
      }
    }
  }
  
  callback(null, user, context);
}

Was this helpful?

/

Account linking with Actions

Auth0 Actions can be used to call the Management API to link user accounts. Auth0 does not automatically change to the correct primary user after Account Linking, so it must be changed within the Actions code upon successful Account Linking.

Account linking with Actions

Actions allows for flexible extension of the Auth0 capabilities, and caution must be taken when linking user accounts.

Insecurely linking accounts can allow malicious actors to access legitimate user accounts.

Please remain aware of the following:

Every manual account link should prompt the user to enter credentials. Your tenant should request authentication for both accounts before linking occurs.

Example account linking with Actions

A basic Account Linking implementation is as follows:

  1. Identify potential user accounts to link with an Action.

  2. Redirect to an external linking app using the Actions redirect functionality and token.

  3. Require the user to authenticate using their credentials for all accounts they wish to link.

  4. Redirect back to the Action with the outcome of the authentication encoded in a signed JWT and validate the authenticity and contents in that token.

  5. Perform Account Linking with a Management API call based on the results.

  6. Switch to the primary user for the remainder of the transaction using Actions.

In order to perform those steps, an example Post-Login Action may contain the following:

// @ts-check
const { ManagementClient } = require("auth0");

// High-level Workflow for Performing Account Linking in Actions
// --------------------------------------------------------------------------------------------
//
// The goal of this workflow is to systematically process all users for potential account
// linking. We want to detect situations where an end-user
// may have other identities in Auth0. These other identities would be discovered through
// matching verified email addresses. The Auth0 Action will ensure that all users are processed
// for account linking.
//
// A redirect app will be hosted by the customer to which we will redirect the user's browser
// when account linking might be available. The customer's account linking app is responsible
// authenticating that the current user owns the candidate linking identities. It will actually
// perform the account linking via Management API before redirecting back to the login flow's
// `/continue?state` endpoint.
//
// The Action will pick up here and update the primary user for the login if necessary and mark
// the user as having been successfully processed.
//
// Here are the details of the workflow:
//
// 1. Check if the user has been processed for account linking. The state for this is encoded in
//    the user's `app_metadata`. If the user has already been processed, exit this flow.
// 2. Discover other user identities that are candidates for account linking. If candidates are
//    found:
//   1. Encode the current user and any candidate identities into a signed session token to be
//      passed to the account linking app.
//   2. Redirect the user's browser to the account linking app with the signed session token.
//   3. The account linking app should challenge the user to authenticate using the candidate
//      identities.
//   4. If the user choses to proceed with account linking and successfully authenticates with
//      candidate identities, determine the primary user under which to consolidate identities.
//   5. Use the management API to perform account linking and to map any user or app metadata
//      from the other user accounts into the new primary user account.
//   6. Redirect back to `/continue?state` with a new signed token encoding the primary user
//      that is the outcome of the account linking app.
//   7. Validate the returned session token in the `onContinuePostLogin` entrypoint of the
//      Action. If a change of primary user is required, change the primary user via
//      `api.authentication.setPrimaryUser(newPrimaryUserId)`.
// 3. Mark the user as having been processed for account linking by storing a flag in the
//    user's `app_metadata`.

const LINKING_STATE_KEY = 'account_linking_state';

/**
 * Handler that will be called during the execution of a PostLogin flow.
 *
 * @param {Event} event - Details about the user and the context in which they are logging in.
 * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
 */
exports.onExecutePostLogin = async (event, api) => {
  if (!event.user.email_verified) {
    // We won't process users for account linking until they have verified their email address.
    // We might consider rejecting logins here or redirecting users to an external tool to
    // remind the user to confirm their email address before proceeding.
    //
    // In this example, we simply won't process users unless their email is verified.
    return;
  }

  const accountLinkingState = event.user.app_metadata[LINKING_STATE_KEY];

  if (accountLinkingState) {
    // Account linking has already been processed and completed for this user. No further work
    // to be done in this Action.
    return;
  }

  const token = await getManagementApiToken(event, api);
  const domain = event.secrets.TENANT_DOMAIN;
  const management = new ManagementClient({ domain, token });

  // Search for other candidate users
  const { data: candidateUsers } = await management.usersByEmail.getByEmail({
    email: event.user.email,
  });

  if (!Array.isArray(candidateUsers)) {
    return;
  }

  const candidateIdentities = candidateUsers.flatMap((user) => user.identities);

  if (!candidateIdentities.length) {
    // No candidate users for linking so mark the user as processed.
    api.user.setAppMetadata(LINKING_STATE_KEY, Date.now());
  }

  // Encode the current user and an array of their 
  const sessionToken = api.redirect.encodeToken({
    payload: {
      current_user: event.user,
      candidate_identities: candidateIdentities,
    },
    secret: event.secrets.REDIRECT_SECRET,
    expiresInSeconds: 20,
  });

  api.redirect.sendUserTo('https://url.for/account/linking/service', {
    query: { session_token: sessionToken },
  });
};

/**
 * Handler that will be invoked when this action is resuming after an external redirect. If your
 * onExecutePostLogin function does not perform a redirect, this function can be safely ignored.
 *
 * @param {Event} event - Details about the user and the context in which they are logging in.
 * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
 */
exports.onContinuePostLogin = async (event, api) => {
  // Validate the session token passed to `/continue?state` and extract the `user_id` claim. 
  const { user_id } = api.redirect.validateToken({
    secret: event.secrets.REDIRECT_SECRET,
    tokenParameterName: 'session_token',
  });

  if (user_id !== event.user.user_id) {
    // The account linking service indicated that the primary user changed.
    api.authentication.setPrimaryUser(user_id);
  }

  // Mark the user as having been processed for account linking
  api.user.setAppMetadata(LINKING_STATE_KEY, Date.now());
};

Was this helpful?

/

Learn more