Skip to content

Conversation

@btiernay
Copy link
Contributor

@btiernay btiernay commented Oct 25, 2025

Custom Token Exchange Support (RFC 8693)

Adds Custom Token Exchange support (RFC 8693) for exchanging subject tokens via Auth0 Token Exchange Profiles for parity with auth0/auth0-auth-js#75.

Note

Early Access feature for Enterprise customers. Contact Auth0 support to enable.

API

from auth0_api_python import ApiClient, ApiClientOptions, ApiError, GetTokenByExchangeProfileError

api_client = ApiClient(ApiClientOptions(
    domain="<AUTH0_DOMAIN>",
    audience="<AUTH0_AUDIENCE>",
    client_id="<AUTH0_CLIENT_ID>",
    client_secret="<AUTH0_CLIENT_SECRET>",
    timeout=10.0  # Optional
))

try:
    result = await api_client.get_token_by_exchange_profile(
        subject_token=subject_token,
        subject_token_type="urn:example:subject-token",
        audience="https://api.example.com",  # Optional
        scope="openid profile",  # Optional
        extra={"device_id": "device-123"}  # Optional
    )
    # Returns: {access_token, expires_in, expires_at, scope?, id_token?, ...}
except GetTokenByExchangeProfileError as e:
    print(f"Validation error: {e}")
except ApiError as e:
    print(f"API error: {e.code} - {e.message}")

Features

  • RFC 8693 compliant token exchange
  • HTTP Basic auth (client_secret_basic)
  • Reserved parameter protection (case-insensitive)
  • DoS protection (array size limits: 20)
  • Strict validation (whitespace, Bearer prefix, blank tokens)
  • Type safety (str, sequences; rejects dict/set/bytes)
  • Lenient expires_in (coerces numeric strings, rejects negatives)

Testing

  • 136 tests, 87% coverage
  • Table-driven validation tests
  • Shared utilities in conftest.py
  • Cross-validated against auth0-auth-js

Behavioral Changes in verify_request()

Important

RFC 7230 Compliance Improvements

This PR improves verify_request() HTTP header parsing to be fully RFC 7230 compliant:

✅ What Improved (Zero Breaking Risk)

  1. Header Case-Insensitivity: HTTP headers are now normalized to lowercase per RFC 7230

    • Authorization, authorization, AUTHORIZATION all work correctly
    • DPoP, dpop, DPOP all work correctly
    • Impact: Fixes edge cases where clients send different casing
  2. Whitespace Handling: Now uses split(None, 1) per RFC 7230 Section 3.2

    • Correctly handles tabs between scheme and token
    • Correctly handles multiple spaces between scheme and token
    • Impact: Previously valid tokens with extra whitespace now accepted (bug fix)

⚠️ Edge Case Error Type Change

Scenario: Malformed Authorization header with spaces inside the token (e.g., "Bearer token with spaces")

  • Before: Raised InvalidAuthSchemeError at parsing stage
  • After: Raises VerifyAccessTokenError during JWT validation
  • Breaking Risk: Negligible
    • No HTTP client generates headers with internal spaces
    • Well-designed error handlers catch BaseAuthError (parent class)
    • The token would fail validation anyway (just different error type)

Examples

# These now work (previously failed):
await verify_request({"Authorization": "Bearer  token"})  # Multiple spaces - OK
await verify_request({"authorization": "bearer token"})  # Lowercase - OK
await verify_request({"DPoP": proof})  # Uppercase D - OK

# This changes error type (but still fails):
await verify_request({"Authorization": "Bearer bad token"})
# Before: InvalidAuthSchemeError (split returned 3+ parts)
# After: VerifyAccessTokenError ("bad token" is invalid JWT)

Recent Improvements

Code Review Feedback (Latest Commit)

Based on automated PR review feedback, the following improvements were made:

Timeout Consistency

  • Added timeout configuration to get_access_token_for_connection for consistency with get_token_by_exchange_profile
  • Both methods now respect ApiClientOptions.timeout (default: 10.0s)

Content-Type Parsing

  • Improved lenient JSON detection in error responses with explicit comments
  • Handles both application/json and text/json Content-Type headers gracefully

Enhanced Error Messages

  • Unsupported type errors now explain which types are allowed: "Only strings, numbers, booleans, and sequences (list/tuple) are allowed"
  • Bearer prefix validation error clarifies case-insensitive check behavior

Documentation Clarity

  • README now documents that Bearer prefix check is case-insensitive
  • Docstring example fixed to avoid "'await' outside async function" linting warning

Related


Release Notes

Version Recommendation

Tip

Recommended: Minor version bump (e.g., 1.1.0) for new public API

Alternative: If planning 1.0 GA, could ship as 1.0.0

CHANGELOG Entries

Added

  • Custom Token Exchange (RFC 8693) [Early Access] - New get_token_by_exchange_profile() method for exchanging subject tokens via Auth0 Token Exchange Profiles
  • Timeout Configuration - New timeout option in ApiClientOptions (default: 10.0s) for HTTP requests
  • New Error Type - GetTokenByExchangeProfileError for token exchange validation errors

Changed

  • RFC 7230 HTTP Compliance - verify_request() improvements:
    • Header Case-Insensitivity: All HTTP headers now correctly normalized to lowercase per RFC 7230
    • Whitespace Handling: Uses split(None, 1) to correctly handle tabs and multiple spaces between scheme and token
    • Bug Fix: Valid tokens with extra whitespace between scheme and token are now accepted (previously incorrectly rejected)
    • Error Type Change (Edge Case): Malformed tokens with spaces inside the token value now raise VerifyAccessTokenError (during JWT parsing) instead of InvalidAuthSchemeError (at header parsing stage)
      • Breaking Risk: Negligible - HTTP clients don't generate such headers; well-designed error handlers catch BaseAuthError
      • Mitigation: If catching InvalidAuthSchemeError specifically for this edge case, update to catch VerifyAccessTokenError or BaseAuthError

Security

Warning

Security improvements in this release:

  • Token Exchange uses HTTP Basic authentication for client credentials (never sent in form body, reducing log exposure risk)
  • DoS protection: extra parameters limited to 20 array values per key
  • Reserved OAuth parameters protected with case-insensitive validation

Dependency Changes

Note

  • poetry.lock: Removed duplicate Python 3.14-gated wheels (coverage, cryptography) - safe deduplication for python-versions = "^3.9"
  • Added: freezegun (dev dependency for deterministic time testing)

CI/CD Notes

  • ✅ No changes needed to CI matrix (Python 3.14 wheels were duplicates, not requirements)
  • ✅ All 136 tests pass with 87% coverage

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

@btiernay btiernay force-pushed the feat/custom-token-exchange branch 11 times, most recently from 6c41684 to 2df8d4c Compare October 25, 2025 23:14
@btiernay btiernay force-pushed the feat/custom-token-exchange branch 8 times, most recently from d2b6bfb to 5c58db4 Compare October 26, 2025 01:47
@btiernay btiernay force-pushed the feat/custom-token-exchange branch 7 times, most recently from f8c84d2 to 898fd85 Compare October 26, 2025 02:46
Implements token exchange via Exchange Profiles for Auth0 API client.

Features:
- get_token_by_exchange_profile() method with HTTP Basic auth
- Reserved parameter protection (case-insensitive)
- DoS protection (array size limits)
- Strict token validation (whitespace, Bearer prefix)
- Type safety for extra params (str, sequences; rejects dict/set/bytes)
- Comprehensive error handling (GetTokenByExchangeProfileError, ApiError)
- Lenient expires_in parsing (coerces numeric strings)
- Negative expires_in validation

Testing:
- 129 tests, 86% coverage
- Shared test utilities in conftest.py
- Table-driven validation tests
- Cross-validated against auth0-auth-js

Docs:
- README with usage examples and Early Access warning
- Related SDKs section
- Error handling patterns

Note: verify_request() now uses split(None, 1) for header parsing.
Malformed headers with multiple spaces now raise VerifyAccessTokenError
during JWT parsing instead of InvalidAuthSchemeError. Standard inputs
unaffected.
@btiernay btiernay force-pushed the feat/custom-token-exchange branch from 898fd85 to 49e003e Compare October 26, 2025 12:15
@btiernay btiernay marked this pull request as ready for review October 26, 2025 12:27
@btiernay btiernay requested a review from a team as a code owner October 26, 2025 12:27
@kishore7snehil
Copy link
Contributor

@claude

@github-actions
Copy link

github-actions bot commented Oct 28, 2025

Claude encountered an error —— View job

Failed with exit code 128

I'll analyze this and get back to you.

1 similar comment
@github-actions
Copy link

github-actions bot commented Oct 28, 2025

Claude encountered an error —— View job

Failed with exit code 128

I'll analyze this and get back to you.

@btiernay
Copy link
Contributor Author

See https://auth0team.atlassian.net/browse/DVTLS-1771

@kishore7snehil
Copy link
Contributor

@btiernay I have ran the CC on your PR. You can check out the results here. Please review them and take actions, if necessary.

@btiernay
Copy link
Contributor Author

Thanks. It's interesting because I did code reviews with both ChatGPT and Gemini and many of these were not flagged. Some of them are overly paranoid I think, but there are a few good ones in there as well. Will address.

…ency

- Add timeout configuration to get_access_token_for_connection for consistency
- Improve Content-Type parsing with explicit comments for lenient JSON detection
- Enhance unsupported type error messages to explain allowed types
- Clarify case-insensitive Bearer prefix check in error message and docs
- Fix docstring example to use async function context (avoid 'await outside async' warning)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@btiernay
Copy link
Contributor Author

@kishore7snehil Updated per feedback. See PR description for details.

Copy link
Contributor

@kishore7snehil kishore7snehil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested the CTE flow with Auth0 Setup and the changes LGTM!

@btiernay btiernay merged commit 87f13ad into auth0:main Oct 29, 2025
8 checks passed
@btiernay btiernay mentioned this pull request Oct 29, 2025
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