Skip to content

Commit 4b3319f

Browse files
committed
docs(sessionTransferToken): add Impersonation via Session Transfer Token section to EXAMPLES.md
1 parent 4583411 commit 4b3319f

1 file changed

Lines changed: 115 additions & 4 deletions

File tree

EXAMPLES.md

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
10. [Use a custom session store](#10-use-a-custom-session-store)
1313
11. [Back-Channel Logout](#11-back-channel-logout)
1414
12. [Custom Token Exchange](#12-custom-token-exchange)
15-
13. [Use a proxy for OIDC requests](#13-use-a-proxy-for-oidc-requests)
16-
14. [Session expiry from upstream IdP (IPSIE `session_expiry`)](#14-session-expiry-from-upstream-idp-ipsie-session_expiry)
15+
13. [Impersonation via Session Transfer Token](#13-impersonation-via-session-transfer-token)
16+
14. [Use a proxy for OIDC requests](#14-use-a-proxy-for-oidc-requests)
17+
15. [Session expiry from upstream IdP (IPSIE `session_expiry`)](#15-session-expiry-from-upstream-idp-ipsie-session_expiry)
1718

1819
## 1. Basic setup
1920

@@ -450,7 +451,117 @@ const tokenSet = await req.oidc.customTokenExchange({
450451
});
451452
```
452453

453-
## 13. Use a proxy for OIDC requests
454+
## 13. Impersonation via Session Transfer Token
455+
456+
Custom Token Exchange Impersonation via Session Transfer lets a support or admin application log a user into a target web application as a customer — so a support engineer can reproduce the customer's exact experience without knowing their password. The agent is recorded in the `act` claim on the impersonated session, making every impersonation auditable.
457+
458+
The flow involves two roles:
459+
460+
- **Initiator** — your support/admin app. It requests a short-lived, single-use Session Transfer Token (STT) and redirects the user's browser to the target app carrying the STT.
461+
- **Target** — the customer's web app. It forwards the STT to `/authorize`, where Auth0 redeems it and establishes a session as the customer.
462+
463+
> **Prerequisites (configured via Management API / Dashboard):**
464+
>
465+
> - Feature flag `cte_session_transfer_token` must be enabled on the tenant.
466+
> - The issuing (initiator) client needs `can_create_session_transfer_token: true` and a Custom Token Exchange Action that calls `setActor`.
467+
> - The redeeming (target) client needs `allow_delegated_access: true` and `"query"` in its allowed authentication methods.
468+
469+
### Initiator: requesting an STT and redirecting
470+
471+
The user must be logged in to the initiator app — the SDK sources the actor from the session's ID token by default (refreshing it automatically if expired). Run your own authorization check before calling the SDK.
472+
473+
```js
474+
const { auth, requiresAuth } = require('express-openid-connect');
475+
476+
app.post('/impersonate', requiresAuth(), async (req, res, next) => {
477+
try {
478+
// Your own proof of which customer to impersonate — validated by your Action.
479+
const { customerToken, customerTokenType } = req.body;
480+
481+
const result = await req.oidc.requestSessionTransferToken({
482+
subject_token: customerToken,
483+
subject_token_type: customerTokenType,
484+
// Optional: pass custom context to your Action via event.request.body
485+
extra: { reason: 'Investigating ticket TCK-1234' },
486+
});
487+
488+
// targetLoginUrl must be a trusted, app-controlled value — never derived from
489+
// untrusted input such as a returnTo param, or the STT could leak to an attacker host.
490+
const redirectUrl = req.oidc.buildSessionTransferRedirect(
491+
'https://app.example.com/auth/login',
492+
result,
493+
);
494+
495+
res.redirect(redirectUrl);
496+
} catch (err) {
497+
// err.error === 'actor_unavailable' — user not logged in or session expired
498+
// err.error === 'setactor_required' — Action did not call setActor
499+
// err.error === 'session_transfer_disabled' — tenant feature flag is off
500+
next(err);
501+
}
502+
});
503+
```
504+
505+
The STT is opaque, single-use, and lives ~60 seconds. The SDK never stores it — hand it straight to `buildSessionTransferRedirect` and discard it.
506+
507+
If the customer belongs to an organization, forward it on the redirect:
508+
509+
```js
510+
const redirectUrl = req.oidc.buildSessionTransferRedirect(
511+
'https://app.example.com/auth/login',
512+
result,
513+
{ organization: 'org_globex' },
514+
);
515+
```
516+
517+
To supply the acting party explicitly instead of using the session ID token:
518+
519+
```js
520+
const result = await req.oidc.requestSessionTransferToken({
521+
subject_token: customerToken,
522+
subject_token_type: customerTokenType,
523+
actor_token: agentIdToken,
524+
actor_token_type: 'urn:ietf:params:oauth:token-type:id_token',
525+
});
526+
```
527+
528+
### Target: redeeming the STT
529+
530+
On the target app, forward the `session_transfer_token` query parameter (and `organization` when present) to `/authorize` through the existing `res.oidc.login()` call — no new SDK methods required:
531+
532+
```js
533+
app.get('/auth/login', async (req, res) => {
534+
const authorizationParams = {};
535+
536+
if (req.query.session_transfer_token) {
537+
authorizationParams.session_transfer_token =
538+
req.query.session_transfer_token;
539+
}
540+
if (req.query.organization) {
541+
authorizationParams.organization = req.query.organization;
542+
}
543+
544+
await res.oidc.login({ authorizationParams });
545+
});
546+
```
547+
548+
The established session is short-lived (hard-capped at 2 hours) and cannot mint a refresh token.
549+
550+
### Reading the `act` claim
551+
552+
Once the impersonation session is established, the acting party is available as `req.oidc.user.act`:
553+
554+
```js
555+
app.get('/dashboard', requiresAuth(), (req, res) => {
556+
const actor = req.oidc.user.act; // { sub: 'support-agent-007' } when impersonated
557+
if (actor) {
558+
// Render an impersonation banner so the agent knows they are acting as the customer.
559+
}
560+
res.render('dashboard');
561+
});
562+
```
563+
564+
## 14. Use a proxy for OIDC requests
454565

455566
If you need to route all OIDC HTTP requests (discovery, token, userinfo, etc.) through a proxy, use the `customFetch` option with `undici`'s `ProxyAgent`:
456567

@@ -473,7 +584,7 @@ app.use(
473584

474585
The SDK wraps your `customFetch` function to add required headers (User-Agent, Auth0-Client telemetry) before making requests.
475586

476-
## 14. Session expiry from upstream IdP (IPSIE `session_expiry`)
587+
## 15. Session expiry from upstream IdP (IPSIE `session_expiry`)
477588

478589
When an upstream IdP supports the IPSIE SL1 spec, it can include a `session_expiry` claim in the ID token — an absolute Unix timestamp (seconds) marking the latest moment the IdP considers the session valid.
479590

0 commit comments

Comments
 (0)