Commit 5fb9754
feat: add Custom Token Exchange support (RFC 8693)
Add get_token_by_exchange_profile() method for exchanging subject tokens for Auth0 tokens using RFC 8693 OAuth 2.0 Token Exchange. This enables migration workflows, external IdP integration, and custom token exchange scenarios via Token Exchange Profiles.
## Core Features
- RFC 8693 Compliant: Standard OAuth 2.0 token exchange flow
- Token Exchange Profiles: subject_token_type determines which profile processes the exchange
- HTTP Basic Authentication: client_secret_basic for confidential clients
- Configurable Timeout: HTTP timeout support (default: 10.0s)
- Extra Parameters: Pass custom form fields with comprehensive validation
- Lenient expires_in: Coerces numeric strings like "3600" to int
## Security & Validation
- Reserved Parameter Protection: Case-insensitive denylist prevents override of OAuth parameters
- DoS Protection: Array size limit (MAX_ARRAY_VALUES_PER_KEY = 20) for extra parameters
- Strict Token Validation: Fail-fast checks for whitespace, Bearer prefix, blank tokens
- Type Safety: Accepts str or sequences (list, tuple); rejects dict/set with clear errors
- Network Efficiency: Early validation prevents unnecessary token endpoint requests
## Error Handling
- GetTokenByExchangeProfileError: Validation failures (invalid format, missing credentials, reserved params, unsupported types)
- ApiError: Token endpoint failures (invalid_grant, network issues, timeouts, malformed responses)
- Both exported at package level: from auth0_api_python import ApiError, GetTokenByExchangeProfileError
## Response Handling
- Always returned: access_token, expires_in, expires_at (deterministic calculation)
- Optional fields: id_token, refresh_token, scope, token_type, issued_token_type
- Preserves falsy values: Empty scope strings from server responses
## API Signature
async def get_token_by_exchange_profile(
subject_token: str,
subject_token_type: str,
audience: Optional[str] = None,
scope: Optional[str] = None,
requested_token_type: Optional[str] = None,
extra: Optional[Mapping[str, Union[str, Sequence[str]]]] = None
) -> dict[str, Any]
## Testing
- 118 tests with 86% coverage
- Comprehensive test suite:
- HTTP Basic auth verification
- Discovery edge cases (missing endpoint, non-JSON, HTTP errors)
- Token endpoint error shapes (non-200, 200 with non-JSON, network errors)
- Response field handling (empty strings, numeric string coercion, zero/negative expires_in)
- Extras handling (array limits, case-insensitive denylist, type coercion, tuple support, dict/set rejection)
- Parameter wiring (presence/absence validation)
- Confidential client requirements
- Shared test utilities in conftest.py:
- Fixtures: api_client_confidential, mock_discovery
- Helpers: last_form(), last_auth_header(), mock_token_response(), token_success()
- Assertions: assert_api_error(), assert_http_basic_auth(), assert_form_post()
- Deterministic testing: freezegun for expires_at validation
- Table-driven tests: Parameterized validation tests for all error conditions
## Documentation
- Comprehensive README with usage examples, error handling, and warnings
- Related SDKs section: Links to auth0-auth-js and auth0-api-js
- Clear Early Access callout: Enterprise feature requiring tenant enablement
- Subject token type namespace guidance: Avoid reserved OAuth namespaces
- Extra parameters documentation: Reserved parameter warnings and array limits
- Error handling examples: Both validation and API error scenarios
## Cross-SDK Validation
Validated against auth0-auth-js for consistency:
- Whitespace validation (strict, fail-fast)
- Array size limits (DoS protection)
- Reserved parameter checking (case-insensitive)
- HTTP Basic authentication
- Error handling patterns
## Follow-up Items
- Poetry/requirements.txt: Consider auto-generating requirements.txt via poetry export in CI. Currently maintained manually for .github/workflows/sca_scan.yml (Snyk security scanning). Source of truth remains pyproject.toml + poetry.lock.
## Breaking Changes
None. This is a new method with no impact on existing functionality.
## Related
- RFC 8693: https://datatracker.ietf.org/doc/html/rfc8693
- Auth0 Docs: https://auth0.com/docs/authenticate/custom-token-exchange
- Related SDK: https://github.com/auth0/auth0-auth-js, https://github.com/auth0/auth0-api-js
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>1 parent a4c600c commit 5fb9754
11 files changed
Lines changed: 1466 additions & 225 deletions
File tree
- src/auth0_api_python
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
31 | 38 | | |
32 | 39 | | |
33 | 40 | | |
| |||
113 | 120 | | |
114 | 121 | | |
115 | 122 | | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
116 | 211 | | |
117 | 212 | | |
118 | 213 | | |
| |||
126 | 221 | | |
127 | 222 | | |
128 | 223 | | |
129 | | - | |
| 224 | + | |
130 | 225 | | |
131 | 226 | | |
132 | 227 | | |
| |||
0 commit comments