Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,13 @@ For detailed behavior and how to read the value, see [examples/Sessions.md](./ex

### Custom Token Exchange

Exchange a token from an external identity provider or legacy system for Auth0 tokens using [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) Token Exchange, without a browser redirect β€” with or without establishing a session. See [examples/CustomTokenExchange.md](./examples/CustomTokenExchange.md) for setup and code samples.
Exchange a token from an external identity provider or legacy system for Auth0 tokens using [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693) Token Exchange, without a browser redirect, with or without establishing a session.

Building on token exchange, the SDK also supports:

- **[Impersonation via Session Transfer (STT)](./examples/CustomTokenExchange.md#3-impersonation-via-session-transfer-stt)** - mint a Session Transfer Token to log an agent into a target app as a customer, via `request_session_transfer_token()` and `build_session_transfer_redirect()`.

See [examples/CustomTokenExchange.md](./examples/CustomTokenExchange.md) for setup and code samples.

## Feedback

Expand Down
38 changes: 36 additions & 2 deletions examples/CustomTokenExchange.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,41 @@ async def get_profile(session: dict = Depends(auth_client.require_session)):

> **TIP**: Use `login_with_custom_token_exchange()` for user-migration or external-IdP login flows. Use `custom_token_exchange()` for pure service-to-service or downstream-API scenarios where the caller's own session should not change.

## 3. Error Handling
## 3. Impersonation via Session Transfer (STT)

Custom Token Exchange can also mint a **Session Transfer Token (STT)** to log an agent into a target app **as** a customer (impersonation via session transfer). For how STTs work, the actor requirement, and Auth0-side configuration, see the [auth0-server-python Custom Token Exchange doc](https://github.com/auth0/auth0-server-python/blob/main/examples/CustomTokenExchange.md) and its Impersonation via Session Transfer section. This section covers the FastAPI wrappers and how the flow maps onto the mounted routes.

`AuthClient` exposes `request_session_transfer_token()` (async, mints the STT) and `build_session_transfer_redirect()` (**sync**, builds the redirect URL) on the initiator side:

```python
@router.post("/impersonate")
async def impersonate(request: Request, response: Response):
auth_client = request.app.state.auth_client

result = await auth_client.request_session_transfer_token(
subject_token="customer-123@example.com", # who to impersonate; validated by your Action
subject_token_type="urn:mycompany:impersonation-token",
# Pass both request and response: sourcing the actor from the agent session can
# refresh an expired ID token, which writes the session cookie back on response.
store_options={"request": request, "response": response},
)
redirect_url = auth_client.build_session_transfer_redirect(
"https://customer-app.example.com/auth/login", result
)
return RedirectResponse(url=redirect_url, status_code=302, headers=response.headers)
```

On the **target** side there is no new SDK code. The SDK's mounted `/auth/login` route already forwards arbitrary query params to `/authorize`, so the redirect lands on `/auth/login?session_transfer_token=...` and the standard callback establishes the impersonated session. Read the acting party off the session afterwards with `session["user"].get("act")`.

Impersonation is one principal acting as another, so the mint is the event worth recording. The SDK does not log it for you. After a successful `request_session_transfer_token`, record who impersonated whom and when (the agent from your own auth context, the customer from your `subject_token`), so each impersonation is auditable on the initiator side, not just via the `act` claim the target later sees.

> **NOTE**: `build_session_transfer_redirect` attaches a single-use credential to the target URL, so that URL must be a trusted, app-controlled value. Never derive it from untrusted input such as a user-supplied `returnTo`. The SDK checks the URL shape, not the host: it requires an absolute https target (http only for localhost/loopback) and rejects a fragment, but any https host passes, so passing a trusted app-controlled value is on you.

> **NOTE**: On the target, redemption via the mounted `/auth/login` route is incompatible with `pushed_authorization_requests`. When PAR is enabled the SDK does not forward inline authorization parameters, so `session_transfer_token` never reaches `/authorize` and the STT is not redeemed. Redeem the STT on a client (or a route) without PAR.

The STT-specific error codes (`ACTOR_UNAVAILABLE`, raised client-side when no actor can be resolved; `SETACTOR_REQUIRED`; `SESSION_TRANSFER_DISABLED`) are on `CustomTokenExchangeErrorCode` and surface through the same handling as below.

## 4. Error Handling

Register the SDK's exception handler once, and `CustomTokenExchangeError` will be mapped to an HTTP `400` JSON response automatically:

Expand Down Expand Up @@ -142,7 +176,7 @@ See the [auth0-server-python Custom Token Exchange doc](https://github.com/auth0

`INVALID_TOKEN_FORMAT` is raised client-side before any network call for an empty or whitespace-only `subject_token`, or one with a `"Bearer "` prefix. Other malformed-but-nonempty values (including a `subject_token_type` that isn't a valid URI) are not checked client-side and are sent to Auth0, which rejects them.

## 4. Token Type URIs
## 5. Token Type URIs

`subject_token_type` accepts any URI β€” a standard RFC 8693 URN (e.g. `urn:ietf:params:oauth:token-type:jwt`) or your own namespace (e.g. `urn:acme:legacy-session-token`).

Expand Down
Loading
Loading