Skip to content

Embedded auth server: CIMD clients get no audience, so every refresh_token grant fails #6489

Description

@alex-feel

Bug description

Clients registered via Client ID Metadata Documents (CIMD) can sign in but can never refresh. buildFositeClient in pkg/authserver/storage/cimd_decorator.go sets Audience: nil on every CIMD-resolved client, while the token handler (pkg/authserver/server/handlers/token.go) grants the session audience from the resource parameter (or defaults it to AllowedAudiences[0] for authorization_code grants, #4805). On the refresh grant, fosite's DefaultAudienceMatchingStrategy checks the granted audience against the client's audience list; an empty list matches nothing, so the grant fails with invalid_request. The authorization_code path never runs that check, which is why sign-in works.

This is the CIMD twin of #3777: DCR clients failed the same way, and #3796 fixed them by registering DCR clients with the server's AllowedAudiences. The CIMD resolution path never received that fix.

Steps to reproduce

  1. Run the embedded auth server with CIMD enabled and one allowed audience, e.g. https://mcp.example.com/mcp.
  2. Complete an authorization code + PKCE flow with a CIMD client id (e.g. https://claude.ai/oauth/claude-code-client-metadata), sending resource=https://mcp.example.com/mcp and scope offline_access. The code exchange succeeds and returns a refresh token.
  3. Send the refresh token back: POST /oauth/token with grant_type=refresh_token, the token, and the same client_id.

Expected behavior

A new access token: offline_access was granted, and DCR clients refresh fine after #3796.

Actual behavior

HTTP 400
error: invalid_request
error_description: [...] Requested audience 'https://mcp.example.com/mcp' has not been whitelisted by the OAuth 2.0 Client.

Every CIMD client (ChatGPT connectors, Claude Code) has to re-authorize interactively once per access-token lifespan.

Environment (if relevant)

  • ToolHive version: v0.46.0; the mechanism is unchanged on main (ee527be); fosite v0.49.0.

Additional context

Suggested fix, mirroring #3796: pass the server's AllowedAudiences into CIMDDecoratorConfig (constructed in pkg/authserver/server_impl.go) and set it as the resolved client's Audience in buildFositeClient, whose comment already states the AS should apply its own audience policy. A refresh-grant regression test would have caught this. Happy to send a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIssue needs initial triage by a maintainer

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions