Skip to content

Commit 87f13ad

Browse files
authored
Merge pull request #51 from btiernay/feat/custom-token-exchange
feat: add Custom Token Exchange support (RFC 8693)
2 parents a4c600c + ed534dd commit 87f13ad

File tree

11 files changed

+1707
-351
lines changed

11 files changed

+1707
-351
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ setup.py
2222
test.py
2323
test-script.py
2424
.coverage
25-
coverage.xml
25+
coverage.xml
26+
27+
# IDE
28+
.idea/

README.md

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ This SDK provides comprehensive support for securing APIs with Auth0-issued acce
2828

2929
- [Docs Site](https://auth0.com/docs) - explore our docs site and learn more about Auth0.
3030

31+
## Related SDKs
32+
33+
This library is part of Auth0's Python ecosystem for server-side authentication and API security. Related SDKs:
34+
35+
- **[auth0-auth-js](https://github.com/auth0/auth0-auth-js)** - JavaScript/TypeScript monorepo containing:
36+
- `@auth0/auth0-auth-js` - Core authentication client (low-level primitives)
37+
- `@auth0/auth0-api-js` - Server-side API security (Node.js equivalent of this library)
38+
- `@auth0/auth0-server-js` - Server-side web app authentication (session management)
39+
3140
## Getting Started
3241

3342
### 1. Install the SDK
@@ -113,6 +122,95 @@ asyncio.run(main())
113122

114123
More info https://auth0.com/docs/secure/tokens/token-vault
115124

125+
### 5. Custom Token Exchange (Early Access)
126+
127+
> [!NOTE]
128+
> This feature is currently available in [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access) for Enterprise customers. Please reach out to Auth0 support to get it enabled for your tenant.
129+
130+
This feature requires a [confidential client](https://auth0.com/docs/get-started/applications/confidential-and-public-applications#confidential-applications) (both `client_id` and `client_secret` must be configured).
131+
132+
Custom Token Exchange allows you to exchange a subject token for Auth0 tokens using RFC 8693. This is useful for:
133+
- Getting Auth0 tokens for another audience
134+
- Integrating external identity providers
135+
- Migrating to Auth0
136+
137+
```python
138+
import asyncio
139+
140+
from auth0_api_python import ApiClient, ApiClientOptions
141+
142+
async def main():
143+
api_client = ApiClient(ApiClientOptions(
144+
domain="<AUTH0_DOMAIN>",
145+
audience="<AUTH0_AUDIENCE>",
146+
client_id="<AUTH0_CLIENT_ID>",
147+
client_secret="<AUTH0_CLIENT_SECRET>",
148+
timeout=10.0 # Optional: HTTP timeout in seconds (default: 10.0)
149+
))
150+
151+
subject_token = "..." # Token from your legacy system or external source
152+
153+
result = await api_client.get_token_by_exchange_profile(
154+
subject_token=subject_token,
155+
subject_token_type="urn:example:subject-token",
156+
audience="https://api.example.com", # Optional - omit if your Action or tenant configuration sets the audience
157+
scope="openid profile email", # Optional
158+
requested_token_type="urn:ietf:params:oauth:token-type:access_token" # Optional
159+
)
160+
161+
# Result contains access_token, expires_in, expires_at
162+
# id_token, refresh_token, and scope are profile/Action dependent (not guaranteed; scope may be empty)
163+
164+
asyncio.run(main())
165+
```
166+
167+
**Important:**
168+
- Client authentication is sent via HTTP Basic (`client_id`/`client_secret`), not in the form body.
169+
- Do not prefix `subject_token` with "Bearer " - send the raw token value only (checked case-insensitively).
170+
- The `subject_token_type` must match a Token Exchange Profile configured in Auth0. This URI identifies which profile will process the exchange and **must not use reserved OAuth namespaces (IETF or vendor-controlled)**. Use your own collision-resistant namespace. See the [Custom Token Exchange documentation](https://auth0.com/docs/authenticate/custom-token-exchange) for naming guidance.
171+
- If neither an explicit `audience` nor tenant/Action logic sets it, you may receive a token not targeted at your API.
172+
173+
#### Additional Parameters
174+
175+
You can pass additional parameters for your Token Exchange Profile or Actions via the `extra` parameter. These are sent as form fields to Auth0 and may be inspected by Actions:
176+
177+
```python
178+
result = await api_client.get_token_by_exchange_profile(
179+
subject_token=subject_token,
180+
subject_token_type="urn:example:subject-token",
181+
audience="https://api.example.com",
182+
extra={
183+
"device_id": "device-12345",
184+
"session_id": "sess-abc"
185+
}
186+
)
187+
```
188+
189+
> [!WARNING]
190+
> Extra parameters are sent as form fields and may appear in logs. Do not include secrets or sensitive data. Reserved OAuth parameter names (like `grant_type`, `client_id`, `scope`) cannot be used and will raise an error. Arrays are supported but limited to 20 values per key to prevent abuse.
191+
192+
#### Error Handling
193+
194+
```python
195+
from auth0_api_python import GetTokenByExchangeProfileError, ApiError
196+
197+
try:
198+
result = await api_client.get_token_by_exchange_profile(
199+
subject_token=subject_token,
200+
subject_token_type="urn:example:subject-token"
201+
)
202+
except GetTokenByExchangeProfileError as e:
203+
# Validation errors (invalid token format, missing credentials, reserved params, etc.)
204+
print(f"Validation error: {e}")
205+
except ApiError as e:
206+
# Token endpoint errors (invalid_grant, network issues, malformed responses, etc.)
207+
print(f"API error: {e.code} - {e.message} (status: {e.status_code})")
208+
```
209+
210+
**Related SDKs:** [auth0-auth-js](https://github.com/auth0/auth0-auth-js) (see `@auth0/auth0-api-js` package for Node.js equivalent)
211+
212+
More info: https://auth0.com/docs/authenticate/custom-token-exchange
213+
116214
#### Requiring Additional Claims
117215

118216
If your application demands extra claims, specify them with `required_claims`:
@@ -126,7 +224,7 @@ decoded_and_verified_token = await api_client.verify_access_token(
126224

127225
If the token lacks `my_custom_claim` or fails any standard check (issuer mismatch, expired token, invalid signature), the method raises a `VerifyAccessTokenError`.
128226

129-
### 5. DPoP Authentication
227+
### 6. DPoP Authentication
130228

131229
> [!NOTE]
132230
> This feature is currently available in [Early Access](https://auth0.com/docs/troubleshoot/product-lifecycle/product-release-stages#early-access). Please reach out to Auth0 support to get it enabled for your tenant.

0 commit comments

Comments
 (0)