Skip to content

refactor(auth): delegate credentials to OAuth provider - #185

Merged
mattzcarey merged 9 commits into
mainfrom
feat/external-api-token-resolution
Aug 4, 2026
Merged

refactor(auth): delegate credentials to OAuth provider#185
mattzcarey merged 9 commits into
mainfrom
feat/external-api-token-resolution

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Move direct Cloudflare bearer credentials behind workers-oauth-provider's resolveExternalToken hook and remove the obsolete refresh-concurrency guard.

The provider now owns bearer parsing, internal MCP access-token lookup, request dispatch, no-store error responses, and RFC 6750/9728 challenges. Our resolver runs only when the provider does not find one of its own tokens.

Direct credential behavior

The resolver uses the credential prefix as an ownership hint:

  • cfat_: query one minimum-size /accounts?per_page=5 page and require exactly one account
  • cfut_: query /user and /accounts in parallel and require a user
  • cfoat_: same user-owned path, including Wrangler OAuth credentials
  • unprefixed legacy credentials: retain response-based fallback

The resolver produces a Zod-validated discriminated identity (account or user) before building request props. Expected validation failures become provider ExternalTokenError responses with appropriate status, challenge, scope, and retry metadata.

The token-hash identity cache remains intentionally: resolveExternalToken runs on every direct-credential MCP request and the cache prevents repeated /user and /accounts probes. Provider-issued MCP tokens resolve internally and never invoke this cache.

Refresh concurrency cleanup

Cloudflare's upstream OAuth server now provides an idempotent refresh-token concurrency window. A production probe used an existing local MCP credential and its matching production grant to send 10 simultaneous refreshes with the same upstream refresh token:

  • 10/10 returned HTTP 200
  • all completed in about 1.45 seconds
  • all returned the same replacement refresh token
  • the returned access token passed /user validation
  • the production grant was updated with the replacement credentials and then refreshed normally through the production /token endpoint

This makes the old upstream-token guard redundant. Removed its token hashing, failure cache, collision telemetry, and promise-sharing behavior.

The provider still rotates a distinct downstream refresh token per successful concurrent request. A staging control proved this is pre-existing on origin/main: 20 concurrent refreshes returned 20 distinct tokens, but only one remained usable. This PR therefore adds a best-effort per-grant admission gate using the existing OAUTH_KV binding:

  • isolate-local competitors are rejected deterministically;
  • an owner-verified KV claim reduces cross-isolate races;
  • successful callbacks leave a short 10-second tombstone covering the concurrent request burst;
  • competitors receive structured 429 temporarily_unavailable with Retry-After;
  • transient upstream failures release the claim;
  • a genuine upstream invalid_grant still revokes the exact downstream grant.

KV is eventually consistent, so this is deliberately documented as race reduction rather than a linearizable mutex. On staging, 100 concurrent refreshes produced exactly one usable 200 response and 99 structured 429 responses; the tombstone rejected an immediate retry and allowed a new refresh after 11 seconds. The shorter window preserves most of Cloudflare OAuth's 90-second upstream retry grace if provider persistence fails after the callback succeeds.

Provider

Upgrade @cloudflare/workers-oauth-provider from 0.9.0 to the latest release, 0.9.1.

Validation

  • live production upstream OAuth concurrency probe: 10/10 successful, one idempotent replacement
  • normal production downstream refresh: HTTP 200; local credential updated
  • authenticated production MCP request: HTTP 200
  • npm run check: 19 files, 247 tests
  • npx wrangler deploy --dry-run --env production
  • npm audit --omit=dev: one existing moderate Hono advisory; unrelated to this PR

@mattzcarey
mattzcarey force-pushed the feat/external-api-token-resolution branch from 9fdfeb9 to 6f23438 Compare July 30, 2026 23:37
@mattzcarey
mattzcarey marked this pull request as ready for review July 31, 2026 08:37
@mattzcarey
mattzcarey force-pushed the feat/external-api-token-resolution branch from 6f23438 to 0507b03 Compare July 31, 2026 13:27
@mattzcarey mattzcarey changed the title refactor(auth): resolve Cloudflare credentials through OAuth provider refactor(auth): delegate credentials to OAuth provider Aug 4, 2026
@mattzcarey
mattzcarey force-pushed the feat/external-api-token-resolution branch from 0507b03 to 4f1f5d1 Compare August 4, 2026 10:47
@mattzcarey
mattzcarey merged commit 3487872 into main Aug 4, 2026
5 checks passed
@mattzcarey
mattzcarey deleted the feat/external-api-token-resolution branch August 4, 2026 15:59
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