Migrate to Access Tokens for Account Linking
Previously you could use ID tokens to link and unlink user accounts for some use cases. Auth0 is deprecating this functionality. You will now need to use access tokens in all cases.
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 withupdate:users
as the granted permission, then you can send as the request's body either theuser_id
or the ID Token of the secondary account.If you use an access token at the
Authorization
header withupdate: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 theazp
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 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 OAuth/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 theuser_id
of the secondary account in the payload.
See Link User Accounts for details.
Link user accounts
To link user accounts you can either call the Link a User Account endpoint of the Management API or use the Auth0.js library.
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.
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.
Get an access token with the
update:current_user_identities
scope as shown in the following example. The example uses the implicit flow, however, you can get access tokens for any application type.Using the previous method using an ID token, your code would look similar to this:
Using the new method using an access token, your code will look similar to this:to configure this snippet with your account
https://{yourDomain}/authorize? scope=openid &response_type=id_token &client_id={yourClientId} &redirect_uri=https://{yourApp}/callback &nonce=NONCE &state=OPAQUE_VALUE
Was this helpful?
/to configure this snippet with your account
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}
Was this helpful?
/To get an access token that can access the Management API:
Set the
audience
tohttps://{yourDomain}/api/v2/
.Ask for the
scope
${scope}
.Set the
response_type
toid_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:Notice that the{ "iss": "https://{yourDomain}/", "sub": "auth0|5a620d29a840170a9ef43672", "aud": "https://{yourDomain}/api/v2/", "iat": 1521031317, "exp": 1521038517, "azp": "{yourClientId}", "scope": "${scope}" }
Was this helpful?
/aud
is set to your tenant's API URI, thescope
to${scope}
, and thesub
to the user ID of the logged-in user.
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 theazp
claim of the access token used to make the request.
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.{ "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?
/
Link current user accounts with auth0.js
If you use auth0.js library 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.
Get an access token with the
update:current_user_identities
scope, then use this token to instantiateauth0.Management
. The final call tolinkUser
remains the same.Using the previous method using an ID token, your code would look similar to this:
Using the new method using an access token, your code will look similar to this:to configure this snippet with your account
// 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}' });
Was this helpful?
/to configure this snippet with your account
// 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}' });
Was this helpful?
/Asks for both an Id token and an access token in response (
responseType: `token id_token`
).Sets the Management API as the intended audience of the token (
audience: `https://YOUR_DOMAIN/api/v2/`
).Asks for the required permission (
scope: `update:current_user_identities`
).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
).
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"}'
Was this helpful?
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);
Was this helpful?
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))
}
Was this helpful?
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();
Was this helpful?
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);
});
Was this helpful?
#import <Foundation/Foundation.h>
NSDictionary *headers = @{ @"authorization": @"Bearer ACCESS_TOKEN",
@"content-type": @"application/json" };
NSDictionary *parameters = @{ @"link_with": @"SECONDARY_ACCOUNT_ID_TOKEN" };
NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];
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];
Was this helpful?
$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;
}
Was this helpful?
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"))
Was this helpful?
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
Was this helpful?
import Foundation
let headers = [
"authorization": "Bearer ACCESS_TOKEN",
"content-type": "application/json"
]
let parameters = ["link_with": "SECONDARY_ACCOUNT_ID_TOKEN"] as [String : Any]
let postData = JSONSerialization.data(withJSONObject: parameters, options: [])
let request = NSMutableURLRequest(url: NSURL(string: "https://{yourDomain}/api/v2/users/PRIMARY_ACCOUNT_USER_ID/identities")! as URL,
cachePolicy: .useProtocolCachePolicy,
timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? HTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
Was this helpful?
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 theazp
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.
First, you must get an access token with the
update:current_user_identities
scope.Using the previous method using an ID token, your code would look similar to this:
Using the new method using an access token, your code will look similar to this:to configure this snippet with your account
https://{yourDomain}/authorize? scope=openid &response_type=id_token &client_id={yourClientId} &redirect_uri=https://{yourApp}/callback &nonce={nonce} &state={opaqueValue}
Was this helpful?
/to configure this snippet with your account
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}
Was this helpful?
/To get an access token that can access the Management API:
Set the
audience
tohttps://{yourDomain}/api/v2/
.Ask for the
scope
${scope}
.Set the
response_type
toid_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:Notice that theto configure this snippet with your account
{ "iss": "https://{yourDomain}/", "sub": "auth0|5a620d29a840170a9ef43672", "aud": "https://{yourDomain}/api/v2/", "iat": 1521031317, "exp": 1521038517, "azp": "{yourClientId}", "scope": "update:current_user_identities" }
Was this helpful?
/aud
is set to your tenant's API URI, thescope
toupdate:current_user_identities
, and thesub
to the user ID of the logged in user.
Once you have the access token, you can call the Unlink a user identity endpoint of the Management API, using it in the
Authorization
header.Using the previous method, your call would look similar to this:
Using the new method, you call will look similar to this:DELETE https://YOUR_DOMAIN/api/v2/users/{primaryAccountUserId}/identities/{secondaryAccountProvider}/{secondaryAccountUserId} Authorization: 'Bearer {yourIdTokenOrMgmtApiAccessToken}'
Was this helpful?
/DELETE https://{yourDomain}/api/v2/users/{primaryAccountUserId}/identities/{secondaryAccountProvider}/{secondaryAccountUserId} Authorization: 'Bearer {yourMgmtApiAccessToken}'
Was this helpful?
/
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 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.