|
117 | 117 | - [On the server (App Router)](#on-the-server-app-router-3) |
118 | 118 | - [On the server (Pages Router)](#on-the-server-pages-router-3) |
119 | 119 | - [Middleware](#middleware-3) |
| 120 | +- [Custom Token Exchange](#custom-token-exchange) |
| 121 | + - [When to Use](#when-to-use) |
| 122 | + - [Basic Usage](#basic-usage) |
| 123 | + - [With Organization](#with-organization) |
| 124 | + - [With Actor Token (Delegation)](#with-actor-token-delegation) |
| 125 | + - [Error Handling](#error-handling-2) |
| 126 | + - [Token Type Requirements](#token-type-requirements) |
| 127 | + - [Limitations](#limitations) |
| 128 | + - [DPoP Support](#dpop-support) |
120 | 129 | - [Customizing Auth Handlers](#customizing-auth-handlers) |
121 | 130 | - [Run custom code before Auth Handlers](#run-custom-code-before-auth-handlers) |
122 | 131 | - [Run code after callback](#run-code-after-callback) |
@@ -1340,7 +1349,7 @@ Handle DPoP-specific errors gracefully with proper error detection and response |
1340 | 1349 | Implement comprehensive error handling for DPoP configuration and runtime issues: |
1341 | 1350 |
|
1342 | 1351 | ```typescript |
1343 | | -import { DPoPError, DPoPErrorCode } from "@auth0/nextjs-auth0/server"; |
| 1352 | +import { DPoPError, DPoPErrorCode } from "@auth0/nextjs-auth0/errors"; |
1344 | 1353 |
|
1345 | 1354 | import { auth0 } from "@/lib/auth0"; |
1346 | 1355 |
|
@@ -2751,6 +2760,151 @@ export async function middleware(request: NextRequest) { |
2751 | 2760 | } |
2752 | 2761 | ``` |
2753 | 2762 |
|
| 2763 | +## Custom Token Exchange |
| 2764 | +
|
| 2765 | +Custom Token Exchange (CTE) allows you to exchange external tokens (from legacy systems, third-party identity providers, or custom token services) for Auth0 access tokens. This implements [RFC 8693 (OAuth 2.0 Token Exchange)](https://datatracker.ietf.org/doc/html/rfc8693). |
| 2766 | +
|
| 2767 | +### When to Use |
| 2768 | +
|
| 2769 | +- **Legacy System Migration**: Exchange tokens from legacy auth systems for Auth0 tokens |
| 2770 | +- **Third-Party Federation**: Convert tokens from external identity providers |
| 2771 | +- **Token Mediation**: Bridge between different token ecosystems in your architecture |
| 2772 | +
|
| 2773 | +### Basic Usage |
| 2774 | +
|
| 2775 | +```ts |
| 2776 | +import { auth0 } from "@/lib/auth0"; |
| 2777 | + |
| 2778 | +export async function exchangeExternalToken(legacyToken: string) { |
| 2779 | + try { |
| 2780 | + const result = await auth0.customTokenExchange({ |
| 2781 | + subjectToken: legacyToken, |
| 2782 | + subjectTokenType: "urn:acme:legacy-token", |
| 2783 | + audience: "https://api.example.com" |
| 2784 | + }); |
| 2785 | + |
| 2786 | + return { |
| 2787 | + accessToken: result.accessToken, |
| 2788 | + expiresIn: result.expiresIn, |
| 2789 | + tokenType: result.tokenType |
| 2790 | + }; |
| 2791 | + } catch (error) { |
| 2792 | + if (error instanceof CustomTokenExchangeError) { |
| 2793 | + console.error(`Exchange failed: ${error.code}`, error.message); |
| 2794 | + } |
| 2795 | + throw error; |
| 2796 | + } |
| 2797 | +} |
| 2798 | +``` |
| 2799 | +
|
| 2800 | +### With Organization |
| 2801 | +
|
| 2802 | +When exchanging tokens for organization-scoped access: |
| 2803 | +
|
| 2804 | +```ts |
| 2805 | +const result = await auth0.customTokenExchange({ |
| 2806 | + subjectToken: externalToken, |
| 2807 | + subjectTokenType: "urn:partner:sso-token", |
| 2808 | + organization: "org_abc123", |
| 2809 | + scope: "read:data write:data" |
| 2810 | +}); |
| 2811 | +``` |
| 2812 | +
|
| 2813 | +### With Actor Token (Delegation) |
| 2814 | +
|
| 2815 | +For delegation scenarios where a service acts on behalf of a user: |
| 2816 | +
|
| 2817 | +```ts |
| 2818 | +const result = await auth0.customTokenExchange({ |
| 2819 | + subjectToken: userToken, |
| 2820 | + subjectTokenType: "urn:acme:user-token", |
| 2821 | + actorToken: serviceToken, |
| 2822 | + actorTokenType: "urn:acme:service-token", |
| 2823 | + audience: "https://downstream-api.example.com" |
| 2824 | +}); |
| 2825 | +``` |
| 2826 | +
|
| 2827 | +### Error Handling |
| 2828 | +
|
| 2829 | +```ts |
| 2830 | +import { |
| 2831 | + CustomTokenExchangeError, |
| 2832 | + CustomTokenExchangeErrorCode |
| 2833 | +} from "@auth0/nextjs-auth0/errors"; |
| 2834 | + |
| 2835 | +try { |
| 2836 | + const result = await auth0.customTokenExchange({ |
| 2837 | + subjectToken: token, |
| 2838 | + subjectTokenType: "urn:acme:token" |
| 2839 | + }); |
| 2840 | +} catch (error) { |
| 2841 | + if (error instanceof CustomTokenExchangeError) { |
| 2842 | + switch (error.code) { |
| 2843 | + case CustomTokenExchangeErrorCode.MISSING_SUBJECT_TOKEN: |
| 2844 | + // Handle missing subject token |
| 2845 | + break; |
| 2846 | + case CustomTokenExchangeErrorCode.INVALID_SUBJECT_TOKEN_TYPE: |
| 2847 | + // Handle invalid token type format |
| 2848 | + break; |
| 2849 | + case CustomTokenExchangeErrorCode.MISSING_ACTOR_TOKEN_TYPE: |
| 2850 | + // Handle missing actor token type when actor token provided |
| 2851 | + break; |
| 2852 | + case CustomTokenExchangeErrorCode.EXCHANGE_FAILED: |
| 2853 | + // Handle server-side exchange failure |
| 2854 | + console.error("Exchange failed:", error.cause); |
| 2855 | + break; |
| 2856 | + } |
| 2857 | + } |
| 2858 | +} |
| 2859 | +``` |
| 2860 | +
|
| 2861 | +### Token Type Requirements |
| 2862 | +
|
| 2863 | +The `subjectTokenType` (and `actorTokenType` if used) must: |
| 2864 | +
|
| 2865 | +- Be 10-100 characters in length (per [Auth0 CTE Profiles Management API](https://auth0.com/docs/api/management/v2#!/Token_Exchange_Profiles)) |
| 2866 | +- Be a valid URI (starting with `urn:` or `https://` or `http://`) |
| 2867 | + |
| 2868 | +Valid examples: |
| 2869 | + |
| 2870 | +- `urn:acme:legacy-token` |
| 2871 | +- `urn:partner:sso-token:v1` |
| 2872 | +- `https://example.com/token-types/external` |
| 2873 | + |
| 2874 | +> **Note**: Reserved namespaces (e.g., `urn:ietf:`, `urn:auth0:`) are validated by Auth0 when creating CTE profiles via the Management API. |
| 2875 | + |
| 2876 | +### Limitations |
| 2877 | + |
| 2878 | +> [!IMPORTANT] |
| 2879 | +> Custom Token Exchange has specific constraints you should be aware of (see [Auth0 Custom Token Exchange documentation](https://auth0.com/docs/authenticate/custom-token-exchange) for details): |
| 2880 | + |
| 2881 | +- **Server-side only**: Requires `client_secret`, cannot be used in browser |
| 2882 | +- **No Auth0 session created**: Returns tokens only, does not establish an Auth0 session |
| 2883 | +- **No token caching**: Tokens are not stored in the user's session; each call performs a new exchange |
| 2884 | +- **MFA not supported**: Exchange fails if the user's policy requires MFA |
| 2885 | +- **Rate limiting**: Subject to Auth0's token exchange rate limits |
| 2886 | + |
| 2887 | +### DPoP Support |
| 2888 | + |
| 2889 | +When DPoP is enabled in your Auth0Client configuration, custom token exchange automatically uses DPoP-bound tokens: |
| 2890 | + |
| 2891 | +```ts |
| 2892 | +const auth0 = new Auth0Client({ |
| 2893 | + // ... other config |
| 2894 | + dPoPOptions: { |
| 2895 | + enabled: true |
| 2896 | + } |
| 2897 | +}); |
| 2898 | +
|
| 2899 | +// DPoP proof will be automatically included |
| 2900 | +const result = await auth0.customTokenExchange({ |
| 2901 | + subjectToken: externalToken, |
| 2902 | + subjectTokenType: "urn:acme:external-token" |
| 2903 | +}); |
| 2904 | +
|
| 2905 | +// result.tokenType will be "DPoP" |
| 2906 | +``` |
| 2907 | + |
2754 | 2908 | ## Customizing Auth Handlers |
2755 | 2909 |
|
2756 | 2910 | Authentication routes (`/auth/login`, `/auth/logout`, `/auth/callback`) are handled automatically by the middleware. You can intercept these routes in your middleware to run custom logic before the auth handlers execute. |
|
0 commit comments