Linking User Accounts

The Account Linking feature requires a paid subscription to the Developer, Developer Pro or Enterprise plan. It is not available as a free feature. For more information on paid subscriptions refer to Pricing.

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

Note that Auth0 will treat 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.

You can implement functionality to enable a user to explicitly link accounts. In this scenario, the user would log in with an initial provider, perhaps Google. Your application would provide a link or button to enable them to link another account to the first one. The user would click on this link/button and your application would make a call so that when the user logs in with the second provider, the 2nd account is linked with the first.

Advantages of linking accounts

  • Allows users to log in with any identity provider without creating a separate profile for each.

  • Allows registered users to use a new social or passwordless login but continue using their existing profile.

  • Allows users that registered using a passwordless login to link to an account with a more complete profile.

  • Allows your apps to retrieve user profile data stored in various connections.

  • Allows your app to interact with several identity provider APIs with the user's identity, for example, to share their status over Twitter or Facebook. For more information see Calling an external IdP API.

  • Allows your app to gather a user's contacts from their social networks for expanded engagement opportunities.

The linking process

The process of linking accounts merges two existing user profiles into a single one. When linking accounts, a primary account and a secondary account must be specified.

For example, if the profile of the primary account is:

{
  "email": "your0@email.com",
  "email_verified": true,
  "name": "John Doe",
  "given_name": "John",
  "family_name": "Doe",
  "picture": "https://lh3.googleusercontent..../photo.jpg",
  "gender": "male",
  "locale": "en",
  "user_id": "google-oauth2|115015401343387192604",
  "identities": [
    {
        "provider": "google-oauth2",
        "user_id": "115015401343387192604",
        "connection": "google-oauth2",
        "isSocial": true
    }
  ],
  "user_metadata": {
    "color": "red"
  },
  "app_metadata": {
    "roles": [
        "Admin"
    ]
  },
  ...
}

and the profile of the secondary account is:

{
  "phone_number": "+14258831929",
  "phone_verified": true,
  "name": "+14258831929",
  "updated_at": "2015-10-08T18:35:18.102Z",
  "user_id": "sms|560ebaeef609ee1adaa7c551",
  "identities": [
    {
        "user_id": "560ebaeef609ee1adaa7c551",
        "provider": "sms",
        "connection": "sms",
        "isSocial": false
    }
  ],
  "user_metadata": {
      "color": "blue"
  },
  "app_metadata": {
      "roles": [
          "AppAdmin"
      ]
  },
  ...
}

after linking, the resulting profile will be:

{
  "email": "your@email.com",
  "email_verified": true,
  "name": "John Doe",
  "given_name": "John",
  "family_name": "Doe",
  "picture": "https://lh3.googleusercontent.../photo.jpg",
  "gender": "male",
  "locale": "en",
  "user_id": "google-oauth2|115015401343387192604",
  "identities": [
    {
      "provider": "google-oauth2",
      "user_id": "115015401343387192604",
      "connection": "google-oauth2",
      "isSocial": true
    },
    {
      "profileData": {
          "phone_number": "+14258831929",
          "phone_verified": true,
          "name": "+14258831929"
      },
      "user_id": "560ebaeef609ee1adaa7c551",
      "provider": "sms",
      "connection": "sms",
      "isSocial": false
    }
  ],
  "user_metadata": {
      "color": "red"
  },
  "app_metadata": {
      "roles": [
          "Admin"
      ]
  },
  ...
}

Note that:

  • The user_id and all other main profile properties continue to be those of the primary identity
  • The secondary account is now embedded in the identities array of the primary profile
  • The attributes of the secondary account are placed inside the profileData field of the corresponding identity inside the array
  • The user_metadata and app_metadata of the primary account have not unchanged
  • The user_metadata and app_metadata of the secondary account are discarded
  • There is no automatic merging of user profiles with associated identities
  • The secondary account is removed from the users list

Merging Metadata

Metadata are not automatically merged during account linking. If you want to merge them you have to do it manually, using the Auth0 APIv2 Update User endpoint.

The Auth0 Node.js SDK for APIv2 is also available. You can find sample code for merging metadata before linking using this SDK here.

The Management API

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

  1. With the JWT from both the primary and secondary accounts:
POST https://YOUR_AUTH0_DOMAIN/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities
Authorization: 'Bearer PRIMARY_ACCOUNT_JWT'
{
  link_with: 'SECONDARY_ACCOUNT_JWT'
}

This method requires a token with update:current_user_identities scope (which the authenticated user's JWT already has) and is suitable for scenarios where the user initiates the linking process. By requiring both JWTs, you can determine that the user was able to authenticate into both accounts and has the right to merge them.

  1. With the user id from both the primary and secondary accounts:
POST https://YOUR_AUTH0_DOMAIN/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities
Authorization: 'Bearer YOUR_API_V2_TOKEN'
{
  provider: 'SECONDARY_ACCOUNT_PROVIDER',
  user_id: 'SECONDARY_ACCOUNT_USER_ID'
}

This method requires a Management API Token with update:users scope and is intended for use in server-side code where you can make sure that both accounts correspond to the same person.

Get Accounts With the Same E-mail Address

To get a list of accounts with the same e-mail address, call the Get Users By Email endpoint. The request must include a Management API Token with the read:users scope:


curl --request GET \
  --url 'https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com' \
  --header 'authorization: Bearer YOUR_API_V2_TOKEN'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com");
var request = new RestRequest(Method.GET);
request.AddHeader("authorization", "Bearer YOUR_API_V2_TOKEN");
IRestResponse response = client.Execute(request);
package main

import (
	"fmt"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("authorization", "Bearer YOUR_API_V2_TOKEN")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.get("https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com")
  .header("authorization", "Bearer YOUR_API_V2_TOKEN")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com",
  "method": "GET",
  "headers": {
    "authorization": "Bearer YOUR_API_V2_TOKEN"
  }
}

$.ajax(settings).done(function (response) {
  console.log(response);
});
var request = require("request");

var options = { method: 'GET',
  url: 'https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email',
  qs: { email: 'user@example.com' },
  headers: { authorization: 'Bearer YOUR_API_V2_TOKEN' } };

request(options, function (error, response, body) {
  if (error) throw new Error(error);

  console.log(body);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer YOUR_API_V2_TOKEN" };

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"GET"];
[request setAllHTTPHeaderFields:headers];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer YOUR_API_V2_TOKEN"
  ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import http.client

conn = http.client.HTTPSConnection("")

headers = { 'authorization': "Bearer YOUR_API_V2_TOKEN" }

conn.request("GET", "/YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com", headers=headers)

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
require 'uri'
require 'net/http'

url = URI("https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Get.new(url)
request["authorization"] = 'Bearer YOUR_API_V2_TOKEN'

response = http.request(request)
puts response.read_body
import Foundation

let headers = ["authorization": "Bearer YOUR_API_V2_TOKEN"]

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/api/v2/users-by-email?email=user%40example.com")!,
                                        cachePolicy: .UseProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.HTTPMethod = "GET"
request.allHTTPHeaderFields = headers

let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    println(error)
  } else {
    let httpResponse = response as? NSHTTPURLResponse
    println(httpResponse)
  }
})

dataTask.resume()

The response would be:

[{
    "email": "user@example.com",
    "email_verified": false,
    "user_id": "auth0|......",
    "picture": "https://s.gravatar.com/avatar/1234abcd.png",
    "identities": [
        {
            "connection": "Username-Password-Authentication",
            "user_id": "......",
            "provider": "auth0",
            "isSocial": false
        }
    ],
    "updated_at": "2017-09-05T21:41:24.076Z",
    "created_at": "2017-08-31T13:12:32.052Z",
},
{
    "email": "user@example.com",
    "email_verified": true,
    "user_id": "auth0|......",
}]

Scenarios

Below are implementation details for calling the Linking Account API in these scenarios:

For security purposes, it is best to link accounts only if both e-mails are verified.

Automatic account linking

Auth0 does not support automatic linking, per se. However, you can implement automatic linking by setting up a Rule that will link accounts with the same e-mail address.

The rule is an example of linking accounts in server-side code using the Auth0 Management API Link a user account endpoint where you have both the primary and secondary user ids and an Management API v2 token with update:users scope.

Note, that if the primary account changes during the authorization transaction (for example, the account the user has logged in with, becomes a secondary account to some other primary account), you could get an error in the Authorization Code flow or an id_token with the wrong sub claim in the token flow. To avoid this, set context.primaryUser = 'auth0|user123' in the rule after account linking. This will tell the authorization server to use the user with id auth0|user123 for the rest of the flow.

For a rule template on automatic account linking, see Link Accounts with Same Email Address. If you want to merge metadata as well, see Link Accounts with Same Email Address while Merging Metadata.

User-initiated account linking

Typically, account linking will be initiated by an authenticated user. Your app must provide the UI, such as a Link accounts button on the user's profile page.

You can follow the User-initiated Account Linking tutorial or view the Auth0 jQuery Single Page App Account Linking Sample on Github for implementation details.

Suggested account linking

As with automatic linking, in this scenario you will set up a Rule that will link accounts with the same verified e-mail address. However, instead of completing the link automatically on authentication, your app will first prompt the user to link their identities.

You can follow the Account Linking from Server Side Code tutorial or view the Auth0 Node.js Regular Web App Account Linking Sample on Github for implementation details.

Unlinking accounts

The Auth0 Management API V2 also provides an Unlink a user account endpoint which can be used with either of these two scopes:

  • update:current_user_identities: when calling the endpoint from client-side code where you have the primary user's JWT (which comes with this scope).
  • update:users: when calling the endpoint from server-side code where you need to generate an Management API v2 TOKEN having this scope.
DELETE https://YOUR_AUTH0_DOMAIN/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities/SECONDARY_ACCOUNT_PROVIDER/SECONDARY_ACCOUNT_USER_ID
Authorization: 'Bearer [PRIMARY_ACCOUNT_JWT OR API_V2_TOKEN]'

As a result of unlinking the accounts, the secondary account is removed from the identities array of the primary account, and a new secondary user account is created. This means that if, for example, a user was john@example.com using Facebook to login, and used the same email address to login via Linkedin, and then unlinked those accounts, then you will end up with two separate accounts; both using john@example.com, one for each identity provider in question.

When accounts are linked, the secondary account's metadata is not linked; thus, when unlinked and the secondary account becomes separated again, it will have no metadata.

If your goal is to delete the secondary identity entirely, you'll want to first unlink the accounts, and then delete the newly (re)created secondary account.