Skip to content

feat: CTE delegation and impersonation support#1608

Merged
yogeshchoudhary147 merged 6 commits into
mainfrom
feat/custom-token-exchange
May 22, 2026
Merged

feat: CTE delegation and impersonation support#1608
yogeshchoudhary147 merged 6 commits into
mainfrom
feat/custom-token-exchange

Conversation

@yogeshchoudhary147
Copy link
Copy Markdown
Contributor

@yogeshchoudhary147 yogeshchoudhary147 commented May 20, 2026

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.

  • Add customTokenExchange() for stateless token exchange (no session side effects)
  • Add actor_token and actor_token_type to CustomTokenExchangeOptions
  • Add ActClaim type and act claim to IdToken and User
  • Add skipTokenStorage flag so refresh_token is discarded inside the worker and never reaches the main thread (non-worker path returns the raw response as documented)
  • customTokenExchange() validates id_token when present, consistent with loginWithCustomTokenExchange

Test plan

  • All existing tests pass (938 passing)
  • customTokenExchange(): no session side effects, correct params, actor token, audience/org fallback
  • customTokenExchange(): id_token verified when present, skipped when absent
  • Worker skipTokenStorage: response stripping, no storage side-effect, no overwrite of existing tokens
  • loginWithCustomTokenExchange() with actor token, isAuthenticated() returns true
  • getIdTokenClaims().act and getUser().act populated correctly
  • No error when refresh_token is absent (suppressed by Auth0 when actor_token is present)
  • Auth0 error codes surfaced via standard SDK error types

Intentional omissions

  • Paired parameter validation: Auth0 returns a clear error (invalid_request). No benefit duplicating this client-side.
  • URI validation: Auth0 validates server-side and returns a clear error. Duplicating adds complexity with no real benefit.

@yogeshchoudhary147 yogeshchoudhary147 requested a review from a team as a code owner May 20, 2026 14:11
- 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
Comment thread src/Auth0Client.ts Fixed
@yogeshchoudhary147 yogeshchoudhary147 force-pushed the feat/custom-token-exchange branch from 3961b5e to 5abef7d Compare May 20, 2026 14:13
- 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
@yogeshchoudhary147 yogeshchoudhary147 changed the title feat: add customTokenExchange and actor token support feat: CTE delegation and impersonation support May 21, 2026
Comment thread src/Auth0Client.ts
{
baseUrl: this.domainUrl,
client_id: this.options.clientId,
auth0Client: this.options.auth0Client,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/Auth0Client.ts
Comment thread src/Auth0Client.ts
Comment thread src/worker/token.worker.ts Outdated
Comment thread src/Auth0Client.ts Dismissed
Copy link
Copy Markdown

@kishore7snehil kishore7snehil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@yogeshchoudhary147 yogeshchoudhary147 merged commit ec960f1 into main May 22, 2026
18 checks passed
@yogeshchoudhary147 yogeshchoudhary147 deleted the feat/custom-token-exchange branch May 22, 2026 06:11
yogeshchoudhary147 added a commit that referenced this pull request May 22, 2026
**Added**
- feat: CTE delegation and impersonation support
[\#1608](#1608)
([yogeshchoudhary147](https://github.com/yogeshchoudhary147))
yogeshchoudhary147 added a commit to auth0/auth0-react that referenced this pull request May 22, 2026
## 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants