Skip to content

OAuth 2.1 makeover: PKCE-only, hashed tokens, real consent with scoped permissions - #68

Merged
nick-transition merged 1 commit into
mainfrom
oauth-2.1-makeover
Jul 8, 2026
Merged

OAuth 2.1 makeover: PKCE-only, hashed tokens, real consent with scoped permissions#68
nick-transition merged 1 commit into
mainfrom
oauth-2.1-makeover

Conversation

@nick-transition

Copy link
Copy Markdown
Owner

Summary

Replaces the secret-based OAuth connector flow with a spec-compliant OAuth 2.1 authorization server for MCP clients (Claude.ai / Claude Desktop):

  • No secrets anywhere: public clients + mandatory PKCE (S256); client secrets deleted, the client-side secret-provisioning screen removed, static API-key fallback removed
  • No credentials at rest: auth codes / access tokens / refresh tokens stored as SHA-256 digests only — a Firestore dump contains no usable bearer credentials
  • Real token lifecycle: 1-hour access tokens + rotating single-use refresh tokens (was 90-day static tokens stored verbatim)
  • Real permissions: consent page lists each requested privilege as a user-toggleable checkbox with a Deny option; tokens carry exactly the approved scopes; no default-to-full-access, no claudeai scope escalation; every MCP tool enforces its scope
  • Zero-config connection: discovery metadata (RFC 8414/9728) + dynamic client registration (RFC 7591) + WWW-Authenticate on 401 — users paste only the MCP URL
  • Connected Apps screen in Flutter: shows grants with exact permissions, revoke kills all tokens for the grant
  • Firestore rules lock all OAuth collections to Cloud Functions; users can read (not write) their own grant records

Breaking change

Existing static API keys and legacy plaintext OAuth tokens no longer authenticate. Users reconnect once via the new flow. Deploy needs both --only functions and --only firestore:rules.

Test plan

  • functions/test-oauth-flow.mjs: 36-check emulator e2e — discovery, registration, consent page content, PKCE enforcement (missing/wrong verifier), single-use codes, scoped token issuance, hashed-at-rest verification, real MCP client connection with scope enforcement, refresh rotation + replay rejection, grant revocation killing live tokens (all passing)
  • flutter analyze clean on changed files
  • tsc build clean

🤖 Generated with Claude Code

…h scoped permissions

Replaces the secret-based OAuth connector flow with a spec-compliant
OAuth 2.1 authorization server for MCP clients:

- Discovery metadata (RFC 8414/9728) + dynamic client registration
  (RFC 7591): clients need only the MCP URL, no credentials to copy
- Public clients with mandatory PKCE S256; client secrets removed
- Auth codes, access tokens, and refresh tokens stored as SHA-256
  digests only; 1h access tokens with rotating single-use refresh
  tokens (was: 90-day static tokens stored verbatim)
- Consent page lists each requested privilege as a user-toggleable
  checkbox with a Deny option; tokens carry exactly what was approved
- Strict scopes: no default-to-full-access, no claudeai escalation,
  static API-key fallback removed
- 401s advertise resource metadata via WWW-Authenticate so MCP
  clients bootstrap the flow automatically
- Flutter: Connected Apps screen (grants + revoke) replaces the
  client-secret / static-token copy screen
- Firestore rules lock OAuth collections to Cloud Functions; users
  can read their own grant records
- functions/test-oauth-flow.mjs: 36-check emulator e2e of the full
  flow (discovery → register → consent → PKCE exchange → scoped MCP
  calls → refresh rotation → revocation)

Breaking: existing static API keys and legacy plaintext tokens no
longer authenticate; users reconnect via the OAuth flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@nick-transition
nick-transition merged commit d1c9682 into main Jul 8, 2026
2 checks passed
@nick-transition
nick-transition deleted the oauth-2.1-makeover branch July 8, 2026 23:54
nick-transition added a commit that referenced this pull request Jul 9, 2026
The deploy workflow only shipped hosting and functions, so rules
changes (like the OAuth 2.1 lockdown in #68) never reached prod.
firebase deploy --only firestore covers rules + indexes per
firebase.json.

Co-authored-by: Nick Stoddart <nickstoddart@Nicks-MacBook-Pro.local>
Co-authored-by: Claude Fable 5 <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