Public: anyone can read this; the crew can change it.

The MCP's 401 is one challenge from one function: it names the scopes, and says invalid_token when a presented token is expired or revoked #8365

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

The 401 challenge on `/mcp` and on `/api/v1/*` now comes from one function, `bearerChallenge(origin, request?)` in `src/http/oauth.ts`. `src/http/mcp.ts` and the API fallback in `src/http/server.ts` both call it.

shipped production is serving it deploy d5155e2

opened this

The 401 challenge on /mcp and on /api/v1/* now comes from one function, bearerChallenge(origin, request?) in src/http/oauth.ts. src/http/mcp.ts and the API fallback in src/http/server.ts both call it.

What changed

  • The value is now Bearer realm="forge", resource_metadata="…", scope="ro rw". resource_metadata is exactly as before, and the scopes come from OAUTH_SCOPES.
  • When the request carried Authorization: Bearer … and was still refused, it adds , error="invalid_token" (RFC 6750 §3). This covers a garbage token, an expired one or a revoked one.
  • The new test/oauth-challenge.test.ts asserts the header for no token, a garbage token and an expired token, on both /mcp and the API (POST /api/v1/repos).
  • test/oauth-discovery.test.ts pinned the old bare challenge for the API, so I updated its expected value to include scope="ro rw". That test was out of date, not wrong.

What I left out

  • /oauth/userinfo keeps its own Bearer error="invalid_token" header. It is a different endpoint and the issue didn't name it.
  • I didn't add an error_description.
  • A bearer that is present but malformed counts as "sent" only if it matches Bearer <something>.
  • A token that authenticates but has too little scope gets a 403, not this 401, so it is unchanged.

Checks

  • test/oauth-challenge.test.ts, test/oauth-discovery.test.ts, test/mcp-size.test.ts, test/oauth-protected-resource.test.ts, test/report-discovery.test.ts, test/mcp-discovery-walk.test.ts, test/mcp.test.ts and test/branches-action.test.ts pass, and tsc is clean.
  • I did not run the full suite; the forge's check runs it on the push.
  • scripts/gate.ts is not in this clone.
  • test/origins-security-push.test.ts also mentions www-authenticate and I did not run it.

To try it: curl -si -XPOST $URL/mcp -d '{}' shows the plain challenge. Adding -H 'authorization: Bearer nope' adds error="invalid_token".

Closes #8362

commented

The 401 challenge on /mcp and on /api/v1/* now comes from one function, bearerChallenge(origin, request?) in src/http/oauth.ts. src/http/mcp.ts and the API fallback in src/http/server.ts both call it.

What changed

  • The value is now Bearer realm="forge", resource_metadata="…", scope="ro rw". resource_metadata is exactly as before, and the scopes come from OAUTH_SCOPES.
  • When the request carried Authorization: Bearer … and was still refused, it adds , error="invalid_token" (RFC 6750 §3). This covers a garbage token, an expired one or a revoked one.
  • The new test/oauth-challenge.test.ts asserts the header for no token, a garbage token and an expired token, on both /mcp and the API (POST /api/v1/repos).
  • test/oauth-discovery.test.ts pinned the old bare challenge for the API, so I updated its expected value to include scope="ro rw". That test was out of date, not wrong.

What I left out

  • /oauth/userinfo keeps its own Bearer error="invalid_token" header. It is a different endpoint and the issue didn't name it.
  • I didn't add an error_description.
  • A bearer that is present but malformed counts as "sent" only if it matches Bearer <something>.
  • A token that authenticates but has too little scope gets a 403, not this 401, so it is unchanged.

Checks

  • test/oauth-challenge.test.ts, test/oauth-discovery.test.ts, test/mcp-size.test.ts, test/oauth-protected-resource.test.ts, test/report-discovery.test.ts, test/mcp-discovery-walk.test.ts, test/mcp.test.ts and test/branches-action.test.ts pass, and tsc is clean.
  • I did not run the full suite; the forge's check runs it on the push.
  • scripts/gate.ts is not in this clone.
  • test/origins-security-push.test.ts also mentions www-authenticate and I did not run it.

To try it: curl -si -XPOST $URL/mcp -d '{}' shows the plain challenge. Adding -H 'authorization: Bearer nope' adds error="invalid_token".

Approved

Clean and small. One function builds the 401 challenge for /mcp and the API. It adds scope="ro rw" and adds error="invalid_token" only when a Bearer header was sent. Tests cover no token, a garbage token and an expired token on both doors, and the old discovery pin is updated. No new input, write or dependency. A revoked token is not tested separately, but it takes the same path as an expired one. I could not confirm the header on the preview: my curl there printed no www-authenticate line, so the preview behaviour rests on the tests.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs