Skip to content

Support MCP OAuth through the configured OIDC issuer - #63

Open
yalait wants to merge 1 commit into
plannotator:mainfrom
yalait:oidc-mcp-oauth
Open

yalait wants to merge 1 commit into
plannotator:mainfrom
yalait:oidc-mcp-oauth

Conversation

@yalait

@yalait yalait commented Sep 15, 2026

Copy link
Copy Markdown

Closes #57.

/mcp now accepts end-user access tokens from the OIDC issuer already configured for
browser login, through the existing ExternalMcpBearerVerifier seam and the same
oidc:<issuer> plus sub binding. A token must name the configured issuer and carry
<origin>/mcp in aud. ID tokens, tokens for another resource, and browser cookies are
refused. The server publishes RFC 9728 protected-resource metadata and answers an
unauthenticated call with 401 and resource_metadata. Managed API keys and the WorkOS
path are untouched.

0028 records the reversal, 0020's exclusion points at it, MCP-013 takes the generic path,
and docs/deployment.md explains the audience mapper and the two client-registration
options: the issuer's own RFC 7591 endpoint, or one client an administrator registers.
Artifact Server issues no client credentials, so MCP-014 stays as it is.

resolveIdentity now also receives the credential. WorkOS ignores it. The OIDC path
needs it, because userinfo answers the presenter of the token, and a Keycloak realm can
leave email out of the access token. The caching alternative is in 0028.

Two calls for you

aud must contain <origin>/mcp, but a multi-valued aud passes. The Keycloak harness
in this PR mints this token from a client whose only extra configuration is the audience
mapper from the deployment guide:

{"aud": ["http://127.0.0.1:44835/mcp", "account"], "typ": "Bearer"}

account comes from Keycloak's audience-resolve mapper, so a single-valued check would
refuse every Keycloak token. A token without this resource in aud is refused either
way. Tell me if you meant single-valued and I will tighten it.

A valid token from a person who was never admitted gets 401 at /mcp, while the
browser callback answers 403 for the same identity. I kept the existing mapping.

Verification

pnpm lint, pnpm typecheck, pnpm build, pnpm conformance:validate,
pnpm conformance:tests, pnpm test:oidc with two new MCP cases against the pinned
Keycloak, and pnpm test with 320 passed. Three tests/cli process-spawning tests time
out under parallel load here and pass when run alone, the same way they do on an
unmodified checkout. I did not run the Compose, object-storage, Kubernetes, coverage, or
performance legs of verify:iteration.

Hostile cases in tests/conformance/oidc-mcp-authorization.test.ts: expired, wrong
issuer, a second issuer signing with the same key, wrong and multi-valued audiences, ID
token, HS256, unknown kid, missing subject, missing claims, userinfo outage, userinfo
rejection, key-set outage. That last case turned up a bug worth naming: jose reports a
non-200 JWKS response as a generic error, which read as an invalid token and answered
401. The key-set fetch now raises its own failure.

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.

Generic OIDC installations cannot use per-user MCP authorization

1 participant