@cloudflare/workers-oauth-provider 0.2.3 → 0.2.4

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
@@ -81,6 +81,12 @@ export default new OAuthProvider({
81
81
  // Defaults to false.
82
82
  allowImplicitFlow: false,
83
83
 
84
+ // Optional: Controls whether the plain PKCE code_challenge_method is allowed.
85
+ // OAuth 2.1 recommends using S256 exclusively as plain offers no cryptographic protection.
86
+ // When false, only S256 is accepted and advertised in the metadata endpoint.
87
+ // Defaults to true for backward compatibility.
88
+ allowPlainPKCE: true,
89
+
84
90
  // Optional: Controls whether public clients (clients without a secret, like SPAs)
85
91
  // can register via the dynamic client registration endpoint.
86
92
  // When true, only confidential clients can register.
@@ -304,6 +310,18 @@ new OAuthProvider({
304
310
 
305
311
  By default, the `onError` callback is set to ``({ status, code, description }) => console.warn(`OAuth error response: ${status} ${code} - ${description}`)``.
306
312
 
313
+ ## Standards Compliance
314
+
315
+ This library implements the following OAuth and MCP specifications:
316
+
317
+ - [OAuth 2.1 (draft-ietf-oauth-v2-1-13)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13) — Core authorization framework with PKCE
318
+ - [OAuth 2.0 Authorization Server Metadata (RFC 8414)](https://datatracker.ietf.org/doc/html/rfc8414) — `/.well-known/oauth-authorization-server` discovery endpoint
319
+ - [OAuth 2.0 Protected Resource Metadata (RFC 9728)](https://datatracker.ietf.org/doc/html/rfc9728) — `/.well-known/oauth-protected-resource` discovery endpoint
320
+ - [OAuth 2.0 Dynamic Client Registration (RFC 7591)](https://datatracker.ietf.org/doc/html/rfc7591) — Dynamic client registration endpoint
321
+ - [OAuth Client ID Metadata Documents (draft-ietf-oauth-client-id-metadata-document)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document) — HTTPS URLs as client IDs
322
+
323
+ These are the specifications required by the [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization).
324
+
307
325
  ## Implementation Notes
308
326
 
309
327
  ### End-to-end encryption
@@ -13,8 +13,10 @@ declare enum GrantType {
13
13
  /**
14
14
  * Aliases for either type of Handler that makes .fetch required
15
15
  */
16
- type ExportedHandlerWithFetch = ExportedHandler & Pick<Required<ExportedHandler>, 'fetch'>;
17
- type WorkerEntrypointWithFetch = WorkerEntrypoint & Pick<Required<WorkerEntrypoint>, 'fetch'>;
16
+ type ExportedHandlerWithFetch<Env = Cloudflare.Env> = ExportedHandler<Env> & Pick<Required<ExportedHandler<Env>>, 'fetch'>;
17
+ type WorkerEntrypointWithFetch<Env = Cloudflare.Env> = WorkerEntrypoint<Env> & {
18
+ fetch: NonNullable<WorkerEntrypoint['fetch']>;
19
+ };
18
20
  /**
19
21
  * Configuration options for the OAuth Provider
20
22
  */
@@ -119,7 +121,7 @@ interface ResolveExternalTokenResult {
119
121
  */
120
122
  audience?: string | string[];
121
123
  }
122
- interface OAuthProviderOptions {
124
+ interface OAuthProviderOptions<Env = Cloudflare.Env> {
123
125
  /**
124
126
  * URL(s) for API routes. Requests with URLs starting with any of these prefixes
125
127
  * will be treated as API requests and require a valid access token.
@@ -137,7 +139,7 @@ interface OAuthProviderOptions {
137
139
  * Used with `apiRoute` for the single-handler configuration. This is incompatible with
138
140
  * the `apiHandlers` property. You must use either `apiRoute` + `apiHandler` OR `apiHandlers`, not both.
139
141
  */
140
- apiHandler?: ExportedHandlerWithFetch | (new (ctx: ExecutionContext, env: any) => WorkerEntrypointWithFetch);
142
+ apiHandler?: ExportedHandlerWithFetch<Env> | (new (ctx: ExecutionContext, env: Env) => WorkerEntrypointWithFetch<Env>);
141
143
  /**
142
144
  * Map of API routes to their corresponding handlers for the multi-handler configuration.
143
145
  * The keys are the API routes (strings only, not arrays), and the values are the handlers.
@@ -148,12 +150,12 @@ interface OAuthProviderOptions {
148
150
  * `apiRoute` + `apiHandler` (single-handler configuration) OR `apiHandlers` (multi-handler
149
151
  * configuration), not both.
150
152
  */
151
- apiHandlers?: Record<string, ExportedHandlerWithFetch | (new (ctx: ExecutionContext, env: any) => WorkerEntrypointWithFetch)>;
153
+ apiHandlers?: Record<string, ExportedHandlerWithFetch<Env> | (new (ctx: ExecutionContext, env: Env) => WorkerEntrypointWithFetch<Env>)>;
152
154
  /**
153
155
  * Handler for all non-API requests or API requests without a valid token.
154
156
  * Can be either an ExportedHandler object with a fetch method or a class extending WorkerEntrypoint.
155
157
  */
156
- defaultHandler: ExportedHandler | (new (ctx: ExecutionContext, env: any) => WorkerEntrypointWithFetch);
158
+ defaultHandler: ExportedHandler<Env> | (new (ctx: ExecutionContext, env: Env) => WorkerEntrypointWithFetch<Env>);
157
159
  /**
158
160
  * URL of the OAuth authorization endpoint where users can grant permissions.
159
161
  * This URL is used in OAuth metadata and is not handled by the provider itself.
@@ -191,6 +193,13 @@ interface OAuthProviderOptions {
191
193
  * Defaults to false.
192
194
  */
193
195
  allowImplicitFlow?: boolean;
196
+ /**
197
+ * Controls whether the plain PKCE method is allowed.
198
+ * OAuth 2.1 recommends using S256 exclusively as plain offers no cryptographic protection.
199
+ * When set to false, only the S256 code_challenge_method will be accepted.
200
+ * Defaults to true for backward compatibility.
201
+ */
202
+ allowPlainPKCE?: boolean;
194
203
  /**
195
204
  * Controls whether OAuth 2.0 Token Exchange (RFC 8693) is allowed.
196
205
  * When false, the token exchange grant type will not be advertised in metadata
@@ -237,6 +246,40 @@ interface OAuthProviderOptions {
237
246
  status: number;
238
247
  headers: Record<string, string>;
239
248
  }) => Response | void;
249
+ /**
250
+ * Optional metadata for RFC 9728 OAuth 2.0 Protected Resource Metadata.
251
+ * Controls the response served at /.well-known/oauth-protected-resource.
252
+ *
253
+ * If not provided, the endpoint will be automatically generated using the request origin
254
+ * as the resource identifier, and the token endpoint's origin as the authorization server.
255
+ */
256
+ resourceMetadata?: {
257
+ /**
258
+ * The protected resource identifier URL (RFC 9728 `resource` field).
259
+ * If not set, defaults to the request URL's origin.
260
+ */
261
+ resource?: string;
262
+ /**
263
+ * List of authorization server issuer URLs that can issue tokens for this resource.
264
+ * If not set, defaults to the token endpoint's origin (consistent with the issuer
265
+ * in authorization server metadata).
266
+ */
267
+ authorization_servers?: string[];
268
+ /**
269
+ * Scopes supported by this protected resource.
270
+ * If not set, falls back to the top-level scopesSupported option.
271
+ */
272
+ scopes_supported?: string[];
273
+ /**
274
+ * Methods by which bearer tokens can be presented to this resource.
275
+ * Defaults to ["header"].
276
+ */
277
+ bearer_methods_supported?: string[];
278
+ /**
279
+ * Human-readable name for this resource.
280
+ */
281
+ resource_name?: string;
282
+ };
240
283
  }
241
284
  /**
242
285
  * Helper methods for OAuth operations provided to handler functions
@@ -714,13 +757,13 @@ interface GrantSummary {
714
757
  * Implements authorization code flow with support for refresh tokens
715
758
  * and dynamic client registration.
716
759
  */
717
- declare class OAuthProvider {
760
+ declare class OAuthProvider<Env = Cloudflare.Env> {
718
761
  #private;
719
762
  /**
720
763
  * Creates a new OAuth provider instance
721
764
  * @param options - Configuration options for the provider
722
765
  */
723
- constructor(options: OAuthProviderOptions);
766
+ constructor(options: OAuthProviderOptions<Env>);
724
767
  /**
725
768
  * Main fetch handler for the Worker
726
769
  * Routes requests to the appropriate handler based on the URL
@@ -729,7 +772,7 @@ declare class OAuthProvider {
729
772
  * @param ctx - Cloudflare Worker execution context
730
773
  * @returns A Promise resolving to an HTTP Response
731
774
  */
732
- fetch(request: Request, env: any, ctx: ExecutionContext): Promise<Response>;
775
+ fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response>;
733
776
  }
734
777
  /**
735
778
  * Gets OAuthHelpers for the given environment
@@ -737,6 +780,6 @@ declare class OAuthProvider {
737
780
  * @param env - Cloudflare Worker environment variables
738
781
  * @returns An instance of OAuthHelpers
739
782
  */
740
- declare function getOAuthApi(options: OAuthProviderOptions, env: any): OAuthHelpers;
783
+ declare function getOAuthApi<Env = Cloudflare.Env>(options: OAuthProviderOptions<Env>, env: Env): OAuthHelpers;
741
784
  //#endregion
742
785
  export { AuthRequest, ClientInfo, CompleteAuthorizationOptions, ExchangeTokenOptions, Grant, GrantSummary, GrantType, ListOptions, ListResult, OAuthHelpers, OAuthProvider, OAuthProvider as default, OAuthProviderOptions, ResolveExternalTokenInput, ResolveExternalTokenResult, Token, TokenBase, TokenExchangeCallbackOptions, TokenExchangeCallbackResult, TokenSummary, getOAuthApi };
@@ -141,7 +141,7 @@ var OAuthProviderImpl = class OAuthProviderImpl {
141
141
  async fetch(request, env, ctx) {
142
142
  const url = new URL(request.url);
143
143
  if (request.method === "OPTIONS") {
144
- if (this.isApiRequest(url) || url.pathname === "/.well-known/oauth-authorization-server" || this.isTokenEndpoint(url) || this.options.clientRegistrationEndpoint && this.isClientRegistrationEndpoint(url)) return this.addCorsHeaders(new Response(null, {
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
145
  status: 204,
146
146
  headers: { "Content-Length": "0" }
147
147
  }), request);
@@ -150,6 +150,10 @@ var OAuthProviderImpl = class OAuthProviderImpl {
150
150
  const response = await this.handleMetadataDiscovery(url);
151
151
  return this.addCorsHeaders(response, request);
152
152
  }
153
+ if (url.pathname === "/.well-known/oauth-protected-resource") {
154
+ const response = this.handleProtectedResourceMetadata(url);
155
+ return this.addCorsHeaders(response, request);
156
+ }
153
157
  if (this.isTokenEndpoint(url)) {
154
158
  const parsed = await this.parseTokenEndpointRequest(request, env);
155
159
  if (parsed instanceof Response) return this.addCorsHeaders(parsed, request);
@@ -287,8 +291,10 @@ var OAuthProviderImpl = class OAuthProviderImpl {
287
291
  * @returns True if the URL matches the API route
288
292
  */
289
293
  matchApiRoute(url, route) {
290
- if (this.isPath(route)) return url.pathname.startsWith(route);
291
- else {
294
+ if (this.isPath(route)) {
295
+ if (route === "/") return url.pathname === "/";
296
+ return url.pathname.startsWith(route);
297
+ } else {
292
298
  const apiUrl = new URL(route);
293
299
  return url.hostname === apiUrl.hostname && url.pathname.startsWith(apiUrl.pathname);
294
300
  }
@@ -367,12 +373,31 @@ var OAuthProviderImpl = class OAuthProviderImpl {
367
373
  "none"
368
374
  ],
369
375
  revocation_endpoint: tokenEndpoint,
370
- code_challenge_methods_supported: ["plain", "S256"],
376
+ code_challenge_methods_supported: this.options.allowPlainPKCE !== false ? ["plain", "S256"] : ["S256"],
371
377
  client_id_metadata_document_supported: this.hasGlobalFetchStrictlyPublic()
372
378
  };
373
379
  return new Response(JSON.stringify(metadata), { headers: { "Content-Type": "application/json" } });
374
380
  }
375
381
  /**
382
+ * Handles the OAuth Protected Resource Metadata endpoint
383
+ * Implements RFC 9728 for OAuth Protected Resource Metadata
384
+ * @param requestUrl - The URL of the incoming request
385
+ * @returns Response with protected resource metadata
386
+ */
387
+ handleProtectedResourceMetadata(requestUrl) {
388
+ const rm = this.options.resourceMetadata;
389
+ const tokenEndpointUrl = this.getFullEndpointUrl(this.options.tokenEndpoint, requestUrl);
390
+ const authServerOrigin = new URL(tokenEndpointUrl).origin;
391
+ const metadata = {
392
+ resource: rm?.resource ?? requestUrl.origin,
393
+ authorization_servers: rm?.authorization_servers ?? [authServerOrigin],
394
+ scopes_supported: rm?.scopes_supported ?? this.options.scopesSupported,
395
+ bearer_methods_supported: rm?.bearer_methods_supported ?? ["header"]
396
+ };
397
+ if (rm?.resource_name) metadata.resource_name = rm.resource_name;
398
+ return new Response(JSON.stringify(metadata), { headers: { "Content-Type": "application/json" } });
399
+ }
400
+ /**
376
401
  * Handles client authentication and token issuance via the token endpoint
377
402
  * Supports authorization_code and refresh_token grant types
378
403
  * @param body - The parsed request body
@@ -411,7 +436,7 @@ var OAuthProviderImpl = class OAuthProviderImpl {
411
436
  if (grantData.clientId !== clientInfo.clientId) return this.createErrorResponse("invalid_grant", "Client ID mismatch");
412
437
  const isPkceEnabled = !!grantData.codeChallenge;
413
438
  if (!redirectUri && !isPkceEnabled) return this.createErrorResponse("invalid_request", "redirect_uri is required when not using PKCE");
414
- if (redirectUri && !clientInfo.redirectUris.includes(redirectUri)) return this.createErrorResponse("invalid_grant", "Invalid redirect URI");
439
+ if (redirectUri && !isValidRedirectUri(redirectUri, clientInfo.redirectUris)) return this.createErrorResponse("invalid_grant", "Invalid redirect URI");
415
440
  if (!isPkceEnabled && codeVerifier) return this.createErrorResponse("invalid_request", "code_verifier provided for a flow that did not use PKCE");
416
441
  if (isPkceEnabled) {
417
442
  if (!codeVerifier) return this.createErrorResponse("invalid_request", "code_verifier is required for PKCE");
@@ -921,8 +946,10 @@ var OAuthProviderImpl = class OAuthProviderImpl {
921
946
  * @returns Response from the API handler or error
922
947
  */
923
948
  async handleApiRequest(request, env, ctx) {
949
+ const url = new URL(request.url);
950
+ const resourceMetadataUrl = `${url.origin}/.well-known/oauth-protected-resource`;
924
951
  const authHeader = request.headers.get("Authorization");
925
- if (!authHeader || !authHeader.startsWith("Bearer ")) return this.createErrorResponse("invalid_token", "Missing or invalid access token", 401, { "WWW-Authenticate": "Bearer realm=\"OAuth\", error=\"invalid_token\", error_description=\"Missing or invalid access token\"" });
952
+ 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") });
926
953
  const accessToken = authHeader.substring(7);
927
954
  const parts = accessToken.split(":");
928
955
  const isPossiblyInternalFormat = parts.length === 3;
@@ -934,14 +961,14 @@ var OAuthProviderImpl = class OAuthProviderImpl {
934
961
  const id = await generateTokenId(accessToken);
935
962
  tokenData = await env.OAUTH_KV.get(`token:${userId}:${grantId}:${id}`, { type: "json" });
936
963
  }
937
- if (!tokenData && !this.options.resolveExternalToken) return this.createErrorResponse("invalid_token", "Invalid access token", 401, { "WWW-Authenticate": "Bearer realm=\"OAuth\", error=\"invalid_token\"" });
964
+ if (!tokenData && !this.options.resolveExternalToken) return this.createErrorResponse("invalid_token", "Invalid access token", 401, { "WWW-Authenticate": this.buildWwwAuthenticateHeader(resourceMetadataUrl, "invalid_token") });
938
965
  if (tokenData) {
939
966
  const now = Math.floor(Date.now() / 1e3);
940
- if (tokenData.expiresAt < now) return this.createErrorResponse("invalid_token", "Access token expired", 401, { "WWW-Authenticate": "Bearer realm=\"OAuth\", error=\"invalid_token\"" });
967
+ if (tokenData.expiresAt < now) return this.createErrorResponse("invalid_token", "Access token expired", 401, { "WWW-Authenticate": this.buildWwwAuthenticateHeader(resourceMetadataUrl, "invalid_token") });
941
968
  if (tokenData.audience) {
942
969
  const requestUrl = new URL(request.url);
943
970
  const resourceServer = `${requestUrl.protocol}//${requestUrl.host}${requestUrl.pathname}`;
944
- if (!(Array.isArray(tokenData.audience) ? tokenData.audience : [tokenData.audience]).some((aud) => audienceMatches(resourceServer, aud))) return this.createErrorResponse("invalid_token", "Token audience does not match resource server", 401, { "WWW-Authenticate": "Bearer realm=\"OAuth\", error=\"invalid_token\", error_description=\"Invalid audience\"" });
971
+ if (!(Array.isArray(tokenData.audience) ? tokenData.audience : [tokenData.audience]).some((aud) => audienceMatches(resourceServer, aud))) return this.createErrorResponse("invalid_token", "Token audience does not match resource server", 401, { "WWW-Authenticate": this.buildWwwAuthenticateHeader(resourceMetadataUrl, "invalid_token", "Invalid audience") });
945
972
  }
946
973
  ctx.props = await decryptProps(await unwrapKeyWithToken(accessToken, tokenData.wrappedEncryptionKey), tokenData.grant.encryptedProps);
947
974
  } else if (this.options.resolveExternalToken) {
@@ -950,16 +977,15 @@ var OAuthProviderImpl = class OAuthProviderImpl {
950
977
  request,
951
978
  env
952
979
  });
953
- if (!ext) return this.createErrorResponse("invalid_token", "Invalid access token", 401, { "WWW-Authenticate": "Bearer realm=\"OAuth\", error=\"invalid_token\"" });
980
+ if (!ext) return this.createErrorResponse("invalid_token", "Invalid access token", 401, { "WWW-Authenticate": this.buildWwwAuthenticateHeader(resourceMetadataUrl, "invalid_token") });
954
981
  if (ext.audience) {
955
982
  const requestUrl = new URL(request.url);
956
983
  const resourceServer = `${requestUrl.protocol}//${requestUrl.host}${requestUrl.pathname}`;
957
- if (!(Array.isArray(ext.audience) ? ext.audience : [ext.audience]).some((aud) => audienceMatches(resourceServer, aud))) return this.createErrorResponse("invalid_token", "Token audience does not match resource server", 401, { "WWW-Authenticate": "Bearer realm=\"OAuth\", error=\"invalid_token\", error_description=\"Invalid audience\"" });
984
+ if (!(Array.isArray(ext.audience) ? ext.audience : [ext.audience]).some((aud) => audienceMatches(resourceServer, aud))) return this.createErrorResponse("invalid_token", "Token audience does not match resource server", 401, { "WWW-Authenticate": this.buildWwwAuthenticateHeader(resourceMetadataUrl, "invalid_token", "Invalid audience") });
958
985
  }
959
986
  ctx.props = ext.props;
960
987
  }
961
988
  if (!env.OAUTH_PROVIDER) env.OAUTH_PROVIDER = this.createOAuthHelpers(env);
962
- const url = new URL(request.url);
963
989
  const apiHandler = this.findApiHandlerForUrl(url);
964
990
  if (!apiHandler) return this.createErrorResponse("invalid_request", "No handler found for API route", 404);
965
991
  if (apiHandler.type === HandlerType.EXPORTED_HANDLER) return apiHandler.handler.fetch(request, env, ctx);
@@ -1183,6 +1209,14 @@ var OAuthProviderImpl = class OAuthProviderImpl {
1183
1209
  return JSON.parse(text);
1184
1210
  }
1185
1211
  /**
1212
+ * Builds a WWW-Authenticate header value with resource_metadata per RFC 9728 §5.1
1213
+ */
1214
+ buildWwwAuthenticateHeader(resourceMetadataUrl, error, errorDescription) {
1215
+ let header = `Bearer realm="OAuth", resource_metadata="${resourceMetadataUrl}", error="${error}"`;
1216
+ if (errorDescription) header += `, error_description="${errorDescription}"`;
1217
+ return header;
1218
+ }
1219
+ /**
1186
1220
  * Helper function to create OAuth error responses
1187
1221
  * @param code - OAuth error code (e.g., 'invalid_request', 'invalid_token')
1188
1222
  * @param description - Human-readable error description
@@ -1338,6 +1372,37 @@ function validateRedirectUriScheme(redirectUri) {
1338
1372
  for (const dangerousScheme of dangerousSchemes) if (scheme === dangerousScheme) throw new Error("Invalid redirect URI");
1339
1373
  }
1340
1374
  /**
1375
+ * Checks if a URI is a loopback redirect URI (127.0.0.0/8 or ::1)
1376
+ * Per RFC 8252 Section 7.3, these get special port handling
1377
+ */
1378
+ function isLoopbackUri(uri) {
1379
+ try {
1380
+ const host = new URL(uri).hostname;
1381
+ if (host.match(/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) return true;
1382
+ if (host === "::1" || host === "[::1]") return true;
1383
+ return false;
1384
+ } catch {
1385
+ return false;
1386
+ }
1387
+ }
1388
+ /**
1389
+ * Validates a redirect URI against registered URIs with RFC 8252 loopback support.
1390
+ * For loopback URIs (127.x.x.x, ::1), any port is allowed as long as scheme, host, path, and query match.
1391
+ * For non-loopback URIs, exact match is required.
1392
+ */
1393
+ function isValidRedirectUri(requestUri, registeredUris) {
1394
+ return registeredUris.some((registered) => {
1395
+ if (isLoopbackUri(requestUri) && isLoopbackUri(registered)) try {
1396
+ const reqUrl = new URL(requestUri);
1397
+ const regUrl = new URL(registered);
1398
+ return reqUrl.protocol === regUrl.protocol && reqUrl.hostname === regUrl.hostname && reqUrl.pathname === regUrl.pathname && reqUrl.search === regUrl.search;
1399
+ } catch {
1400
+ return false;
1401
+ }
1402
+ return requestUri === registered;
1403
+ });
1404
+ }
1405
+ /**
1341
1406
  * Encodes a string as base64url (URL-safe base64)
1342
1407
  * @param str - The string to encode
1343
1408
  * @returns The base64url encoded string
@@ -1506,11 +1571,12 @@ var OAuthHelpersImpl = class {
1506
1571
  const resource = parseResourceParameter(resourceParam);
1507
1572
  if (resourceParam && !resource) throw new Error("The resource parameter must be a valid absolute URI without a fragment");
1508
1573
  if (responseType === "token" && !this.provider.options.allowImplicitFlow) throw new Error("The implicit grant flow is not enabled for this provider");
1574
+ if (codeChallengeMethod === "plain" && this.provider.options.allowPlainPKCE === false) throw new Error("The plain PKCE method is not allowed. Use S256 instead.");
1509
1575
  if (clientId) {
1510
1576
  const clientInfo = await this.lookupClient(clientId);
1511
1577
  if (!clientInfo) throw new Error(`Invalid client. The clientId provided does not match to this client.`);
1512
1578
  if (clientInfo && redirectUri) {
1513
- if (!clientInfo.redirectUris.includes(redirectUri)) throw new Error(`Invalid redirect URI. The redirect URI provided does not match any registered URI for this client.`);
1579
+ if (!isValidRedirectUri(redirectUri, clientInfo.redirectUris)) throw new Error(`Invalid redirect URI. The redirect URI provided does not match any registered URI for this client.`);
1514
1580
  }
1515
1581
  }
1516
1582
  return {
@@ -1543,7 +1609,7 @@ var OAuthHelpersImpl = class {
1543
1609
  const { clientId, redirectUri } = options.request;
1544
1610
  if (!clientId || !redirectUri) throw new Error("Client ID and Redirect URI are required in the authorization request.");
1545
1611
  const clientInfo = await this.lookupClient(clientId);
1546
- if (!clientInfo || !clientInfo.redirectUris.includes(redirectUri)) throw new Error("Invalid redirect URI. The redirect URI provided does not match any registered URI for this client.");
1612
+ if (!clientInfo || !isValidRedirectUri(redirectUri, clientInfo.redirectUris)) throw new Error("Invalid redirect URI. The redirect URI provided does not match any registered URI for this client.");
1547
1613
  const grantId = generateRandomString(16);
1548
1614
  const { encryptedData, key: encryptionKey } = await encryptProps(options.props);
1549
1615
  const now = Math.floor(Date.now() / 1e3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudflare/workers-oauth-provider",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "OAuth provider for Cloudflare Workers",
5
5
  "main": "dist/oauth-provider.js",
6
6
  "types": "dist/oauth-provider.d.ts",