How to Implement User Account Linking and Unlinking with buildLinkUserUrl in Auth0
Use AuthClient.buildLinkUserUrl to construct the authorization URL with the link_account scope, or leverage ServerClient.startLinkUser for a managed flow that handles PKCE verification and state storage automatically.
Auth0-Auth-JS provides enterprise-grade utilities for consolidating user identities through account linking and unlinking operations. The library exposes both low-level URL construction methods in AuthClient and high-level orchestration methods in ServerClient to manage the complete OAuth2 flow required to associate multiple authentication providers with a single user profile.
Understanding the Core Architecture
Auth0-Auth-JS separates concerns between raw URL generation and flow management. The source code reveals two distinct layers working together to complete account linking transactions.
AuthClient: Low-Level URL Construction
The AuthClient class in packages/auth0-auth-js/src/auth-client.ts provides the foundation for account linking operations. According to the source code at lines 94-102, the buildLinkUserUrl method constructs the /authorize endpoint URL with mandatory parameters including the link_account scope, PKCE challenge, and id_token_hint. Similarly, lines 122-130 implement buildUnlinkUserUrl using the unlink_account scope.
These methods return a { linkUserUrl: URL, codeVerifier: string } tuple, requiring you to manually store the PKCE verifier for later token exchange.
ServerClient: High-Level Flow Orchestration
The ServerClient class in packages/auth0-server-js/src/server-client.ts wraps the low-level AuthClient operations. As implemented at lines 72-82, startLinkUser retrieves the current session, calls buildLinkUserUrl internally, and automatically persists the PKCE codeVerifier and optional appState in a transaction store. The corresponding completeLinkUser method at lines 124-132 handles the authorization code exchange and returns your original application state.
The Account Linking Flow
Before initiating linking, the user must maintain an active session with a valid ID token. The complete flow follows four distinct phases:
- Authentication – Verify the user is logged in to obtain an
id_tokenfor theid_token_hintparameter. - URL Generation – Call
buildLinkUserUrldirectly orstartLinkUserfor managed flows, specifying the targetconnection(e.g.,"google-oauth2") and optionalconnectionScope. - User Authorization – Redirect the user-agent to the generated URL; Auth0 authenticates the secondary identity and returns to your
redirect_uriwith acodeandstate. - Token Exchange – Complete the flow by exchanging the authorization code for tokens, validating the PKCE verifier stored during step 2.
The types.ts file at lines 125-140 defines the configuration interfaces (BuildLinkUserUrlOptions, StartLinkUserOptions), while errors.ts at lines 151-159 provides specific error classes (BuildLinkUserUrlError, BuildUnlinkUserUrlError) for debugging construction failures.
Implementing Manual Linking with buildLinkUserUrl
For scenarios requiring custom redirect handling or non-standard UI flows, invoke AuthClient.buildLinkUserUrl directly. This approach requires manual PKCE verifier storage but offers maximum flexibility.
import { AuthClient } from '@auth0/auth0-spa-js';
const authClient = new AuthClient({
clientId: '<YOUR_CLIENT_ID>',
domain: '<YOUR_TENANT>.auth0.com',
});
async function initiateLinking() {
// Retrieve the current user's ID token from your session store
const idToken = '<CURRENT_USER_ID_TOKEN>';
const { linkUserUrl, codeVerifier } = await authClient.buildLinkUserUrl({
connection: 'google-oauth2', // Target identity provider
connectionScope: 'email profile', // Optional scopes for linked account
idToken, // Used as id_token_hint
authorizationParams: {
redirect_uri: 'https://myapp.com/callback/link',
},
});
// Critical: Store the PKCE verifier for the callback handler
sessionStorage.setItem('link_verifier', codeVerifier);
// Redirect user to Auth0
window.location.href = linkUserUrl.toString();
}
As implemented in auth-client.ts, this method automatically appends the required scopes (openid link_account offline_access), generates PKCE parameters, and merges requested_connection query parameters.
Full SDK Implementation with ServerClient
For standard server-side or single-page applications, use ServerClient to handle transaction state automatically. This approach eliminates manual PKCE storage and provides seamless appState preservation across the redirect.
Starting the Linking Process
import { ServerClient } from '@auth0/auth0-server-js';
const serverClient = new ServerClient({
clientId: '<YOUR_CLIENT_ID>',
domain: '<YOUR_TENANT>.auth0.com',
store: window.localStorage, // Or your custom storage implementation
});
async function startAccountLinking() {
const linkUrl = await serverClient.startLinkUser({
connection: 'google-oauth2',
connectionScope: 'email profile',
appState: { returnTo: '/profile' }, // Survives the redirect
authorizationParams: {
redirect_uri: 'https://myapp.com/callback/link',
},
});
window.location.href = linkUrl.toString();
}
The startLinkUser implementation at lines 72-82 in server-client.ts reads the existing session from the state store, invokes buildLinkUserUrl, and persists the transaction data in the internal transaction store.
Completing the Linking Process
In your callback route (/callback/link), complete the transaction:
import { ServerClient } from '@auth0/auth0-server-js';
const serverClient = new ServerClient({
clientId: '<YOUR_CLIENT_ID>',
domain: '<YOUR_TENANT>.auth0.com',
store: window.localStorage,
});
async function handleLinkCallback() {
const url = new URL(window.location.href);
// Exchanges code for tokens using stored PKCE verifier
const { appState } = await serverClient.completeLinkUser(url);
console.log('Account linked successfully');
// appState contains { returnTo: '/profile' }
window.location.href = appState?.returnTo || '/';
}
The completeLinkUser method at lines 124-132 executes the token exchange using completeInteractiveLogin internally and retrieves the original appState from the transaction store.
Implementing Account Unlinking
Unlinking follows an identical pattern but uses the unlink_account scope. The ServerClient provides startUnlinkUser and completeUnlinkUser methods that mirror the linking implementation at lines 144-154 and 84-92 respectively.
// Initiate unlinking
const unlinkUrl = await serverClient.startUnlinkUser({
connection: 'google-oauth2', // Identity to remove
appState: { message: 'Account disconnected' },
authorizationParams: {
redirect_uri: 'https://myapp.com/callback/unlink',
},
});
window.location.href = unlinkUrl.toString();
// Callback handler
const url = new URL(window.location.href);
const { appState } = await serverClient.completeUnlinkUser(url);
Under the hood, buildUnlinkUserUrl constructs the /authorize request with the unlink_account scope instead of link_account, as defined at lines 122-130 in auth-client.ts.
Summary
- Use
AuthClient.buildLinkUserUrlwhen you need raw URL construction and manual control over PKCE verifier storage; the method is located atpackages/auth0-auth-js/src/auth-client.tslines 94-102. - Use
ServerClient.startLinkUserfor automatic transaction management, PKCE storage, and state preservation; implemented atpackages/auth0-server-js/src/server-client.tslines 72-82. - Required scopes are automatically injected:
openid link_account offline_accessfor linking andopenid unlink_accountfor unlinking. - Always provide an active
idTokenfrom the current session when callingbuildLinkUserUrldirectly;ServerClientretrieves this automatically from the internal state store. - Complete the flow with
completeLinkUserorcompleteUnlinkUserto exchange the authorization code and clear the transaction store.
Frequently Asked Questions
What parameters are required for buildLinkUserUrl?
The buildLinkUserUrl method requires a BuildLinkUserUrlOptions object containing at minimum the connection string (e.g., "google-oauth2") and idToken (the current user's ID token used as id_token_hint). Optionally, provide connectionScope for additional permissions and authorizationParams for custom redirect URIs or audiences. The type definition is located in packages/auth0-auth-js/src/types.ts at lines 125-140.
How does ServerClient handle the PKCE verifier?
When using ServerClient.startLinkUser, the SDK automatically generates the PKCE codeVerifier, calls buildLinkUserUrl to create the challenge, and stores both values in the transaction store (packages/auth0-server-js/src/transaction-store.ts). During completeLinkUser, the SDK retrieves this verifier to exchange the authorization code for tokens, then deletes the transaction entry. This eliminates the risk of verifier loss during browser redirects.
Can I link multiple accounts to a single user?
Yes. The Auth0 platform supports multiple identity providers linked to one user profile. Call buildLinkUserUrl or startLinkUser repeatedly with different connection values (e.g., first google-oauth2, then linkedin). Each successful linking operation associates the new identity with the existing Auth0 user account while maintaining the same user_id root profile.
What happens if the linking transaction fails?
If the user denies permission or an error occurs during the Auth0 authorization, the callback URL will contain error parameters instead of a code. The completeLinkUser method will throw a BuildLinkUserUrlError (defined in packages/auth0-auth-js/src/errors.ts lines 151-159) or an OAuth error describing the failure reason. Always wrap your completion logic in try-catch blocks to handle these rejection scenarios gracefully.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →