feat: add session transfer token support to CTE#2734
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds Session Transfer Token support for RFC 8693 Custom Token Exchange Phase 2, including public types, typed errors, helper utilities, server client methods, redirects, tests, exports, and documentation. ChangesSession Transfer Token feature
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant Auth0Client
participant AuthClient
participant SessionHelpers
participant AuthorizationServer
Caller->>Auth0Client: requestSessionTransferToken(options)
Auth0Client->>Auth0Client: getSession()
Auth0Client->>AuthClient: requestSessionTransferToken(options, session)
AuthClient->>SessionHelpers: resolveActorFromSession(session, actor)
SessionHelpers-->>AuthClient: actor token or error
AuthClient->>AuthorizationServer: STT token exchange
AuthorizationServer-->>AuthClient: raw response or DPoP nonce error
AuthClient->>AuthorizationServer: retry with DPoP nonce
AuthorizationServer-->>AuthClient: raw STT response
AuthClient->>SessionHelpers: parseSessionTransferTokenResponse(raw)
SessionHelpers-->>AuthClient: SessionTransferTokenResult
AuthClient-->>Auth0Client: result or typed error
Auth0Client-->>Caller: SessionTransferTokenResult
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2734 +/- ##
==========================================
- Coverage 88.11% 87.73% -0.38%
==========================================
Files 79 80 +1
Lines 11105 11454 +349
Branches 2296 2366 +70
==========================================
+ Hits 9785 10049 +264
- Misses 1276 1359 +83
- Partials 44 46 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/types/token-vault.ts (1)
235-244: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider narrowing
issuedTokenType's type.The doc comment instructs callers to "Branch on
issuedTokenType", but it's typed as plainstring, losing type-narrowing benefits for the primary documented usage pattern.♻️ Proposed refactor
- issuedTokenType: string; + issuedTokenType: TOKEN_TYPES.SESSION_TRANSFER_TOKEN | string;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/types/token-vault.ts` around lines 235 - 244, The SessionTransferTokenResult interface documents issuedTokenType as the field callers should branch on, but it is currently typed too broadly as string. Update the SessionTransferTokenResult type to narrow issuedTokenType to the specific session transfer token discriminator used in this module (for example, the corresponding TOKEN_TYPES.SESSION_TRANSFER_TOKEN literal type), so consumers of buildSessionTransferRedirect and related token-vault helpers get proper type narrowing when checking it.src/utils/session-transfer-helpers.ts (1)
99-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider naming the default
expiresInfallback.The literal
60on Line 108 is a magic number with no inline explanation of why 60 seconds is the fallback. A named constant (e.g.,DEFAULT_STT_EXPIRES_IN_SECONDS) would make the intent explicit at the call site.♻️ Proposed refactor
+const DEFAULT_STT_EXPIRES_IN_SECONDS = 60; + export function parseSessionTransferTokenResponse(raw: { access_token: string; issued_token_type: string; expires_in?: number; token_type?: string; }): SessionTransferTokenResult { return { sessionTransferToken: raw.access_token, issuedTokenType: raw.issued_token_type, - expiresIn: Number(raw.expires_in ?? 60), + expiresIn: Number(raw.expires_in ?? DEFAULT_STT_EXPIRES_IN_SECONDS), tokenType: raw.token_type }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/session-transfer-helpers.ts` around lines 99 - 111, The `parseSessionTransferTokenResponse` helper currently uses a magic number for the `expiresIn` fallback, so replace the inline `60` with a clearly named constant near the function (for example a default session transfer token expiry value) and use that constant in the returned `SessionTransferTokenResult` to make the intent explicit.src/utils/session-transfer-helpers.test.ts (1)
8-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueImport
TOKEN_TYPESfrom the same barrel asSessionData.
SessionDatais imported from../types/index.jswhileTOKEN_TYPESis imported directly from../types/token-vault.js, even thoughTOKEN_TYPESis now re-exported from../types/index.jstoo. Consolidating to one import source avoids coupling the test to the internal file layout.♻️ Proposed refactor
-import { SessionData } from "../types/index.js"; -import { TOKEN_TYPES } from "../types/token-vault.js"; +import { SessionData, TOKEN_TYPES } from "../types/index.js";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/session-transfer-helpers.test.ts` around lines 8 - 9, Update the imports in the session transfer helper test so both SessionData and TOKEN_TYPES come from the same barrel export in ../types/index.js. The issue is that TOKEN_TYPES is still imported directly from ../types/token-vault.js even though it is re-exported, so adjust the test’s import statements to use the shared index barrel and remove the direct file import to keep the test decoupled from internal file layout.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@EXAMPLES.md`:
- Around line 177-182: The table of contents entry for the repeated “Error
Handling” heading is using the wrong anchor, so update the TOC item in
EXAMPLES.md to match the sixth occurrence count used by the markdown renderer.
Keep the surrounding TOC entries aligned with the duplicate-heading pattern
already used for “Basic Usage”, “With Organization”, and “Limitations”, and
change the `Error Handling` anchor from the current value to the correct one for
the section.
---
Nitpick comments:
In `@src/types/token-vault.ts`:
- Around line 235-244: The SessionTransferTokenResult interface documents
issuedTokenType as the field callers should branch on, but it is currently typed
too broadly as string. Update the SessionTransferTokenResult type to narrow
issuedTokenType to the specific session transfer token discriminator used in
this module (for example, the corresponding TOKEN_TYPES.SESSION_TRANSFER_TOKEN
literal type), so consumers of buildSessionTransferRedirect and related
token-vault helpers get proper type narrowing when checking it.
In `@src/utils/session-transfer-helpers.test.ts`:
- Around line 8-9: Update the imports in the session transfer helper test so
both SessionData and TOKEN_TYPES come from the same barrel export in
../types/index.js. The issue is that TOKEN_TYPES is still imported directly from
../types/token-vault.js even though it is re-exported, so adjust the test’s
import statements to use the shared index barrel and remove the direct file
import to keep the test decoupled from internal file layout.
In `@src/utils/session-transfer-helpers.ts`:
- Around line 99-111: The `parseSessionTransferTokenResponse` helper currently
uses a magic number for the `expiresIn` fallback, so replace the inline `60`
with a clearly named constant near the function (for example a default session
transfer token expiry value) and use that constant in the returned
`SessionTransferTokenResult` to make the intent explicit.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 725bbe96-437e-4fbe-bd79-9777bb03da07
📒 Files selected for processing (10)
EXAMPLES.mdsrc/errors/oauth-errors.tssrc/server/auth-client.tssrc/server/client.tssrc/server/custom-token-exchange.test.tssrc/server/index.tssrc/types/index.tssrc/types/token-vault.tssrc/utils/session-transfer-helpers.test.tssrc/utils/session-transfer-helpers.ts
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/utils/session-transfer-helpers.ts (2)
162-170: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExact-match checks are redundant given the substring
includescalls.
code === "setactor_required"is fully covered bycode.includes("setactor"), andcode === "session_transfer_disabled"is fully covered bycode.includes("session_transfer"). The exact matches appear to serve as inline documentation of the expected platform codes, which is reasonable for readability — but if the intent is purely matching, the exact checks can be removed to reduce cognitive noise.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/session-transfer-helpers.ts` around lines 162 - 170, Remove the redundant exact equality checks from the conditionals mapping codes to SETACTOR_REQUIRED and SESSION_TRANSFER_DISABLED, leaving the corresponding includes checks and return behavior unchanged.
104-118: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAdd HTTP/HTTPS protocol validation to
buildSessionTransferRedirectUrl.
new URL(targetLoginUrl)accepts any absolute URL includingjavascript:,data:, andfile:schemes. While the doc comment states the URL must be "trusted, app-controlled," adding a protocol allowlist (http:/https:) is cheap defense-in-depth against open-redirect and XSS if the URL ever flows from partially untrusted input. The downstream caller inauth-client.tsuses the return value directly as a redirect target, so a non-HTTP scheme would propagate to the browser.🛡️ Proposed defense-in-depth protocol check
try { url = new URL(targetLoginUrl); } catch { throw new CustomTokenExchangeError( CustomTokenExchangeErrorCode.EXCHANGE_FAILED, `Invalid targetLoginUrl: "${targetLoginUrl}" is not an absolute URL. ` + "Pass a trusted, app-controlled absolute login URL (e.g. https://app.example.com/auth/login)." ); } + if (url.protocol !== "http:" && url.protocol !== "https:") { + throw new CustomTokenExchangeError( + CustomTokenExchangeErrorCode.EXCHANGE_FAILED, + `Invalid targetLoginUrl: "${targetLoginUrl}" must use http or https.` + ); + } url.searchParams.set("session_transfer_token", sessionTransferToken);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/session-transfer-helpers.ts` around lines 104 - 118, Update buildSessionTransferRedirectUrl to validate the parsed URL’s protocol after new URL(targetLoginUrl) succeeds, allowing only http: and https:. For any other scheme, throw the same CustomTokenExchangeError used for invalid targetLoginUrl input and include the URL value and requirement for an absolute HTTP/HTTPS login URL in the message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/utils/session-transfer-helpers.ts`:
- Around line 162-170: Remove the redundant exact equality checks from the
conditionals mapping codes to SETACTOR_REQUIRED and SESSION_TRANSFER_DISABLED,
leaving the corresponding includes checks and return behavior unchanged.
- Around line 104-118: Update buildSessionTransferRedirectUrl to validate the
parsed URL’s protocol after new URL(targetLoginUrl) succeeds, allowing only
http: and https:. For any other scheme, throw the same CustomTokenExchangeError
used for invalid targetLoginUrl input and include the URL value and requirement
for an absolute HTTP/HTTPS login URL in the message.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 3bd8445c-be1b-43e1-9f7a-d7db43a40468
📒 Files selected for processing (6)
EXAMPLES.mdsrc/server/auth-client.tssrc/server/client.tssrc/server/custom-token-exchange.test.tssrc/utils/session-transfer-helpers.test.tssrc/utils/session-transfer-helpers.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- src/server/client.ts
- src/utils/session-transfer-helpers.test.ts
- EXAMPLES.md
- src/server/custom-token-exchange.test.ts
- src/server/auth-client.ts
65af9ff to
4e50dcd
Compare
… error code matching, optional expiresIn
|
@kishore7snehil @nandan-bhat Did another pass over the STT surface myself and found and fixed a few more things:
18 new tests added, full suite passing, TypeScript clean. |
…m dedup, scheme-guard tests, Pages Router support, MCD guard
All new/changed/fixed functionality is covered by tests (or N/A)
I have added documentation for all new/changed functionality (or N/A)
📋 Changes
Implements CTE Phase 2 — Session Transfer Token (STT). An authenticated agent can now request a short-lived, one-shot token that lets them establish a web session as a customer in a target app, without the customer's password.
New methods on
Auth0Client:requestSessionTransferToken(options)— exchanges a subject token for an STT viaPOST /oauth/tokenwithaudience=urn:{domain}:session_transfer. The actor defaults to the agent session's ID token (checked for expiry); anexplicit
actoroverride is also supported.buildSessionTransferRedirect(targetLoginUrl, result, opts?)— pure URL builder returning aNextResponse.redirectwith?session_transfer_token=<stt>appended.New types exported from
@auth0/nextjs-auth0/serverand@auth0/nextjs-auth0/types:SessionTransferTokenOptions/SessionTransferTokenResultTOKEN_TYPESenum (ID_TOKEN,ACCESS_TOKEN,SESSION_TRANSFER_TOKEN)New error codes on
CustomTokenExchangeErrorCode:ACTOR_UNAVAILABLE— no usable actor token (client-side, before network call)SETACTOR_REQUIRED— server requires an actor to be set viaapi.authentication.setActor()SESSION_TRANSFER_DISABLED— tenant feature flag not enabledImplementation notes:
oauth4webapi'sprocessGenericTokenEndpointResponserejectstoken_type: "N_A"(returned by the STT endpoint). The implementation bypasses it by callinggenericTokenEndpointRequestdirectly and parsing the raw JSON body.src/utils/session-transfer-helpers.tsto keepauth-client.tsmanageable.handleLogin()already forwards all query params to/authorize, sosession_transfer_tokenpasses through transparently on the target app side.📎 References
🎯 Testing
src/server/custom-token-exchange.test.ts(section 5) covering result shape, actor resolution, wireparameters, STT not persisted to session, server error mapping, input validation,
buildSessionTransferRedirect, andbackward compatibility.
src/utils/session-transfer-helpers.test.tscovering all 4 helper functions.Summary by CodeRabbit