feat: CTE delegation and impersonation support#1608
Conversation
- Add customTokenExchange() method — performs token exchange without session side effects - Add actor_token and actor_token_type to CustomTokenExchangeOptions - Extract _buildTokenExchangeParams() to avoid duplication between methods - Add skipTokenStorage flag to worker path so refresh_token is discarded inside the worker and never reaches the main thread - Add ActClaim type and act claim to IdToken
3961b5e to
5abef7d
Compare
- Add 3 worker-level tests for skipTokenStorage: response stripping, no storage side-effect, and no overwrite of existing tokens - Clarify customTokenExchange JSDoc: when no Web Worker is configured the caller is responsible for discarding any refresh_token returned
| { | ||
| baseUrl: this.domainUrl, | ||
| client_id: this.options.clientId, | ||
| auth0Client: this.options.auth0Client, |
There was a problem hiding this comment.
When no Web Worker is configured, customTokenExchange() calls oauthToken() with skipTokenStorage: true, but the non-worker path (fetchWithoutWorker) does not use skipTokenStorage - it returns the raw JSON response including any refresh_token. The worker path always strips it, so this is inconsistent. The caller receives the refresh_token in the non-worker path which could lead to accidental token leakage.
Can we strip refresh_token from the response in customTokenExchange() before returning, regardless of whether a worker is used. Something like this :
const { refresh_token, ...safeResult } = await oauthToken(...);
return safeResult;There was a problem hiding this comment.
The worker strips refresh_token because of its own internal security model (unconditional for all calls), not as a contract of customTokenExchange. Stripping on the non-worker path would be silent data loss against an explicit TokenEndpointResponse return type; behavior is intentional and documented.
**Added** - feat: CTE delegation and impersonation support [\#1608](#1608) ([yogeshchoudhary147](https://github.com/yogeshchoudhary147))
## Summary Adds delegation and impersonation support for Custom Token Exchange per RFC 8693, building on [auth0-spa-js#1608](auth0/auth0-spa-js#1608). - Add `customTokenExchange()` to `Auth0ContextInterface` — stateless token exchange with no session side effects - Bump `@auth0/auth0-spa-js` to `^2.20.0` - Re-export `ActClaim` type ## Test plan - [x] All existing tests pass - [x] `customTokenExchange()`: returns raw `TokenEndpointResponse`, correct params forwarded including `actor_token`/`actor_token_type` - [x] `customTokenExchange()`: no auth state update, `isAuthenticated` unchanged - [x] `customTokenExchange()`: errors propagate raw (no try/catch wrapping) - [x] `customTokenExchange()`: method is memoized - [x] 100% coverage maintained
Summary
Adds delegation and impersonation support for Custom Token Exchange (CTE) per RFC 8693, allowing an actor (e.g. an AI agent or support rep) to act on behalf of a user.
customTokenExchange()for stateless token exchange (no session side effects)actor_tokenandactor_token_typetoCustomTokenExchangeOptionsActClaimtype andactclaim toIdTokenandUserskipTokenStorageflag sorefresh_tokenis discarded inside the worker and never reaches the main thread (non-worker path returns the raw response as documented)customTokenExchange()validatesid_tokenwhen present, consistent withloginWithCustomTokenExchangeTest plan
customTokenExchange(): no session side effects, correct params, actor token, audience/org fallbackcustomTokenExchange():id_tokenverified when present, skipped when absentskipTokenStorage: response stripping, no storage side-effect, no overwrite of existing tokensloginWithCustomTokenExchange()with actor token,isAuthenticated()returns truegetIdTokenClaims().actandgetUser().actpopulated correctlyrefresh_tokenis absent (suppressed by Auth0 whenactor_tokenis present)Intentional omissions
invalid_request). No benefit duplicating this client-side.