You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add Custom Token Exchange support (RFC 8693)
Implements get_token_by_exchange_profile() method for exchanging subject tokens
via Auth0 Token Exchange Profiles, following RFC 8693.
Features:
- Subject token exchange with strict validation (fail-fast on whitespace)
- Support for optional audience, scope, and requested_token_type parameters
- Extra parameters for Actions with reserved param validation (fail-fast)
- DoS protection with 20-item array size limit for extra parameters
- Returns access_token, expires_in, expires_at, and optional fields
- HTTP Basic authentication with confidential client credentials
- Comprehensive error handling with upstream status preservation
- 10-second HTTP timeout to prevent hanging requests
Implementation details:
- Validates subject tokens strictly (rejects whitespace, Bearer prefix)
- Fails fast when reserved OAuth parameters supplied in extras
- Enforces MAX_ARRAY_VALUES_PER_KEY=20 for DoS protection
- Preserves upstream 4xx error status codes
- Module-level constants (TOKEN_EXCHANGE_GRANT_TYPE, RESERVED_PARAMS)
- Case-insensitive header handling (normalizes to lowercase)
- Precise error handling (ValueError for JSON parse errors)
- Adds GetTokenByExchangeProfileError for validation failures
Validation against auth0-auth-js:
- Whitespace handling matches strict JS SDK behavior (fail-fast)
- Array size limit matches JS SDK (20 items for DoS protection)
- Reserved params list aligned with JS SDK implementation
- Improves on JS SDK: fails fast on reserved params vs silent ignore
- Enhances return value: includes both expires_in and expires_at
Documentation:
- Added Early Access feature note with confidential client requirement
- Documented Token Exchange Profile URI matching and namespace rules
- Security warning for extra parameters (form fields, no secrets)
- Clarified audience optionality with inline example
- Linked to auth0-auth-js companion SDK and Auth0 docs
Tests: 24 new tests covering success paths, validation, error handling,
and edge cases. All 84 tests passing with 85% coverage.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: README.md
+64-1Lines changed: 64 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,6 +113,69 @@ asyncio.run(main())
113
113
114
114
More info https://auth0.com/docs/secure/tokens/token-vault
115
115
116
+
### 5. Custom Token Exchange (Early Access)
117
+
118
+
> [!NOTE]
119
+
> 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.
120
+
121
+
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).
122
+
123
+
Custom Token Exchange allows you to exchange a subject token for Auth0 tokens using RFC 8693. This is useful for:
124
+
- Getting Auth0 tokens for another audience
125
+
- Integrating external identity providers
126
+
- Migrating to Auth0
127
+
128
+
```python
129
+
import asyncio
130
+
131
+
from auth0_api_python import ApiClient, ApiClientOptions
132
+
133
+
asyncdefmain():
134
+
api_client = ApiClient(ApiClientOptions(
135
+
domain="<AUTH0_DOMAIN>",
136
+
audience="<AUTH0_AUDIENCE>",
137
+
client_id="<AUTH0_CLIENT_ID>",
138
+
client_secret="<AUTH0_CLIENT_SECRET>",
139
+
))
140
+
141
+
subject_token ="..."# Token from your legacy system or external source
142
+
143
+
result =await api_client.get_token_by_exchange_profile(
144
+
subject_token=subject_token,
145
+
subject_token_type="urn:example:subject-token",
146
+
audience="https://api.example.com"# Optional - omit to use the client's default audience
147
+
)
148
+
149
+
# Result contains access_token, expires_in, expires_at, and optionally id_token, refresh_token
150
+
151
+
asyncio.run(main())
152
+
```
153
+
154
+
The `subject_token_type` must match a Token Exchange Profile configured in Auth0. This URI identifies which profile will process the exchange and cannot use reserved OAuth namespaces (e.g., `urn:ietf:params:oauth:*`).
155
+
156
+
#### Additional Parameters
157
+
158
+
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:
159
+
160
+
```python
161
+
result =await api_client.get_token_by_exchange_profile(
162
+
subject_token=subject_token,
163
+
subject_token_type="urn:example:subject-token",
164
+
audience="https://api.example.com",
165
+
extra={
166
+
"device_id": "device-12345",
167
+
"session_id": "sess-abc"
168
+
}
169
+
)
170
+
```
171
+
172
+
> [!WARNING]
173
+
> 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.
If the token lacks `my_custom_claim` or fails any standard check (issuer mismatch, expired token, invalid signature), the method raises a `VerifyAccessTokenError`.
128
191
129
-
### 5. DPoP Authentication
192
+
### 6. DPoP Authentication
130
193
131
194
> [!NOTE]
132
195
> 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