Skip to content

feat: add On-Behalf-Of token exchange support - #122

Merged
kishore7snehil merged 2 commits into
mainfrom
feat/on-behalf-of-token-exchange
Aug 17, 2026
Merged

feat: add On-Behalf-Of token exchange support#122
kishore7snehil merged 2 commits into
mainfrom
feat/on-behalf-of-token-exchange

Conversation

@kishore7snehil

Copy link
Copy Markdown
Contributor

📋 Changes

This PR adds On-Behalf-Of (OBO) token exchange support to auth0-fastapi-api, enabling MCP servers and intermediary FastAPI APIs to exchange an incoming Auth0 access token for a new access token targeting a downstream API while preserving the original user identity (RFC 8693). The exchange itself is performed by the underlying auth0-api-python client (auth0.api_client.get_token_on_behalf_of()), consistent with how the SDK already surfaces get_access_token_for_connection(). This PR exposes the OBO surface through the plugin and documents it.

✨ Features

  • On-Behalf-Of Token Exchange: Exchange an incoming access token for a downstream audience via auth0.api_client.get_token_on_behalf_of(access_token, audience, scope?), returning access-token-oriented fields only (no id_token or refresh_token). Requires a confidential client (client_id and client_secret).
  • Actor Claim Helpers: get_current_actor() and get_delegation_chain() re-exported from fastapi_plugin for inspecting the act claim on verified tokens - the current actor (outermost act.sub) for authorization and the full delegation chain for audit or attribution.

🔧 API Changes

  • New re-exports from fastapi_plugin: get_current_actor, get_delegation_chain, OnBehalfOfTokenResult, GetTokenByExchangeProfileError, and ApiError

📖 Documentation

  • Updated README.md with an On-Behalf-Of Token Exchange section
  • Updated EXAMPLES.md with the end-to-end flow (verify -> exchange -> call downstream), return value, error handling, and delegation inspection, plus production and DPoP notes

🧪 Testing

  • This change adds test coverage
  • This change has been tested on the latest version of the platform/language
Manual Testing

Requires an Auth0 tenant with an On-Behalf-Of / token-exchange profile configured: an API for the intermediary's own audience, a second API for the downstream audience, and a confidential client authorized for the downstream API.

import asyncio
from fastapi_plugin import Auth0FastAPI, get_current_actor, get_delegation_chain

async def main():
    auth0 = Auth0FastAPI(
        domain="<AUTH0_DOMAIN>",
        audience="<AUTH0_AUDIENCE>",
        client_id="<AUTH0_CLIENT_ID>",
        client_secret="<AUTH0_CLIENT_SECRET>",
    )
    incoming_access_token = "<ACCESS_TOKEN_FOR_AUDIENCE>"

    result = await auth0.api_client.get_token_on_behalf_of(
        access_token=incoming_access_token,
        audience="<DOWNSTREAM_AUDIENCE>",
        scope="read:data",
    )
    print("exchanged:", result["access_token"])

    claims = await auth0.api_client.verify_access_token(access_token=result["access_token"])
    print("current actor:", get_current_actor(claims))
    print("delegation chain:", get_delegation_chain(claims))

asyncio.run(main())

Expected: the exchange returns a downstream-scoped access token whose act.sub identifies the exchanging client, and the delegation helpers resolve the current actor and full chain.

Contributor Checklist

@kishore7snehil
kishore7snehil requested a review from a team as a code owner August 14, 2026 09:00
Comment thread EXAMPLES.md Outdated
Comment thread EXAMPLES.md Outdated
Comment thread EXAMPLES.md Outdated
Comment thread EXAMPLES.md
Comment thread EXAMPLES.md Outdated
Comment thread EXAMPLES.md
Comment thread fastapi_plugin/__init__.py
Comment thread tests/test_on_behalf_of.py Outdated
- Re-export MissingRequiredArgumentError, VerifyAccessTokenError, and
  BaseAuthError from fastapi_plugin so the documented OBO surface is catchable
  without reaching into auth0_api_python.errors
- Correct the Error Handling docs: GetTokenByExchangeProfileError also fires at
  request time for a malformed token, and document the request-time cases
- Fix the authorization example to raise HTTPException(403) instead of
  PermissionError, which escaped as a 500
- Clarify the DPoP note: the binding is not preserved on the exchanged token
- Move the OBO section after Protecting API Routes so require_auth() is
  introduced first
- Add a TestClient test for the require_auth -> exchange flow and drop the
  duplicates of upstream auth0-api-python tests
@kishore7snehil
kishore7snehil merged commit de81d99 into main Aug 17, 2026
9 checks passed
@kishore7snehil kishore7snehil mentioned this pull request Aug 28, 2026
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