> ## Documentation Index
> Fetch the complete documentation index at: https://auth0.com/llms.txt
> Use this file to discover all available pages before exploring further.

> Describes how to migrate from using ID tokens to access tokens when linking user accounts.

# Migrate to Access Tokens for Account Linking

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****MASKED*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

export const codeExample1 = `https://{yourDomain}/authorize?
  scope=openid
  &response_type=id_token
  &client_id={yourClientId}
  &redirect_uri=https://{yourApp}/callback
  &nonce=NONCE
  &state=OPAQUE_VALUE
`;

export const codeExample2 = `https://{yourDomain}/authorize?
  audience=https://{yourDomain}/api/v2/
  &scope=update:current_user_identities
  &response_type=token%20id_token
  &client_id={yourClientId}
  &redirect_uri=https://{yourApp}/callback
  &nonce={nonce}
  &state={opaqueValue}
`;

export const codeExample3 = `{
  "iss": "https://{yourDomain}/",
  "sub": "auth0|5a620d29a840170a9ef43672",
  "aud": "https://{yourDomain}/api/v2/",
  "iat": 1521031317,
  "exp": 1521038517,
  "azp": "{yourClientId}",
  "scope": "\${scope}"
}`;

export const codeExample4 = `{
  "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\\"}"
  }
}`;

export const codeExample5 = `// get an ID Token
var webAuth = new auth0.WebAuth({
  clientID: '{yourClientId}',
  domain: '{yourDomain}',
  redirectUri: 'https://{yourApp}/callback',
  scope: 'openid',
  responseType: 'id_token'
});
// create a new instance
var auth0Manage = new auth0.Management({
  domain: '{yourDomain}',
  token: '{yourIdToken}'
});`;

export const codeExample6 = `// get an Access Token
  var webAuth = new auth0.WebAuth({
    clientID: '{yourClientId}',
    domain: '{yourDomain}',
    redirectUri: 'https://{yourApp}/callback',
    audience: 'https://{yourDomain}/api/v2/',
    scope: 'update:current_user_identities',
    responseType: 'token id_token'
  });
  // create a new instance
  var auth0Manage = new auth0.Management({
    domain: '{yourDomain}',
    token: '{yourMgmtApiAccessToken}'
  });
`;

export const codeExample17 = `https://{yourDomain}/authorize?
  scope=openid
  &response_type=id_token
  &client_id={yourClientId}
  &redirect_uri=https://{yourApp}/callback
  &nonce={nonce}
  &state={opaqueValue}`;

export const codeExample18 = `https://{yourDomain}/authorize?
  audience=https://{yourDomain}/api/v2/
  &scope=update:current_user_identities
  &response_type=token%20id_token
  &client_id={yourClientId}
  &redirect_uri=https://{yourApp}/callback
  &nonce={nonce}
  &state={opaqueValue}
`;

export const codeExample19 = `{
  "iss": "https://{yourDomain}/",
  "sub": "auth0|5a620d29a840170a9ef43672",
  "aud": "https://{yourDomain}/api/v2/",
  "iat": 1521031317,
  "exp": 1521038517,
  "azp": "{yourClientId}",
  "scope": "update:current_user_identities"
}`;

export const codeExample20 = `DELETE https://{yourDomain}/api/v2/users/{primaryAccountUserId}/identities/{secondaryAccountProvider}/{secondaryAccountUserId}
  Authorization: 'Bearer {yourMgmtApiAccessToken}'
`;

Previously you could use <Tooltip tip="ID Token: Credential meant for the client itself, rather than for accessing a resource." cta="View Glossary" href="/docs/glossary?term=ID+tokens">ID tokens</Tooltip> to link and unlink user accounts for some use cases. Auth0 is deprecating this functionality. You will now need to use <Tooltip tip="ID Token: Credential meant for the client itself, rather than for accessing a resource." cta="View Glossary" href="/docs/glossary?term=access+tokens">access tokens</Tooltip> in all cases.

<Warning>
  This deprecation is in response to a potential security vulnerability. Auth0 strongly recommends that you update your code as soon as possible.
</Warning>

## Features affected

The changes in account linking are:

* You can no longer use an ID token at the `Authorization` header, an access token must be used instead.
* If you use an access token at the `Authorization` header with `update:users` as the granted permission, then you can send as the request's body either the `user_id` or the ID Token of the secondary account.
* If you use an access token at the `Authorization` header with `update:current_user_metadata` as the granted permission, then you can only send the ID token of the secondary account in the request's body.
* If you send the ID token of the secondary account in the request's body (the use cases described in the previous two bullets) then the following must apply:

  * The ID token must be signed using `RS256` (you can set this value at **Dashboard > Clients > Client Settings > Advanced Settings > OAuth**.
  * The claim `aud` of the ID Token, must identify the client and be the same value with the `azp` claim of the access token.
* For unlinking accounts, you can no longer use an ID token at the `Authorization` header. You must use an access token instead.

There are several ways you can link and unlink accounts. In the following list you can see the use cases and how the changes affect them.

| Use Case                                                                                                                                                                                                                         | Status       |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
| Use the Management API `POST /api/v2/users/{id}/identities` endpoint and send the primary account's ID token in the `Authorization` header.                                                                                      | Affected     |
| Use the Management API `POST /api/v2/users/{id}/identities` endpoint and send an access token (with scope `update:users`) in the `authorization` header, and the secondary account's `user_id` in the payload.                   | Not affected |
| Use the Management API `POST /api/v2/users/{id}/identities` endpoint and send an access token (with scope `update:current_user_identities`) in the `Authorization` header, and the secondary account's `user_id` in the payload. | Affected     |
| Use the Management API `POST /api/v2/users/{id}/identities` endpoint and send an access token in the `Authorization` header and the secondary account's ID token in the payload.                                                 | New use case |
| Use the auth0.js library and the primary account's ID token to instantiate `auth0.Management`.                                                                                                                                   | Affected     |
| Use the auth0.js library and an access token (with scope `update:users`) to instantiate `auth0.Management`.                                                                                                                      | Not affected |
| Use the auth0.js library and an access token (with scope `update:current_user_identities`) to instantiate `auth0.Management`.                                                                                                    | Affected     |
| Use the Management API `DELETE	/api/v2/users/{id}/identities/{provider}/{user_id}` endpoint and send the primary account's ID token in the `Authorization` header.                                                               | Affected     |
| Use the Management API `DELETE	/api/v2/users/{id}/identities/{provider}/{user_id}` endpoint and send an access token in the `Authorization` header.                                                                              | Not affected |

## Actions

Review all your calls to the account linking [Identities endpoint](https://auth0.com/docs/api/management/v2/#!/Users/post_identities) and update those that make use of the vulnerable flow described above. You can update your calls to either of the following:

* **Client-side / user-initiated linking scenarios:** For client-side linking scenarios, make the call to the Identities endpoint using an access token with the `update:current_user_identities` scope, and provide the ID token of the secondary account in the payload (`link_with`). This ID token must be obtained through an <Tooltip tip="OAuth 2.0: Authorization framework that defines authorization protocols and workflows." cta="View Glossary" href="/docs/glossary?term=OAuth">OAuth</Tooltip>/OIDC-conformant flow.
* **Server-side linking scenarios**:For server-side linking scenarios, make the call to Identities endpoint using an access token with the `update:users` scope and provide the `user_id` of the secondary account in the payload.

See [Link User Accounts](/docs/manage-users/user-accounts/user-account-linking/link-user-accounts) for details.

### Link user accounts

To link user accounts you can either call the [Link a User Account endpoint](https://auth0.com/docs/api/management/v2#!/Users/post_identities) of the <Tooltip tip="Management API: A product to allow customers to perform administrative tasks." cta="View Glossary" href="/docs/glossary?term=Management+API">Management API</Tooltip> or use the [Auth0.js library](/docs/libraries/auth0js).

#### Link current user accounts with the Management API

A common use case is to allow the logged in user to link their accounts using your app.

Prior to the deprecation you could use the primary user's ID token or access token (which contained the `update:current_user_identities` scope) to authenticate with the Management API and use the [Link a User Account endpoint](https://auth0.com/docs/api/management/v2#!/Users/post_identities).

Now you must get an access token (containing the `update:current_user_identities` scope) and use that to authenticate with the API and use the Link a User Account endpoint. The payload must be the ID token of the secondary user.

1. Get an access token with the `update:current_user_identities` scope as shown in the following example. The example uses the [implicit flow](/docs/get-started/authentication-and-authorization-flow/implicit-flow-with-form-post), however, you can [get access tokens](/docs/secure/tokens/access-tokens/get-access-tokens) for any application type.

2. Using the previous method using an ID token, your code would look similar to this:

   <AuthCodeBlock children={codeExample1} language="http" />

   Using the new method using an access token, your code will look similar to this:

   <AuthCodeBlock children={codeExample2} language="http" />

3. To get an access token that can access the Management API:

   1. Set the `audience` to `https://{yourDomain}/api/v2/`.
   2. Ask for the `scope` `${scope}`.
   3. Set the `response_type` to `id_token token` so Auth0 will send both an ID token and an access token.
      If we decode the access token and review its contents we can see the following:

      <AuthCodeBlock children={codeExample3} language="json" />

      Notice that the `aud` is set to your tenant's API URI, the `scope` to `${scope}`, and the `sub` to the user ID of the logged-in user.

4. The following must apply:

   1. The secondary account's ID Token must be signed with `RS256.`
   2. The `aud` claim in the secondary account's ID token must identify the client, and hold the same value with the `azp` claim of the access token used to make the request.

5. Once you have the access token, you can use it to link user accounts. This part remains the same, nothing else changes in the request except for the value you use as `Bearer` token. The response also remains the same.

   <AuthCodeBlock children={codeExample4} language="json" />

#### Link current user accounts with auth0.js

If you use the [auth0.js library](/docs/libraries/auth0js) to access the Management API and link accounts, then you probably use the ID token of the user's primary identity to instantiate `auth0.Management` and use it to link accounts.

1. Get an access token with the `update:current_user_identities` scope, then use this token to instantiate `auth0.Management`. The final call to `linkUser` remains the same.
2. Using the previous method using an ID token, your code would look similar to this:

   <AuthCodeBlock children={codeExample5} language="javascript" />

   Using the new method using an access token, your code will look similar to this:

   <AuthCodeBlock children={codeExample6} language="javascript" />

   1. Asks for both an Id token and an access token in response (`` responseType: `token id_token` ``).
   2. Sets the Management API as the intended audience of the token (`` audience: `https://YOUR_DOMAIN/api/v2/` ``).
   3. Asks for the required permission (`` scope: `update:current_user_identities` ``).
   4. Authenticates with the Management API using the access token.

#### Link any user account with the Management API

If you get an access token for account linking that contains the `update:users` scope, and send the secondary account's `user_id` and `provider` in the request, then you don't have to make any changes.

However, this new method introduces an alternative to this. You still use an access token that contains the `update:users` scope to authenticate with the API, but in the request's payload you can send the secondary's account ID token (instead of `user_id` and `provider`).

<AuthCodeGroup>
  ```bash cURL lines theme={null}
  curl --request POST \
    --url 'https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities' \
    --header 'authorization: Bearer ACCESS_TOKEN' \
    --header 'content-type: application/json' \
    --data '{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}'
  ```

  ```csharp C# lines theme={null}
  var client = new RestClient("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities");
  var request = new RestRequest(Method.POST);
  request.AddHeader("authorization", "Bearer ACCESS_TOKEN");
  request.AddHeader("content-type", "application/json");
  request.AddParameter("application/json", "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}", ParameterType.RequestBody);
  IRestResponse response = client.Execute(request);
  ```

  ```go Go lines theme={null}
  package main

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

  func main() {

  	url := "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities"

  	payload := strings.NewReader("{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}")

  	req, _ := http.NewRequest("POST", url, payload)

  	req.Header.Add("authorization", "Bearer ACCESS_TOKEN")
  	req.Header.Add("content-type", "application/json")

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

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

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

  }
  ```

  ```java Java lines theme={null}
  HttpResponse<String> response = Unirest.post("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities")
    .header("authorization", "Bearer ACCESS_TOKEN")
    .header("content-type", "application/json")
    .body("{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}")
    .asString();
  ```

  ```javascript Node.JS lines theme={null}
  var axios = require("axios").default;

  var options = {
    method: 'POST',
    url: 'https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities',
    headers: {authorization: 'Bearer ACCESS_TOKEN', 'content-type': 'application/json'},
    data: {link_with: 'SECONDARY_ACCOUNT_ID_TOKEN'}
  };

  axios.request(options).then(function (response) {
    console.log(response.data);
  }).catch(function (error) {
    console.error(error);
  });
  ```

  ```php PHP lines theme={null}
  $curl = curl_init();

  curl_setopt_array($curl, [
    CURLOPT_URL => "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "POST",
    CURLOPT_POSTFIELDS => "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}",
    CURLOPT_HTTPHEADER => [
      "authorization: Bearer ACCESS_TOKEN",
      "content-type: application/json"
    ],
  ]);

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

  curl_close($curl);

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

  ```python Python lines theme={null}
  import http.client

  conn = http.client.HTTPSConnection("")

  payload = "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}"

  headers = {
      'authorization': "Bearer ACCESS_TOKEN",
      'content-type': "application/json"
      }

  conn.request("POST", "/{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities", payload, headers)

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

  print(data.decode("utf-8"))
  ```

  ```ruby Ruby lines theme={null}
  require 'uri'
  require 'net/http'
  require 'openssl'

  url = URI("https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities")

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

  request = Net::HTTP::Post.new(url)
  request["authorization"] = 'Bearer ACCESS_TOKEN'
  request["content-type"] = 'application/json'
  request.body = "{"link_with":"SECONDARY_ACCOUNT_ID_TOKEN"}"

  response = http.request(request)
  puts response.read_body
  ```
</AuthCodeGroup>

The following must apply:

* The secondary account's ID token must be signed with `RS256`.
* The `aud` claim in the secondary account's ID token must identify the client, and hold the same value with the `azp` claim of the access token used to make the request.

### Unlink user accounts

If you use ID tokens to unlink accounts then you must update your code to use access tokens.

1. First, you must get an access token with the `update:current_user_identities` scope.

2. Using the previous method using an ID token, your code would look similar to this:

   <AuthCodeBlock children={codeExample17} language="http" />

   Using the new method using an access token, your code will look similar to this:

   <AuthCodeBlock children={codeExample18} language="http" />

3. To get an access token that can access the Management API:

   1. Set the `audience` to `https://{yourDomain}/api/v2/`.
   2. Ask for the `scope` `${scope}`.
   3. Set the `response_type` to `id_token token` so Auth0 will send both an ID token and an access token.
      If we decode the access token and review its contents we can see the following:

      <AuthCodeBlock children={codeExample19} language="json" />

      Notice that the `aud` is set to your tenant's API URI, the `scope` to `update:current_user_identities`, and the `sub` to the user ID of the logged in user.

4. Once you have the access token, you can call the [Unlink a user identity endpoint](https://auth0.com/docs/api/management/v2#!/Users/delete_user_identity_by_user_id) of the Management API, using it in the `Authorization` header.

5. Using the previous method, your call would look similar to this:

   ```http lines theme={null}
   DELETE https://YOUR_DOMAIN/api/v2/users/{primaryAccountUserId}/identities/{secondaryAccountProvider}/{secondaryAccountUserId}
       Authorization: 'Bearer {yourIdTokenOrMgmtApiAccessToken}'
   ```

   Using the new method, your call will look similar to this:

   <AuthCodeBlock children={codeExample20} language="http" />

## Security considerations

We have identified a weakness in a particular account linking flow that could allow it to be misused in specific circumstances. We have found no evidence that this has been used maliciously but have decided to deprecate the flow to prevent that ever happening.

Therefore, Auth0 requires customers using the affected account linking flow to migrate to a more secure implementation before 19 October 2018. Migration paths are provided in this guide, which should not result in any lost functionality.

On or after 19 October 2018 the affected account linking flow will be disabled and you will experience run-time errors.

You are impacted if you call the [Post Identities endpoint](https://auth0.com/docs/api/management/v2#!/Users/post_identities) using a token (ID or access token) with the scope `update:current_user_identities` in the Authorization header and include the secondary account's `user_id` in the payload. No other use cases are impacted.

## Learn more

* [Migrate to Management API Endpoints with Access Tokens](/docs/troubleshoot/product-lifecycle/past-migrations/migrate-to-calling-api-with-access-tokens)
