# How to Implement User Account Linking and Unlinking with buildLinkUserUrl in Auth0

> Learn to implement user account linking and unlinking with Auth0 buildLinkUserUrl. Securely connect user accounts using the auth0-auth-js library for seamless integration.

- Repository: [Auth0/auth0-auth-js](https://github.com/auth0/auth0-auth-js)
- Tags: how-to-guide
- Published: 2026-02-25

---

**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`](https://github.com/auth0/auth0-auth-js/blob/main/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`](https://github.com/auth0/auth0-auth-js/blob/main/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:

1. **Authentication** – Verify the user is logged in to obtain an `id_token` for the `id_token_hint` parameter.
2. **URL Generation** – Call `buildLinkUserUrl` directly or `startLinkUser` for managed flows, specifying the target `connection` (e.g., `"google-oauth2"`) and optional `connectionScope`.
3. **User Authorization** – Redirect the user-agent to the generated URL; Auth0 authenticates the secondary identity and returns to your `redirect_uri` with a `code` and `state`.
4. **Token Exchange** – Complete the flow by exchanging the authorization code for tokens, validating the PKCE verifier stored during step 2.

The [`types.ts`](https://github.com/auth0/auth0-auth-js/blob/main/types.ts) file at lines 125-140 defines the configuration interfaces (`BuildLinkUserUrlOptions`, `StartLinkUserOptions`), while [`errors.ts`](https://github.com/auth0/auth0-auth-js/blob/main/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.

```typescript
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`](https://github.com/auth0/auth0-auth-js/blob/main/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

```typescript
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`](https://github.com/auth0/auth0-auth-js/blob/main/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:

```typescript
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.

```typescript
// 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`](https://github.com/auth0/auth0-auth-js/blob/main/auth-client.ts).

## Summary

- **Use `AuthClient.buildLinkUserUrl`** when you need raw URL construction and manual control over PKCE verifier storage; the method is located at [`packages/auth0-auth-js/src/auth-client.ts`](https://github.com/auth0/auth0-auth-js/blob/main/packages/auth0-auth-js/src/auth-client.ts) lines 94-102.
- **Use `ServerClient.startLinkUser`** for automatic transaction management, PKCE storage, and state preservation; implemented at [`packages/auth0-server-js/src/server-client.ts`](https://github.com/auth0/auth0-auth-js/blob/main/packages/auth0-server-js/src/server-client.ts) lines 72-82.
- **Required scopes** are automatically injected: `openid link_account offline_access` for linking and `openid unlink_account` for unlinking.
- **Always provide an active `idToken`** from the current session when calling `buildLinkUserUrl` directly; `ServerClient` retrieves this automatically from the internal state store.
- **Complete the flow** with `completeLinkUser` or `completeUnlinkUser` to 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`](https://github.com/auth0/auth0-auth-js/blob/main/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`](https://github.com/auth0/auth0-auth-js/blob/main/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`](https://github.com/auth0/auth0-auth-js/blob/main/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.