Skip to content

feat: Custom Token Exchange support#141

Open
rmad17 wants to merge 8 commits into
mainfrom
SDK-8920-cte
Open

feat: Custom Token Exchange support#141
rmad17 wants to merge 8 commits into
mainfrom
SDK-8920-cte

Conversation

@rmad17

@rmad17 rmad17 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Changes

Features

  • Token exchange (AuthClient): custom_token_exchange and login_with_custom_token_exchange. Call custom_token_exchange to exchange a token for Auth0 tokens without touching the caller's session
    (service-to-service delegation, downstream API calls with a different audience/scope). Call login_with_custom_token_exchange to exchange a token and establish a full Auth0 session, same as completing
    /auth/callback.
  • Actor tokens (delegation): both methods accept actor_token/actor_token_type to represent a party acting on behalf of the subject; Auth0 surfaces this as the act claim on the response
    (custom_token_exchange) or persisted on the session user (login_with_custom_token_exchange).
  • Organization support: both methods accept an organization parameter to scope the exchange to a specific org.
  • Error handling: CustomTokenExchangeError is raised on exchange failure or invalid subject/actor token parameters, and is mapped to an HTTP 400 JSON response automatically once

API Changes

  • New public methods: AuthClient.custom_token_exchange, AuthClient.login_with_custom_token_exchange.
  • New exports from auth0_fastapi.errors: CustomTokenExchangeError, CustomTokenExchangeErrorCode (re-exported from auth0-server-python).

@rmad17 rmad17 self-assigned this Jul 9, 2026
@rmad17 rmad17 changed the title Sdk 8920 cte feat: Custom Token Exchange support Jul 9, 2026
@rmad17
rmad17 requested a review from kishore7snehil July 12, 2026 09:50
@rmad17
rmad17 marked this pull request as ready for review July 12, 2026 09:56
@rmad17
rmad17 requested a review from a team as a code owner July 12, 2026 09:56
Comment thread src/auth0_fastapi/auth/auth_client.py Outdated
"""
return await self.client.complete_interactive_login(callback_url, store_options=store_options)

async def custom_token_exchange(

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.

This should go at the last.

await auth_client.handle_backchannel_logout(invalid_token)


class TestCustomTokenExchange:

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.

These tests should go at the last. We should avoid adding new tests in middle of file until it's relevant to the existing class or function.

with patch.object(auth_client.client, 'custom_token_exchange', new_callable=AsyncMock) as mock_exchange:
mock_exchange.side_effect = CustomTokenExchangeError(
CustomTokenExchangeErrorCode.INVALID_TOKEN_FORMAT,
"subject_token_type must be a valid URI",

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.

This simulated error message describes URI validation that the exchange path doesn't actually do. The underlying custom_token_exchange only rejects an empty or whitespace-only token and a "Bearer " prefix, there's no valid-URI check on subject_token_type anywhere. A maintainer reading this test would come away thinking URI validation exists when it doesn't.

Shall we switch the message to match a real trigger (like the empty-token case) so the test doesn't imply behavior we don't have? Worth noting the spec does ask for client-side URI validation before the network call, but since this SDK just wraps the base one, the actual check belongs in auth0-server-python rather than being added here.

)

@pytest.mark.asyncio
async def test_login_with_custom_token_exchange_passes_store_options_for_session_write(

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.

These login-variant tests cover the happy path and store_options forwarding, but not two things the docs lean on: forwarding actor_token/actor_token_type through this method (the custom_token_exchange test above checks that, the login one doesn't), and the missing-response case that the README and examples call out as raising a ValueError.

Could we add a couple of small delegation tests here to lock both down?

Comment thread examples/CustomTokenExchange.md Outdated
- `TOKEN_EXCHANGE_FAILED`: general token exchange failure (e.g., Auth0 returned an OAuth error)
- `INVALID_RESPONSE`: Auth0 returned a non-JSON response

`INVALID_TOKEN_FORMAT` is raised client-side before any network call — malformed tokens never reach Auth0.

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.

This says malformed tokens never reach Auth0, but the client-side check only catches an empty or whitespace-only token and a "Bearer " prefix. A subject_token_type that isn't a valid URI, or any other malformed-but-nonempty token, still goes to Auth0. Since this SDK just wraps auth0-server-python and that layer doesn't do URI validation either, the guarantee here reads stronger than what the code actually does.

Shall we soften this to say only empty, whitespace, or "Bearer "-prefixed tokens are caught before the call? And if we do want real URI validation, that check belongs in auth0-server-python so every wrapper inherits it.


`INVALID_TOKEN_FORMAT` is raised client-side before any network call — malformed tokens never reach Auth0.

## 5. Token Type URIs

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.

This token-type list, and the error-code list just above it, restate what's already in the auth0-server-python doc we link at the bottom. Since this SDK just wraps that one, these will drift the moment the base SDK changes a code or its URI guidance.

Could we trim both to a short pointer to the base SDK doc and the official Auth0 docs instead of re-listing them here? One thing to double check while we're at it: the reserved-namespace line names okta and http variants that aren't in the base doc or the spec, so that dashboard-config detail is probably safer left to the official docs.

```

> **NOTE**: When an `actor_token` is present, Auth0 does not issue a refresh token (`offline_access` is dropped). The acting party is fixed at exchange time and is not re-emitted on a later token refresh.

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.

We can add Organization Support Section here

@rmad17
rmad17 requested a review from kishore7snehil July 16, 2026 16:20
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.

2 participants