Skip to content

fix: make CIMD explicitly opt-in via clientIdMetadataDocumentEnabled#158

Merged
mattzcarey merged 9 commits intocloudflare:mainfrom
mattzcarey:fix/cimd-explicit-optin
Mar 4, 2026
Merged

fix: make CIMD explicitly opt-in via clientIdMetadataDocumentEnabled#158
mattzcarey merged 9 commits intocloudflare:mainfrom
mattzcarey:fix/cimd-explicit-optin

Conversation

@mattzcarey
Copy link
Contributor

@mattzcarey mattzcarey commented Feb 27, 2026

Summary

Fixes #156

Three changes to make CIMD (Client ID Metadata Document) robust and explicitly opt-in:

1. Explicit opt-in via clientIdMetadataDocumentEnabled

CIMD previously auto-enabled whenever the global_fetch_strictly_public compatibility flag was present. This caused 500 crashes for servers where URL-shaped client_ids (e.g. https://claude.ai/oauth/mcp-oauth-client-metadata) hit bot-protected endpoints, since there was no way to disable CIMD independently of the compat flag.

  • Added clientIdMetadataDocumentEnabled?: boolean to OAuthProviderOptions (defaults to false)
  • When not enabled (default): URL-formatted client_ids fall through to standard KV lookup — no fetch, no crash
  • When enabled without compat flag: throws a clear error telling you to add global_fetch_strictly_public
  • When enabled with compat flag: CIMD works as before
  • Metadata endpoint reports client_id_metadata_document_supported: true only when both the option and compat flag are set

2. Graceful CIMD fetch failure handling

When CIMD is enabled and the metadata fetch fails (size limit exceeded, timeout, HTTP error, invalid JSON, network error, metadata validation failure), getClient() now catches the error and returns null instead of throwing. This means:

  • Callers see "Invalid client" instead of an unhandled 500 error
  • The token endpoint returns a proper invalid_client OAuth error response (HTTP 401), which the typescript-sdk client can catch and retry with DCR
  • No more production crashes from bot-protected endpoints

3. Structured error logging

CIMD fetch failures are logged via console.warn with the client URL and error message, so operators can debug issues without the error propagating as a 500:

CIMD fetch failed for https://client.example.com/metadata.json: Failed to fetch client metadata: HTTP 404

Usage

new OAuthProvider({
  clientIdMetadataDocumentEnabled: true, // opt-in to CIMD
  // ...
})

Source changes (src/oauth-provider.ts)

  • Added clientIdMetadataDocumentEnabled option to OAuthProviderOptions
  • getClient(): new CIMD opt-in gate → KV fallback when disabled; try/catch with console.warn when enabled
  • Metadata endpoint: client_id_metadata_document_supported now requires both option AND compat flag

Test plan

  • All 251 tests pass (5 new test groups added)
  • Explicit opt-in tests: default falls through to KV, metadata reports false, enabled+flag works, enabled-no-flag throws
  • Error logging tests: verifies console.warn is called with client URL and error details for HTTP failures, timeouts, size limits, and validation failures
  • Token endpoint test: CIMD failure returns proper invalid_client OAuth error (401)
  • Existing CIMD success tests updated to use clientIdMetadataDocumentEnabled: true
  • Existing failure tests updated to expect "Invalid client" instead of specific internal errors
  • Typecheck passes
  • Prettier passes
  • Deployed to staging (staging.mcp.cloudflare.com) and tested with Claude.ai as client — both CIMD disabled and enabled modes work correctly

@changeset-bot
Copy link

changeset-bot bot commented Feb 27, 2026

🦋 Changeset detected

Latest commit: 97ae17c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/workers-oauth-provider Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/workers-oauth-provider/@cloudflare/workers-oauth-provider@158

commit: 97ae17c

…tion

CIMD previously auto-enabled when the global_fetch_strictly_public compat
flag was present, causing crashes for servers where URL-shaped client_ids
hit bot-protected endpoints. This makes CIMD explicitly opt-in and falls
through to standard KV lookup when disabled.

Closes cloudflare#156
@mattzcarey mattzcarey force-pushed the fix/cimd-explicit-optin branch from 90413de to 9c71716 Compare February 27, 2026 17:29
mattzcarey and others added 6 commits February 27, 2026 17:29
When CIMD fetch fails (size limit, timeout, HTTP error, invalid metadata),
return null from getClient() so callers treat it as "Invalid client"
instead of propagating an unhandled error as a 500. This allows clients
like the typescript-sdk to detect the failure and retry with DCR.
When CIMD fetch fails, log a structured warning with host and error
category (timeout, network, http_4xx, http_5xx, size_limit, json_parse,
metadata_validation) for operator debugging. Log message intentionally
excludes error.message to prevent URL leakage.

Adds 10 new tests: error logging per category, log sanitization,
and token endpoint CIMD failure returning proper invalid_client.
Remove brittle error categorization (string matching). Just log the
client URL and error message via console.warn for operator debugging.
@mattzcarey
Copy link
Contributor Author

Tested on staging

Deployed to staging.mcp.cloudflare.com (cloudflare/mcp) and verified:

  1. CIMD disabled (default): URL-shaped client_ids (e.g. https://claude.ai/oauth/mcp-oauth-client-metadata) no longer crash the server — they fall through to KV lookup gracefully.
  2. CIMD enabled (clientIdMetadataDocumentEnabled: true): CIMD works as expected with the compat flag. Failures are logged via console.warn and return "Invalid client" instead of 500.
  3. Typecheck passes with the new option in cloudflare/mcp.

Tested with Claude.ai as the client.

@mattzcarey mattzcarey merged commit b26f7ff into cloudflare:main Mar 4, 2026
4 checks passed
@github-actions github-actions bot mentioned this pull request Mar 4, 2026
mattzcarey added a commit to mattzcarey/workers-oauth-provider that referenced this pull request Mar 4, 2026
Add documentation for undocumented options: accessTokenTTL,
allowTokenExchangeGrant, clientIdMetadataDocumentEnabled, and
resourceMetadata. Rewrite CIMD section to reflect the new opt-in
flag added in cloudflare#158.
mattzcarey added a commit that referenced this pull request Mar 4, 2026
## Summary

- Add `accessTokenTTL`, `allowTokenExchangeGrant`, and
`clientIdMetadataDocumentEnabled` to the main config example
- Add new "Protected Resource Metadata (RFC 9728)" section documenting
the `resourceMetadata` option
- Rewrite CIMD section to reflect the opt-in flag added in #158
(two-step enablement: option + compat flag)

## Test plan

- [x] `npm run check` passes (typecheck + 279 tests)
- [ ] Review rendered README on GitHub for formatting
mattzcarey pushed a commit that referenced this pull request Mar 4, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @cloudflare/workers-oauth-provider@0.3.0

### Minor Changes

- [#158](#158)
[`b26f7ff`](b26f7ff)
Thanks [@mattzcarey](https://github.com/mattzcarey)! - Add
`clientIdMetadataDocumentEnabled` option to make CIMD (Client ID
Metadata Document) support explicitly opt-in. Previously, CIMD
auto-enabled when the `global_fetch_strictly_public` compatibility flag
was present, which could cause crashes for servers where URL-shaped
client_ids hit bot-protected endpoints. When not enabled (the default),
URL-formatted client_ids now fall through to standard KV lookup instead
of throwing.

- [#144](#144)
[`49a1d24`](49a1d24)
Thanks [@mattzcarey](https://github.com/mattzcarey)! - Add
`revokeExistingGrants` option to `completeAuthorization()` that revokes
existing grants for the same user+client after creating a new one.
Defaults to `true`, fixing infinite re-auth loops when props change
between authorizations (issue #34). Set to `false` to allow multiple
concurrent grants per user+client.

Revoke tokens and grant when an authorization code is reused, per RFC
6749 §10.5. This prevents authorization code replay attacks by
invalidating all tokens issued from the first exchange.

**Breaking behavior change:** Previously, re-authorizing the same
user+client created an additional grant, leaving old tokens valid. Now,
old grants are revoked by default. If your application relies on
multiple concurrent grants per user+client, set `revokeExistingGrants:
false` to preserve the old behavior.

### Patch Changes

- [#164](#164)
[`4b640a3`](4b640a3)
Thanks [@pnguyen-atlassian](https://github.com/pnguyen-atlassian)! -
Include `client_secret_expires_at` and `client_secret_issued_at` in
dynamic client registration responses when a `client_secret` is issued,
per RFC 7591 §3.2.1.

- [#165](#165)
[`9cce070`](9cce070)
Thanks [@mattzcarey](https://github.com/mattzcarey)! - Use
`Promise.allSettled` instead of `Promise.all` for best-effort grant
revocation in `completeAuthorization()`, ensuring all grants are
attempted even if one fails.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.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.

CIMD should fall back to DCR on failure, and be explicit opt-in

2 participants