@cloudflare/workers-oauth-provider 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -393,11 +393,11 @@ new OAuthProvider({
393
393
 
394
394
  Setup:
395
395
 
396
- 1. Configure your IdP as an ID-JAG issuer with this worker's origin as the resource and a public JWKS endpoint.
396
+ 1. Configure your IdP as an ID-JAG issuer with a public JWKS endpoint. If it includes the optional `resource` claim, configure it with the MCP endpoint URL.
397
397
  2. Set `resourceMetadata.resource` to the MCP endpoint URL (required when EMA is enabled).
398
398
  3. Implement `trustedIssuers` as a resolver — for multi-tenant deployments it can read `env` / `clientInfo` to look up per-tenant IdP config without redeploying.
399
399
 
400
- The AS enforces `resolved.issuer === iss` (confused-deputy guard) and validates ID-JAG `typ`, signature, audience, client binding, resource, `exp` / `iat` / `nbf`, max lifetime, and `jti` replay. Refresh tokens are not issued for this grant — the ID-JAG itself is the renewable assertion.
400
+ The AS enforces `resolved.issuer === iss` (confused-deputy guard) and validates ID-JAG `typ`, signature, audience, client binding, any supplied resource, `exp` / `iat` / `nbf`, max lifetime, and `jti` replay. When the optional `resource` claim is omitted, the AS uses `resourceMetadata.resource`, so issued tokens remain pinned to the configured MCP resource. Refresh tokens are not issued for this grant — the ID-JAG itself is the renewable assertion.
401
401
 
402
402
  ### Public clients
403
403
 
@@ -410,7 +410,7 @@ enterpriseManagedAuthorization: {
410
410
  }
411
411
  ```
412
412
 
413
- This is useful for clients registered via a [Client ID Metadata Document (CIMD)](https://modelcontextprotocol.io/), which are always public and therefore cannot present a client secret. With this enabled, trust rests on the IdP-issued, signature-verified, short-lived, single-use ID-JAG assertion (audience-, resource-, and client-bound) rather than on a separately presented client secret. Leave it unset (default `false`) to keep the spec-default behavior of requiring client authentication.
413
+ This is useful for clients registered via a [Client ID Metadata Document (CIMD)](https://modelcontextprotocol.io/), which are always public and therefore cannot present a client secret. With this enabled, trust rests on the IdP-issued, signature-verified, short-lived, single-use ID-JAG assertion (audience- and client-bound), together with the provider's configured resource pinning, rather than on a separately presented client secret. Leave it unset (default `false`) to keep the spec-default behavior of requiring client authentication.
414
414
 
415
415
  Experimental — the MCP extension is still a draft.
416
416
 
@@ -102,7 +102,11 @@ interface EmaIdJagClaims {
102
102
  sub: string;
103
103
  /** Authorization server issuer URL or URLs for which this assertion is intended. */
104
104
  aud: string | string[];
105
- /** RFC 9728 resource identifier of the MCP server. */
105
+ /**
106
+ * Effective RFC 9728 resource identifier of the MCP server. When the ID-JAG
107
+ * omits its optional `resource` claim, the provider supplies its configured
108
+ * `resourceMetadata.resource` value.
109
+ */
106
110
  resource: string;
107
111
  /** OAuth client identifier this assertion was issued to. */
108
112
  client_id: string;
@@ -140,7 +144,10 @@ interface EmaClaimsMapperInput<Env = Cloudflare.Env> {
140
144
  claims: EmaIdJagClaims;
141
145
  /** Authenticated OAuth client that presented the assertion. */
142
146
  clientInfo: ClientInfo;
143
- /** Validated MCP resource identifier from the assertion. */
147
+ /**
148
+ * Effective MCP resource identifier. Taken from the assertion when present,
149
+ * otherwise from the provider's configured `resourceMetadata.resource`.
150
+ */
144
151
  resource: string;
145
152
  /** Requested scopes after downscoping to the assertion's scope claim, if present. */
146
153
  requestedScope: string[];
@@ -281,8 +288,9 @@ interface EmaOptions<Env = Cloudflare.Env> {
281
288
  * always public (`none`) and therefore cannot present a client secret. The
282
289
  * security trade-off is documented in the README: the trust then rests on
283
290
  * the IdP-issued, signature-verified, short-lived, single-use ID-JAG
284
- * assertion (audience-, resource-, and client-bound) rather than on a
285
- * separately presented client secret.
291
+ * assertion (audience- and client-bound), together with the provider's
292
+ * configured resource pinning, rather than on a separately presented client
293
+ * secret.
286
294
  */
287
295
  allowPublicClients?: boolean;
288
296
  }
@@ -469,13 +469,14 @@ function isWellFormedTrustedIssuer(issuer) {
469
469
  return true;
470
470
  }
471
471
  /**
472
- * Validate every required ID-JAG claim and produce a typed `ValidatedIdJag`.
472
+ * Validate the ID-JAG claims and produce a typed `ValidatedIdJag`.
473
473
  *
474
474
  * Enforces (in order):
475
- * - presence + type of `iss`, `sub`, `aud`, `resource`, `client_id`, `jti`, `exp`, `iat`
475
+ * - presence + type of `iss`, `sub`, `aud`, `client_id`, `jti`, `exp`, `iat`
476
476
  * - `aud` contains the AS's expected audience
477
477
  * - `client_id` matches the authenticated client
478
- * - `resource` is a valid RFC 8707 URI and matches the AS's configured resource
478
+ * - optional `resource` is a valid RFC 8707 URI and matches the AS's configured resource
479
+ * - the configured resource is used when `resource` is omitted
479
480
  * - `exp` is in the future
480
481
  * - `iat` is not more than `clockSkewSeconds` in the future
481
482
  * - `nbf` (if present) is ≤ `now + clockSkewSeconds`
@@ -495,7 +496,7 @@ function validateIdJagClaims(input) {
495
496
  if (!sub.ok) return sub;
496
497
  const aud = readAudienceClaim(rawClaims);
497
498
  if (!aud.ok) return aud;
498
- const resource = readRequiredString(rawClaims, "resource");
499
+ const resource = rawClaims.resource === void 0 ? ok(configuredResource) : readRequiredString(rawClaims, "resource");
499
500
  if (!resource.ok) return resource;
500
501
  const claimClientId = readRequiredString(rawClaims, "client_id");
501
502
  if (!claimClientId.ok) return claimClientId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/workers-oauth-provider",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "OAuth provider for Cloudflare Workers",
5
5
  "main": "dist/oauth-provider.js",
6
6
  "types": "dist/oauth-provider.d.ts",