Skip to content

fix(mcp): require audience-bound OAuth JWTs - #3130

Open
fahreddinozcan wants to merge 1 commit into
masterfrom
codex/mcp-oauth-jwt-migration
Open

fix(mcp): require audience-bound OAuth JWTs#3130
fahreddinozcan wants to merge 1 commit into
masterfrom
codex/mcp-oauth-jwt-migration

Conversation

@fahreddinozcan

Copy link
Copy Markdown
Collaborator

Summary

Replace the permissive OAuth ingress behavior with locally verified, audience-bound JWT access tokens on /mcp/oauth.

This supersedes #3068. Server-side validation is still required for every bearer token; the change is that Clerk JWT access tokens can be verified locally from JWKS, while opaque values can only be trusted after authoritative introspection. Passing an opaque string through to the downstream API was not sufficient validation at the MCP resource boundary.

What changes

  • Reject every non-JWT credential on /mcp/oauth, including legacy oat_ tokens, API keys, and arbitrary bearer strings.
  • Verify Clerk OAuth JWTs with:
    • exact issuer;
    • exact MCP audience (defaults to RESOURCE_URL);
    • remote JWKS signature;
    • RS256 allowlist;
    • required sub, iat, and exp claims; and
    • required profile email scopes by default.
  • Keep /mcp anonymous/API-key behavior unchanged.
  • Add configurable OAUTH_ACCESS_TOKEN_AUDIENCE and OAUTH_REQUIRED_SCOPES values.
  • Add a changeset, tests, and a Clerk rollout/rollback runbook.

OAuth vs OIDC

This migrates OAuth access tokens to JWT format; it does not use OIDC ID tokens as API credentials. ID tokens describe the user's authentication event and are intended for the client. Requiring the MCP resource audience and delegated OAuth scopes prevents an otherwise similarly signed ID token from being accepted by the resource server.

Deployment gate

Before deployment, enable Generate access tokens as JWTs for the Clerk OAuth application in staging and inspect a token obtained through the complete authorization-code flow.

The token must include the configured issuer, aud=https://mcp.context7.com, sub, iat, exp, and the required scopes. Although the callback currently forwards the RFC 8707 resource parameter, Clerk's public documentation does not guarantee that this value becomes the JWT aud claim.

If Clerk does not produce an MCP-bound audience, do not deploy and do not remove audience validation. Configure the audience in Clerk or introduce a Context7 token-exchange issuer instead. The full procedure is in packages/mcp/OAUTH_JWT_MIGRATION.md.

Existing opaque-token holders may need to re-authorize. JWT revocation is bounded by token expiry, so token lifetime is an operational security setting.

Test plan

  • pnpm --filter @upstash/context7-mcp format:check
  • pnpm --filter @upstash/context7-mcp typecheck
  • pnpm --filter @upstash/context7-mcp lint:check
  • pnpm --filter @upstash/context7-mcp test

Result: 6 test files and 63 tests passed.

New coverage asserts strict Clerk verification options, required scopes, and rejection of opaque, API-key-shaped, and arbitrary bearer credentials on /mcp/oauth.

Follow-up boundary

The MCP server currently forwards the authenticated bearer credential to the Context7 API. If those components are treated as separate resource servers, that hop should be replaced with a scoped internal assertion or service credential. This PR fixes ingress validation and documents that follow-up; it does not claim bearer-token forwarding is resolved.

References

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.

1 participant