Skip to content

fix(worker): require PKCE and bind consent and login to the browser - #21

Merged
pentarim merged 1 commit into
mainfrom
fix/oauth-consent-hardening
Oct 1, 2026
Merged

pentarim merged 1 commit into
mainfrom
fix/oauth-consent-hardening

Conversation

@pentarim

@pentarim pentarim commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

MCP clients register themselves, and Auth0 skips its own consent for the one upstream application, so the Worker's consent screen is the only place a person agrees to a particular client. This makes that screen hold.

  • /authorize requires code_challenge with S256. plain is no longer advertised or accepted.
  • The pending authorization request is kept in KV under a random single-use token. The consent form carries the token only.
  • A consent post counts only with the matching consent cookie, and /callback only with the matching login cookie. Each cookie is named after its own token, so parallel sign-ins do not collide.
  • The consent page shows the redirect URI, says the client name is unverified, and may not be framed.
  • The approval cookie is renamed, so earlier approvals are void and each browser is asked once more.
  • Invalid authorize requests answer 400 instead of 500.

Open client registration is unchanged: the MCP authorization spec expects it.

Notes

  • Cookies use the __Host- prefix over HTTPS and the bare name over HTTP, because browsers refuse the prefix there and wrangler dev serves HTTP. Production should have Always Use HTTPS on for the zone.
  • Already deployed to mcp.tribeunal.com (version 24c24cb6), ahead of this PR, because the repository is public.

Testing

  • npm run test:unit: 259 pass, 21 of them new in tests/worker-oauth-consent.test.ts.
  • npx tsc --noEmit in worker/ is clean.
  • Checked on local wrangler dev, in a real browser up to the Auth0 page, and against production after the deploy.
  • Not yet run: a full sign-in through Auth0 from a real MCP client.

🤖 Generated with Claude Code

MCP clients register themselves and Auth0 skips its own consent for the
one upstream application, so the Worker's consent screen is the only
place a person agrees to a particular client. It was not holding that
line: the request being approved travelled through the form, an
approval was accepted from anywhere, and a login could finish in a
browser that did not start it. /authorize also accepted requests with
no PKCE challenge.

- /authorize requires code_challenge with S256; plain is no longer
  advertised or accepted.
- The pending request is kept in KV under a random single-use token.
  The form carries the token only.
- A consent post counts only with the matching consent cookie, and
  /callback only with the matching login cookie. Each cookie is named
  after its own token so parallel sign-ins do not collide.
- The consent page shows the redirect URI, says the client name is
  unverified, and may not be framed.
- The approval cookie is renamed, so earlier approvals are void.
- Invalid authorize requests answer 400 instead of 500.

Cookies use the __Host- prefix over HTTPS and the bare name over HTTP,
because browsers refuse the prefix there and wrangler dev serves HTTP.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@pentarim
pentarim merged commit f2ccad6 into main Oct 1, 2026
2 checks passed
pentarim added a commit that referenced this pull request Oct 1, 2026
Brings in the remote-server sign-in hardening from #21 so a Worker deploy
from this branch does not revert it. One conflict, in the worker/README.md
file table: kept this branch's 46-tool row and main's two rewritten rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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