Skip to content

Wire RFC 9728 resource_metadata discovery into Bearer 401 + observability#107

Merged
jpr5 merged 2 commits into
mainfrom
feature/bearer-rfc9728-discovery-wire
Jun 15, 2026
Merged

Wire RFC 9728 resource_metadata discovery into Bearer 401 + observability#107
jpr5 merged 2 commits into
mainfrom
feature/bearer-rfc9728-discovery-wire

Conversation

@jpr5

@jpr5 jpr5 commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Bearer middleware 401s now include RFC 9728 resource_metadata on WWW-Authenticate (wires the dead unauthorizedWithDiscovery helper at src/oauth/handlers.ts:626, which had zero callers since landing).
  • Adds 3 structured [oauth] bearer_failure reason=… log lines for observability — empty_token, the four typed JWT errors discriminated by instanceof (invalid_signature / expired / aud_mismatch / malformed_token), and the unknown fallthrough.
  • Discovery endpoint .well-known/oauth-protected-resource already lives on this server, so no new endpoint work.

Spec: https://datatracker.ietf.org/doc/html/rfc9728

Red test output

Pre-substitution, the new RFC 9728 cases fail because the current unauthorized() emits no resource_metadata=:

 FAIL  src/__tests__/oauth-handlers.test.ts > bearerMiddleware > RFC 9728 resource_metadata discovery on 401 > empty Bearer token → 401 + resource_metadata
AssertionError: expected "vi.fn()" to be called with arguments: [ 'WWW-Authenticate', …(1) ]

Received:
  1st vi.fn() call:
  [
    "WWW-Authenticate",
-   StringContaining "resource_metadata=\"https://mcp.example.com/.well-known/oauth-protected-resource\"",
+   "Bearer realm=\"mcp\", error=\"invalid_token\"",
  ]

 FAIL  ... > invalid signature → 401 + resource_metadata
 FAIL  ... > expired token → 401 + resource_metadata
 FAIL  ... > aud mismatch → 401 + resource_metadata

 Test Files  1 failed (1)
      Tests  4 failed | 55 skipped (59)

Post-substitution: 4 passed, 0 failed, full repo suite green (Test Files 334 passed (334) | Tests 6248 passed (6248)).

Test plan

  • New RFC 9728 resource_metadata discovery on 401 describe block in oauth-handlers.test.ts asserts WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource" on each Bearer failure mode (empty / signature / expired / aud).
  • Pre-existing bearerMiddleware > expired token and oauth-e2e > garbage token updated to the new contract (error="invalid_token" moved from header to JSON body, mirroring what unauthorizedWithDiscovery was always emitting).
  • npm test green locally + in CI.

Risk

None — discovery endpoint already live; substitution preserves 401 status code; unauthorized() helper retained (other callers may exist); no new infra. The header contract narrows from error="invalid_token" (RFC 6750 optional) to resource_metadata=… (RFC 9728 required for discovery) — bodies are unchanged.

jpr5 added 2 commits June 15, 2026 08:12
…lity

Bearer middleware 401s on /mcp now advertise the protected-resource
metadata document on WWW-Authenticate via `resource_metadata=`, wiring
the previously-dead `unauthorizedWithDiscovery` helper at
src/oauth/handlers.ts:626 into all three failure sites:
empty token, the four typed JWT errors (invalid signature, expired,
aud mismatch, malformed), and the unknown-error fallthrough.

The discovery endpoint already exists on this server, so no new
endpoint work — this is pure substitution at the call sites.

Adds three `console.warn` lines (`[oauth] bearer_failure reason=…`)
matching the existing log shape (`[oauth] <event> ip=<ip>`), with the
catch-block reason discriminated by `instanceof` so operators can tell
signature drift from clock skew from aud-mismatch (RFC 8707) from a
malformed token.

The `unauthorized()` helper is preserved — `error="invalid_token"`
moves from WWW-Authenticate to the JSON body, mirroring what
`unauthorizedWithDiscovery` was always emitting. Updates the two
pre-existing tests that asserted `error="invalid_token"` on the header.

Spec: https://datatracker.ietf.org/doc/html/rfc9728
@jpr5 jpr5 merged commit 03ee87a into main Jun 15, 2026
6 checks passed
@jpr5 jpr5 deleted the feature/bearer-rfc9728-discovery-wire branch June 15, 2026 15:33
jpr5 added a commit that referenced this pull request Jun 15, 2026
…109)

Patch for PR #107 (#107).
Publishes @copilotkit/pathfinder@1.15.1 → tags v1.15.1 → Publish Docker
→ Railway redeploy.
jpr5 added a commit that referenced this pull request Jun 15, 2026
…8/6750 (#110)

Updates three doc pages to reflect what shipped in v1.15.0 (PR #106) and
v1.15.1 (PR #107):

- `docs/deploy/index.html`: add `PATHFINDER_CONSENT_HMAC_KEY` to
env-vars table, docker-compose, and systemd examples (Required in
production, same shape as `MCP_JWT_SECRET`).
- `docs/config/index.html`: document consent screen, redirect_uri
policy, client cap (10k/100-per-IP), and security headers.
- `docs/clients/index.html`: note the consent screen in the claude.ai
flow + the new `WWW-Authenticate` `resource_metadata=` discovery hint.

No behavioral change. Pages auto-deploy via
`.github/workflows/deploy-pages.yml` on merge.
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