Skip to content

feat(auth0-auth-js): add actor token support and act claim to Custom Token Exchange#175

Open
cschetan77 wants to merge 5 commits into
mainfrom
feature/support-act-claim
Open

feat(auth0-auth-js): add actor token support and act claim to Custom Token Exchange#175
cschetan77 wants to merge 5 commits into
mainfrom
feature/support-act-claim

Conversation

@cschetan77
Copy link
Copy Markdown

@cschetan77 cschetan77 commented May 25, 2026

Description

Extends the Custom Token Exchange (CTE) implementation in auth0-auth-js to support RFC 8693 delegation flows via actor tokens.

  • Adds actorToken and actorTokenType optional fields to ExchangeProfileOptions, which are forwarded as actor_token and actor_token_type in the token request.
  • Adds the ActClaim interface ({ sub: string; [key: string]: unknown }) per RFC 8693 Section 4.1, the index signature allows for additional server-provided fields (e.g. chained delegation, iss).
  • Adds act?: ActClaim to TokenResponse, populated from the parsed ID token claims in fromTokenEndpointResponse() with a fallback to the access token payload, covering M2M delegation flows where no ID token is issued.
  • Adds client-side syntactic URI validation for both subjectTokenType and actorTokenType via new URL(), throws TokenExchangeError before the network request if invalid, semantic namespace validation remains on the Auth0 server
  • Removes actor_token and actor_token_type from PARAM_DENYLIST since they are now first-class typed parameters

Testing

Unit Tests

  • should send actor_token and actor_token_type when provided: verifies request params and result.act are correctly wired
  • should expose act claim from id_token on TokenResponse: verifies extra fields (e.g. iss) on act are accessible
  • should not send actor_token or actor_token_type when omitted: verifies no regression on existing exchanges
  • should throw TokenExchangeError for invalid token type URIs: covers both subjectTokenType and actorTokenType invalid URI cases
  • should accept valid URI formats for subjectTokenType and actorTokenType: covers http:// URI forms
  • should expose act claim from access token when no id_token is returned (M2M delegation): verifies that a token response with no id_token but an access token carrying act correctly populates TokenResponse.act.
  • should throw TokenExchangeError when actorToken is provided without actorTokenType

Checklist

  • I have added documentation for new/changed functionality in this PR or in auth0.com/docs
  • All active GitHub checks for tests, formatting, and security are passing
  • The correct base branch is being used, if not the default branch

  - Added ActClaim interface (sub: string + open index signature for extra claims per RFC 8693)
  - Added actorToken? and actorTokenType? to ExchangeProfileOptions
  - Added act?: ActClaim property to TokenResponse
  - Populated act from parsed ID token claims in fromTokenEndpointResponse()

  auth-client.ts
  - Removed actor_token and actor_token_type from PARAM_DENYLIST (they now have explicit typed params)
  - Updated the denylist comment block to remove the stale bullet
  - Added validateTokenTypeUri() — syntactic-only new URL() check, throws TokenExchangeError with a clear message
  - In #exchangeProfileToken(): calls validateTokenTypeUri for both subjectTokenType and actorTokenType, then appends actor_token/actor_token_type to the request when present

  auth-client.spec.ts
  - New describe('exchangeToken — actor token support') block with 5 tests covering: params wired + act claim populated, act claim with extra fields, params absent when not provided, invalid URI validation (both params in one test), valid URI acceptance
@cschetan77 cschetan77 force-pushed the feature/support-act-claim branch from 77b32f4 to 1ef94d5 Compare June 3, 2026 11:45

tokenResponse.tokenType = response.token_type;
tokenResponse.issuedTokenType = (response as typeof response & { issued_token_type?: string }).issued_token_type;
const atClaims = decodeJwt(response.access_token);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What will happen if Auth0 issues an opaque token.

tokenResponse.tokenType = response.token_type;
tokenResponse.issuedTokenType = (response as typeof response & { issued_token_type?: string }).issued_token_type;
const atClaims = decodeJwt(response.access_token);
tokenResponse.act = (claims?.act ?? atClaims.act) as ActClaim | undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does that mean, tokenResponse would always have act ?
Because fromTokenEndpointResponse is called from multiple places.

@nandan-bhat
Copy link
Copy Markdown
Contributor

  • Please add tests for opaque token scenarios.
  • Please update documentation (EXAMPLES.md) elaborating this feature.

* behalf of the subject.
*
* @see {@link https://www.rfc-editor.org/rfc/rfc8693#section-4.1 RFC 8693 Section 4.1}
*/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The act claim is defined in Section 4.4 ("Delegation Semantics"), not 4.1. Section 4.1 is the token exchange response structure.

Should be #section-4.4. (Same fix landed on the Android and Swift CTE PRs.)

* the acting party on whose behalf the subject token was exchanged.
*
* @see {@link https://www.rfc-editor.org/rfc/rfc8693#section-4.1 RFC 8693 Section 4.1}
*/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as the ActClaim interface above: act is defined in Section 4.4, not 4.1. Should be #section-4.4.


validateSubjectToken(options.subjectToken);
validateTokenTypeUri(options.subjectTokenType, 'subjectTokenType');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Two concerns here:

  • The team aligned that token-type URI validation stays server-side, no other SDK (Swift, Android, spa-js) does client-side URI validation. Adding it here for subjectTokenType (which previously had none) breaks that alignment and is a behavior change for existing callers.

  • new URL() is the wrong validator and risks false rejections. It can reject values the Auth0 server would actually accept, turning a server-side concern into a client-side breaking change on upgrade. (new URL('urn:acme:custom-token') happens to pass, but the general class of valid token-type identifiers isn't guaranteed to parse as a WHATWG URL.)

'subject_token_type',
'requested_token_type',
'actor_token',
'actor_token_type',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A caller can again pass actor_token through extra, which is no longer guarded. The typed param is the documented path.

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