@cloudflare/workers-oauth-provider 0.3.1 → 0.3.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.
@@ -1,6 +1,7 @@
1
1
  import { WorkerEntrypoint } from "cloudflare:workers";
2
2
 
3
3
  //#region src/oauth-provider.ts
4
+ const PROTECTED_RESOURCE_WELL_KNOWN_PREFIX = "/.well-known/oauth-protected-resource";
4
5
  if (!(typeof Cloudflare !== "undefined" && Cloudflare.compatibilityFlags?.global_fetch_strictly_public === true)) console.warn("CIMD (Client ID Metadata Document) is disabled: add '\"compatibility_flags\": [\"global_fetch_strictly_public\"]' to your wrangler.jsonc to enable. See: https://developers.cloudflare.com/workers/configuration/compatibility-flags/#global-fetch-strictly-public");
5
6
  /**
6
7
  * Enum representing the type of handler (ExportedHandler or WorkerEntrypoint)
@@ -141,7 +142,7 @@ var OAuthProviderImpl = class OAuthProviderImpl {
141
142
  async fetch(request, env, ctx) {
142
143
  const url = new URL(request.url);
143
144
  if (request.method === "OPTIONS") {
144
- if (this.isApiRequest(url) || url.pathname === "/.well-known/oauth-authorization-server" || url.pathname === "/.well-known/oauth-protected-resource" || this.isTokenEndpoint(url) || this.options.clientRegistrationEndpoint && this.isClientRegistrationEndpoint(url)) return this.addCorsHeaders(new Response(null, {
145
+ if (this.isApiRequest(url) || url.pathname === "/.well-known/oauth-authorization-server" || this.isProtectedResourceMetadataRequest(url) || this.isTokenEndpoint(url) || this.options.clientRegistrationEndpoint && this.isClientRegistrationEndpoint(url)) return this.addCorsHeaders(new Response(null, {
145
146
  status: 204,
146
147
  headers: { "Content-Length": "0" }
147
148
  }), request);
@@ -150,7 +151,7 @@ var OAuthProviderImpl = class OAuthProviderImpl {
150
151
  const response = await this.handleMetadataDiscovery(url);
151
152
  return this.addCorsHeaders(response, request);
152
153
  }
153
- if (url.pathname === "/.well-known/oauth-protected-resource") {
154
+ if (this.isProtectedResourceMetadataRequest(url)) {
154
155
  const response = this.handleProtectedResourceMetadata(url);
155
156
  return this.addCorsHeaders(response, request);
156
157
  }
@@ -245,6 +246,27 @@ var OAuthProviderImpl = class OAuthProviderImpl {
245
246
  return this.matchEndpoint(url, this.options.clientRegistrationEndpoint);
246
247
  }
247
248
  /**
249
+ * Checks if a URL is a request for OAuth Protected Resource Metadata (RFC 9728).
250
+ * Matches both the root well-known path and path-suffixed variants per RFC 9728 §3.1.
251
+ */
252
+ isProtectedResourceMetadataRequest(url) {
253
+ return url.pathname === PROTECTED_RESOURCE_WELL_KNOWN_PREFIX || url.pathname.startsWith(PROTECTED_RESOURCE_WELL_KNOWN_PREFIX + "/");
254
+ }
255
+ /**
256
+ * Derives the resource identifier from a protected resource metadata well-known URL.
257
+ * Per RFC 9728 §3.1, the well-known URI is inserted after the authority and before the path,
258
+ * so the resource identifier is reconstructed by removing the well-known prefix.
259
+ *
260
+ * Examples:
261
+ * /.well-known/oauth-protected-resource → origin (e.g. https://example.com)
262
+ * /.well-known/oauth-protected-resource/mcp → origin + /mcp (e.g. https://example.com/mcp)
263
+ */
264
+ deriveResourceIdentifier(requestUrl) {
265
+ const suffix = requestUrl.pathname.slice(37);
266
+ if (!suffix || suffix === "/") return requestUrl.origin;
267
+ return `${requestUrl.origin}${suffix}`;
268
+ }
269
+ /**
248
270
  * Parses and validates a token endpoint request (used for both token exchange and revocation)
249
271
  * @param request - The HTTP request to parse
250
272
  * @returns Promise with parsed body and client info, or error response
@@ -389,7 +411,7 @@ var OAuthProviderImpl = class OAuthProviderImpl {
389
411
  const tokenEndpointUrl = this.getFullEndpointUrl(this.options.tokenEndpoint, requestUrl);
390
412
  const authServerOrigin = new URL(tokenEndpointUrl).origin;
391
413
  const metadata = {
392
- resource: rm?.resource ?? requestUrl.origin,
414
+ resource: rm?.resource ?? this.deriveResourceIdentifier(requestUrl),
393
415
  authorization_servers: rm?.authorization_servers ?? [authServerOrigin],
394
416
  scopes_supported: rm?.scopes_supported ?? this.options.scopesSupported,
395
417
  bearer_methods_supported: rm?.bearer_methods_supported ?? ["header"]
@@ -956,7 +978,7 @@ var OAuthProviderImpl = class OAuthProviderImpl {
956
978
  */
957
979
  async handleApiRequest(request, env, ctx) {
958
980
  const url = new URL(request.url);
959
- const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
981
+ const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource${url.pathname}`;
960
982
  const authHeader = request.headers.get("Authorization");
961
983
  if (!authHeader || !authHeader.startsWith("Bearer ")) return this.createErrorResponse("invalid_token", "Missing or invalid access token", 401, { "WWW-Authenticate": this.buildWwwAuthenticateHeader(resourceMetadataUrl, "invalid_token", "Missing or invalid access token") });
962
984
  const accessToken = authHeader.substring(7);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/workers-oauth-provider",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "OAuth provider for Cloudflare Workers",
5
5
  "main": "dist/oauth-provider.js",
6
6
  "types": "dist/oauth-provider.d.ts",