Skip to content

load_access_token discards upstream_claims embedded by _extract_upstream_claims #3723

@nadav-bit

Description

@nadav-bit

Summary

_extract_upstream_claims (added in #2997) allows subclasses to embed custom claims in the FastMCP JWT under the upstream_claims key. These claims are correctly signed into the JWT at issuance. However, load_access_token doesn't surface them during validation — it swaps the JWT for the upstream provider token and returns that, losing the upstream_claims.

Expected Behavior

Claims returned by _extract_upstream_claims should be accessible on the AccessToken returned by load_access_token, since they were signed into the JWT and verified during validation.

Actual Behavior

load_access_token:

  1. Verifies the FastMCP JWT signature (payload includes upstream_claims) ✅
  2. Extracts jti from the payload
  3. Looks up the upstream token via JTI mapping
  4. Validates the upstream token → returns AccessToken with upstream provider claims only
  5. upstream_claims from step 1 is discarded ❌

Reproduction

  1. Subclass OIDCProxy and override _extract_upstream_claims to return custom claims
  2. Authenticate via OAuth — the FastMCP JWT correctly contains upstream_claims
  3. Make an MCP tool call — load_access_token validates the JWT but the returned AccessToken.claims only has the upstream provider's claims, not the upstream_claims from the FastMCP JWT

Workaround

Override load_access_token to re-decode the already-verified JWT and merge upstream_claims into the AccessToken:

async def load_access_token(self, token: str) -> AccessToken | None:
    access_token = await super().load_access_token(token)
    if access_token is None:
        return None
    payload = pyjwt.decode(token, options={"verify_signature": False})
    upstream_claims = payload.get("upstream_claims")
    if upstream_claims:
        access_token.claims["upstream_claims"] = upstream_claims
    return access_token

Suggested Fix

In OAuthProxy.load_access_token, after verifying the JWT and looking up the upstream token, merge upstream_claims from the verified JWT payload into the returned AccessToken.claims. This makes _extract_upstream_claims useful for both external JWT consumers (API gateways) and internal FastMCP tool dependencies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    authRelated to authentication (Bearer, JWT, OAuth, WorkOS) for client or server.bugSomething isn't working. Reports of errors, unexpected behavior, or broken functionality.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions