feat: CTE delegation and impersonation support#1190
Conversation
| /// | ||
| /// - [RFC 8693: OAuth 2.0 Token Exchange - act Claim](https://tools.ietf.org/html/rfc8693#section-4.1) | ||
| /// - [Custom Token Exchange Documentation](https://auth0.com/docs/authenticate/custom-token-exchange) | ||
| public final class ActClaim: Sendable { |
There was a problem hiding this comment.
could this be struct instead
| /// ## See Also | ||
| /// | ||
| /// - [RFC 8693: OAuth 2.0 Token Exchange - act Claim](https://tools.ietf.org/html/rfc8693#section-4.1) | ||
| /// - [Custom Token Exchange Documentation](https://auth0.com/docs/authenticate/custom-token-exchange) |
There was a problem hiding this comment.
This references RFC 8693 Section 4.1, but the act claim is defined in Section 4.4 ("Delegation Semantics"). Section 4.1 covers the token exchange response structure.
Suggestion: https://tools.ietf.org/html/rfc8693#section-4.4
|
|
||
| /// The subject identifier of the acting party. | ||
| public let sub: String? | ||
|
|
There was a problem hiding this comment.
Per RFC 8693 Section 4.4: "The sub claim is required" within an act claim. The Android SDK (ActorClaim) makes sub non-optional and returns null for the entire object if sub is missing from the JSON.
Here, sub is String? which means callers always need to nil-check even though a valid act claim per the RFC will always have it. This creates inconsistent behavior across platforms.
Consider either:
- Making
subnon-optional (String) and returningnilfrominit?ifsubis absent, or - Documenting why the lenient approach was chosen (e.g., forward-compatibility with potential spec changes)
| var additional: [String: String] = [:] | ||
| for (key, value) in json where key != "sub" && key != "act" { | ||
| if let stringValue = value as? String { | ||
| additional[key] = stringValue |
There was a problem hiding this comment.
additionalClaims is [String: String] and the initializer only keeps values that cast to String, silently dropping non-string JSON values (booleans, numbers, objects, arrays). While RFC 8693 act claims primarily use string values like sub, custom Auth0 Actions could set non-string values via api.authentication.setActor() that would be lost here.
| scope: String, | ||
| organization: String?, | ||
| actorToken: ActorToken?, | ||
| parameters: [String: Any]) -> Request<Credentials, AuthenticationError> |
There was a problem hiding this comment.
Adding actorToken: ActorToken? as a required parameter to the public Authentication protocol is a source-breaking change for anyone who has created a custom conformance . Their implementation will fail to compile until they add the new parameter.
Consider noting it in the changelog, or providing a default implementation in a protocol extension to avoid breaking custom conformers.
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
This test verifies actor_token/actor_token_type ARE sent when provided, but there's no corresponding test verifying they are NOT present in the request body when actorToken is nil (the default).
The hasAllOf matcher only checks for presence of specified keys, not absence of others. Consider adding a test like:
it("should not include actor token params when actorToken is nil") {
// ... call customTokenExchange without actorToken
// assert body does NOT contain "actor_token" or "actor_token_type"
}The Android PR includes this test (shouldSendCTERequestWithoutActorToken).
| /// | ||
| /// - ``ActClaim`` | ||
| /// - [RFC 8693: OAuth 2.0 Token Exchange](https://tools.ietf.org/html/rfc8693#section-4.1) | ||
| public let act: ActClaim? |
There was a problem hiding this comment.
Same issue as in ActClaim.swift: this links to Section 4.1 but the act claim is defined in Section 4.4 ("Delegation Semantics"). Section 4.1 covers the token exchange response structure.
Should be: https://tools.ietf.org/html/rfc8693#section-4.4
| #### Reading the `act` claim from the ID token | ||
|
|
||
| When a token exchange involves an actor token, Auth0 may include an `act` (actor) claim in the resulting ID token. This claim identifies who is acting on behalf of the subject and may be nested to represent delegation chains. | ||
|
|
There was a problem hiding this comment.
This says "Auth0 may include an act claim" but doesn't explain when it will. The act claim only appears when an Action is configured server-side that calls api.authentication.setActor(). Without that, the response will never have an act claim regardless of whether actor_token is sent.
Consider adding a note like:
The
actclaim is set server-side via an Auth0 Action that callsapi.authentication.setActor(). Without this Action configured, the ID token will not contain anactclaim even when an actor token is provided in the request.
|
|
||
| ```swift | ||
| import JWTDecode | ||
|
|
There was a problem hiding this comment.
JWTDecode is a separate package (JWTDecode.swift) that needs to be added as a dependency. Developers copying this example may not know they need to add it to their project.
Consider adding a brief note: "This example uses JWTDecode.swift — add it to your project if you haven't already."
📋 Changes
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.
Types added:
ActorToken— A struct that bundlestokenandtokenTypetogether, ensuring both are always provided when performing delegation/impersonation token exchanges (per RFC 8693).ActClaim— Afinal classrepresenting theact(actor) claim from an ID token. Supports recursive nesting for multi-hop delegation chains. Exposessub, nestedact, andadditionalClaims.Methods changed:
Authentication.customTokenExchange(...)— Added optionalactorToken: ActorToken?parameter. When provided,actor_tokenandactor_token_typeare sent in the token exchange request.Properties added:
UserInfo.act: ActClaim?— Parses theactclaim from the ID token JSON body. Also added"act"toUserInfo.publicClaimsso it is excluded fromcustomClaims.Documentation:
EXAMPLES.mdwith sections on actor token delegation and reading theactclaim.README.mdto mention custom token exchange in the comprehensive guides note.📎 References
actClaim🎯 Testing
AuthenticationSpecfor actor token parameter in CTE requests.UserInfoSpecforactclaim parsing: nil when absent, basic parsing, nested delegation chains, and exclusion from custom claims.swift testto verify all tests pass.