@cloudflare/workers-oauth-provider 0.10.4 → 1.0.0

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.
@@ -324,6 +324,134 @@ declare class AuthorizationError extends Error {
324
324
  }
325
325
  declare function isValidOAuthScopeToken(scopeToken: string): boolean;
326
326
  //#endregion
327
+ //#region src/oauth-resource-server.d.ts
328
+ /** RFC 9728 metadata published by a standalone OAuth resource server. */
329
+ interface OAuthResourceMetadata {
330
+ /** The one canonical HTTPS identifier for this protected resource. */
331
+ resource: string;
332
+ /** Authorization server issuers that can issue tokens for this resource. */
333
+ authorization_servers: string[];
334
+ /** Minimal scopes used to access the protected resource. */
335
+ scopes_supported?: string[];
336
+ /** Bearer-token presentation methods. This implementation supports only `header`. */
337
+ bearer_methods_supported?: string[];
338
+ /** Human-readable protected-resource name. */
339
+ resource_name?: string;
340
+ }
341
+ /** Successful result returned by the application's token validator. */
342
+ interface OAuthResourceTokenValidation<Props> {
343
+ /** Application data exposed to the protected handler as `ctx.props`. */
344
+ props: Props;
345
+ /** Canonical audience to which the token is bound. */
346
+ audience: string;
347
+ /** Optional absolute expiry as seconds since the Unix epoch. */
348
+ expiresAt?: number;
349
+ /**
350
+ * Scopes the token carries. `OAuthAuthorizationServer.validateToken()` always reports them;
351
+ * a handler needs them to answer with {@link insufficientScope}.
352
+ */
353
+ scope?: string[];
354
+ /** Subject the token was issued for. */
355
+ userId?: string;
356
+ /** Client the token was issued to. */
357
+ clientId?: string;
358
+ }
359
+ /**
360
+ * What the host verified about the bearer token before calling the handler, exposed as
361
+ * `ctx.auth` beside the application's `ctx.props`. Both hosts set it: `OAuthResourceServer`
362
+ * from the validator's result, `OAuthProvider` from its own token record.
363
+ */
364
+ interface OAuthResourceAuth {
365
+ /** The token as presented. It is already in the request's `Authorization` header; never log it. */
366
+ token: string;
367
+ /** Canonical resource the token was accepted for. */
368
+ audience: string;
369
+ /** Absolute expiry as seconds since the Unix epoch, when known. */
370
+ expiresAt?: number;
371
+ /** Scopes the token carries; empty when the validator reported none. */
372
+ scope: string[];
373
+ /** Subject the token was issued for, when known. */
374
+ userId?: string;
375
+ /** Client the token was issued to, when known. */
376
+ clientId?: string;
377
+ }
378
+ /** The execution context a protected handler receives: `props` from the validator, `auth` from the host. */
379
+ type OAuthResourceContext<Props> = ExecutionContext<Props> & {
380
+ readonly auth: OAuthResourceAuth;
381
+ };
382
+ /**
383
+ * Validates a bearer token presented to one resource. `null` for a token that is not
384
+ * valid for that resource; a thrown error fails closed as `503`.
385
+ */
386
+ type OAuthResourceTokenValidator<Props> = (resource: string, token: string) => Promise<OAuthResourceTokenValidation<Props> | null>;
387
+ /**
388
+ * Protected application handler called after successful token validation: an object with
389
+ * `fetch`, or a `WorkerEntrypoint` subclass instantiated per request with `(ctx, env)`. Either
390
+ * way `ctx.props` carries what the validator returned and `ctx.auth` what the host verified.
391
+ */
392
+ type OAuthResourceHandler<Env, Props> = {
393
+ fetch(request: Request, env: Env, ctx: OAuthResourceContext<Props>): Response | Promise<Response>;
394
+ } | (new (ctx: OAuthResourceContext<Props>, env: Env) => {
395
+ fetch(request: Request): Response | Promise<Response>;
396
+ });
397
+ /** Configuration for {@link OAuthResourceServer}. */
398
+ interface OAuthResourceServerOptions<Env = Cloudflare.Env, Props = unknown> {
399
+ /** RFC 9728 metadata, including this server's one canonical resource. */
400
+ resourceMetadata: OAuthResourceMetadata;
401
+ /** Application handler for the canonical resource URL and its path descendants. */
402
+ handler: OAuthResourceHandler<Env, Props>;
403
+ /**
404
+ * The validator for a request. The host calls what you return with this server's
405
+ * canonical resource and the bearer token, so neither is repeated here.
406
+ *
407
+ * - The authorization server in another Worker, over a Service Binding to a
408
+ * `WorkerEntrypoint` that exposes `OAuthAuthorizationServer.validateToken()`:
409
+ * `(env) => env.AUTH_SERVER.validateToken`
410
+ * - The authorization server in this Worker:
411
+ * `(env) => (resource, token) => authorizationServer.validateToken(resource, token, env)`
412
+ * - Anything else, at your own risk: a function that validates the token for `resource`.
413
+ */
414
+ validateToken(env: Env, request: Request): OAuthResourceTokenValidator<Props>;
415
+ }
416
+ /**
417
+ * The response for a valid token that lacks the scopes an operation needs (RFC 6750 §3.1,
418
+ * MCP scope challenge handling): `403` with `WWW-Authenticate: Bearer error="insufficient_scope"`,
419
+ * the `scope` the operation requires and the `resource_metadata` URL the client already knows.
420
+ * Name every scope the operation needs in one response; clients treat the list as complete.
421
+ *
422
+ * ```ts
423
+ * if (!ctx.auth.scope.includes('calendar:write')) return insufficientScope(ctx.auth, ['calendar:write']);
424
+ * ```
425
+ */
426
+ declare function insufficientScope(auth: OAuthResourceAuth, scope: string[], description?: string): Response;
427
+ /**
428
+ * One OAuth protected resource, in the authorization server's Worker or its own. Publishes
429
+ * RFC 9728 metadata, challenges unauthenticated requests, validates the bearer token through
430
+ * the configured `validateToken`, enforces audience and expiry on what it returns, and routes
431
+ * only the canonical resource and its descendants to the application handler.
432
+ *
433
+ * `export default new OAuthResourceServer({ … })`, or dispatch to `fetch()` from your own
434
+ * router, exactly as with `OAuthAuthorizationServer`.
435
+ */
436
+ declare class OAuthResourceServer<Env = Cloudflare.Env, Props = unknown> {
437
+ #private;
438
+ constructor(options: OAuthResourceServerOptions<Env, Props>);
439
+ fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response>;
440
+ }
441
+ //#endregion
442
+ //#region src/oauth-resource.d.ts
443
+ /** Validate an RFC 3986-safe HTTP(S) resource identifier for RFC 8707. */
444
+ declare function validateResourceUri(uri: string): boolean;
445
+ /**
446
+ * Whether a requested resource identifies a granted or configured resource.
447
+ * Only ASCII case in the URI scheme and host is ignored, and an empty path is
448
+ * equivalent to `/` (RFC 3986 §6.2.3), so a client that round-trips
449
+ * `https://example.com` through a URL parser as `https://example.com/` still
450
+ * names it. Port, path, query, a trailing slash after a path segment, user
451
+ * information, and every other byte remain significant.
452
+ */
453
+ declare function resourceMatches(requested: string, granted: string): boolean;
454
+ //#endregion
327
455
  //#region src/oauth-provider.d.ts
328
456
  /**
329
457
  * Enum representing OAuth grant types
@@ -337,8 +465,8 @@ declare enum GrantType {
337
465
  /**
338
466
  * Aliases for either type of Handler that makes .fetch required
339
467
  */
340
- type ExportedHandlerWithFetch<Env = Cloudflare.Env> = ExportedHandler<Env> & Pick<Required<ExportedHandler<Env>>, 'fetch'>;
341
- type WorkerEntrypointWithFetch<Env = Cloudflare.Env> = WorkerEntrypoint<Env> & {
468
+ type ExportedHandlerWithFetch<Env = Cloudflare.Env, Props = unknown> = ExportedHandler<Env, unknown, unknown, Props> & Pick<Required<ExportedHandler<Env, unknown, unknown, Props>>, 'fetch'>;
469
+ type WorkerEntrypointWithFetch<Env = Cloudflare.Env, Props = {}> = WorkerEntrypoint<Env, Props> & {
342
470
  fetch: NonNullable<WorkerEntrypoint['fetch']>;
343
471
  };
344
472
  /**
@@ -382,16 +510,36 @@ interface TokenExchangeCallbackResult {
382
510
  /**
383
511
  * Override the default refresh token TTL (time-to-live) for this specific grant.
384
512
  * Value should be in seconds.
385
- * Note: This is only honored during authorization code exchange. If returned during
386
- * refresh token exchange, it will be ignored.
513
+ * Note: This is only honored during authorization code exchange. Returning it during
514
+ * refresh token exchange is rejected with `invalid_request`; to extend a grant on
515
+ * refresh, return `refreshTokenIdleTTL`.
387
516
  */
388
517
  refreshTokenTTL?: number;
518
+ /**
519
+ * Idle lifetime for this grant's refresh token, in seconds from this refresh. The
520
+ * grant then expires that long after this refresh unless it is refreshed again. Only
521
+ * honored during refresh token exchange; returning it for another grant type is
522
+ * rejected with `invalid_request`. Overrides the provider's `refreshTokenIdleTTL`
523
+ * for this refresh.
524
+ *
525
+ * Useful when the Worker is itself an OAuth client and has just rotated an upstream
526
+ * refresh token: return the upstream lifetime and the grant lives exactly as long as
527
+ * the credentials it proxies. Must be an integer of at least 60 seconds.
528
+ */
529
+ refreshTokenIdleTTL?: number;
389
530
  /**
390
531
  * Optional scopes for the new access token. Values outside the scope ceiling
391
532
  * for the current grant flow are ignored. If omitted, the effective requested
392
533
  * scopes are used.
393
534
  */
394
535
  accessTokenScope?: string[];
536
+ /**
537
+ * Permit a token exchange whose authenticated client differs from the client the
538
+ * subject token's grant was issued to. Cross-client exchange is rejected with
539
+ * `invalid_request` unless the callback returns `true` here, so impersonation
540
+ * across clients is always a deliberate policy decision.
541
+ */
542
+ allowCrossClientExchange?: boolean;
395
543
  }
396
544
  /**
397
545
  * Options for token exchange callback functions
@@ -402,9 +550,17 @@ interface TokenExchangeCallbackOptions {
402
550
  */
403
551
  grantType: GrantType;
404
552
  /**
405
- * Client that received this grant
553
+ * Client authenticated on this token request. For `authorization_code` and
554
+ * `refresh_token` it is always the grant's client. For token exchange it is the
555
+ * exchanging client, which may differ from {@link subjectClientId}.
406
556
  */
407
557
  clientId: string;
558
+ /**
559
+ * Client the underlying grant was issued to. Equal to `clientId` except during a
560
+ * cross-client token exchange, which the callback must explicitly allow with
561
+ * {@link TokenExchangeCallbackResult.allowCrossClientExchange}.
562
+ */
563
+ subjectClientId: string;
408
564
  /**
409
565
  * User who authorized this grant
410
566
  */
@@ -425,6 +581,8 @@ interface TokenExchangeCallbackOptions {
425
581
  * Effective scopes selected for this token before applying the callback result.
426
582
  */
427
583
  requestedScope: string[];
584
+ /** Canonical protected resource selected for this grant and token. */
585
+ resource: string;
428
586
  /**
429
587
  * Application-specific properties currently associated with this grant
430
588
  */
@@ -504,11 +662,40 @@ interface ResolveExternalTokenResult {
504
662
  *
505
663
  * A JWT may carry this value as an `aud` claim. For an opaque API token or
506
664
  * PAT, the callback can supply the local resource URI as policy after
507
- * successful validation. When `resourceMetadata.resource` is configured,
508
- * this value is required and must match it exactly.
665
+ * successful validation. This value is required and must identify the
666
+ * configured canonical `resourceMetadata.resource`: ASCII case in the scheme
667
+ * and host is folded and an empty path equals `/`, while port, path, query,
668
+ * and trailing slash are compared exactly.
509
669
  */
510
- audience?: string | string[];
670
+ audience: string;
671
+ }
672
+ /** RFC 9728 metadata owned by one protected resource server. */
673
+ interface OAuthProtectedResourceMetadata {
674
+ /**
675
+ * The protected resource identifier HTTPS URL (RFC 9728 `resource` field).
676
+ * Configure an RFC 3986-safe HTTPS producer URL with lowercase scheme/host
677
+ * and no userinfo, default port, fragment, or dot segments.
678
+ */
679
+ resource: string;
680
+ /**
681
+ * Authorization server issuers that can issue tokens for this resource.
682
+ * In the legacy combined configuration this defaults to the token endpoint
683
+ * origin. In the role-based configuration it defaults to the configured AS
684
+ * issuer.
685
+ */
686
+ authorization_servers?: string[];
687
+ /** Minimal scopes required for basic protected-resource functionality. */
688
+ scopes_supported?: string[];
689
+ /** Methods by which bearer tokens can be presented. Defaults to `["header"]`. */
690
+ bearer_methods_supported?: string[];
691
+ /** Human-readable name for this resource. */
692
+ resource_name?: string;
511
693
  }
694
+ /**
695
+ * Existing combined authorization-server and protected-resource configuration.
696
+ * This shape remains supported in 1.0 and is normalized to a one-resource
697
+ * role-based provider internally.
698
+ */
512
699
  interface OAuthProviderOptions<Env = Cloudflare.Env> {
513
700
  /**
514
701
  * URL(s) for API routes. Requests with URLs starting with any of these prefixes
@@ -572,6 +759,18 @@ interface OAuthProviderOptions<Env = Cloudflare.Env> {
572
759
  * For example: 3600 = 1 hour, 2592000 = 30 days
573
760
  */
574
761
  refreshTokenTTL?: number;
762
+ /**
763
+ * Idle lifetime for refresh tokens in seconds. When set, every successful refresh
764
+ * moves the grant's expiry to this many seconds after the refresh, so a grant lives
765
+ * for as long as the client keeps using it and expires once it has been idle for
766
+ * this long. `refreshTokenTTL` still sets the lifetime of a new grant, and
767
+ * `tokenExchangeCallback` can override the idle lifetime per refresh by returning
768
+ * `refreshTokenIdleTTL`. Must be an integer of at least 60 seconds.
769
+ *
770
+ * Leave unset for a fixed lifetime, where a grant expires `refreshTokenTTL` seconds
771
+ * after the code exchange however often it is refreshed.
772
+ */
773
+ refreshTokenIdleTTL?: number;
575
774
  /**
576
775
  * Time-to-live for dynamically registered clients in seconds.
577
776
  * Defaults to 90 days (7,776,000 seconds).
@@ -655,10 +854,10 @@ interface OAuthProviderOptions<Env = Cloudflare.Env> {
655
854
  *
656
855
  * If the function returns a Response, that will be used in place of the OAuthProvider's default one.
657
856
  *
658
- * `internal` (when present) carries a tagged, server-side-only reason that the library
659
- * deliberately did NOT put on the wire — used for richer diagnostics where the public
660
- * response must stay generic (e.g. JWT validation failures on the EMA path). Backwards
661
- * compatible: existing callbacks ignoring this field continue to work unchanged.
857
+ * `internal` carries the stable, server-side-only reason for the error — often more
858
+ * specific than what the wire deliberately reveals (RFC 6749 §5.2), e.g. which refresh-token
859
+ * check failed or which EMA validation fired. It is never placed on the wire. See
860
+ * {@link OAuthErrorInternal}.
662
861
  *
663
862
  * `request` (when present) is the HTTP request that produced the error response, so the
664
863
  * callback can correlate the error with per-request state such as request-keyed telemetry.
@@ -670,11 +869,7 @@ interface OAuthProviderOptions<Env = Cloudflare.Env> {
670
869
  description: string;
671
870
  status: number;
672
871
  headers: Record<string, string>;
673
- internal?: {
674
- category: string;
675
- reason: string;
676
- detail?: unknown;
677
- };
872
+ internal: OAuthErrorInternal;
678
873
  request?: Request;
679
874
  }) => Response | void;
680
875
  /**
@@ -685,59 +880,52 @@ interface OAuthProviderOptions<Env = Cloudflare.Env> {
685
880
  */
686
881
  clientIdMetadataDocumentEnabled?: boolean;
687
882
  /**
688
- * When true, requested-vs-granted resource validation compares origins only
689
- * (scheme + host + port) instead of exact URIs. This allows an origin-only
690
- * grant such as `https://server.com` to accept `https://server.com/mcp`, but
691
- * also ignores path and query differences. Configured canonical resources
692
- * always use exact matching.
693
- *
694
- * Defaults to false (strict exact matching per RFC 8707).
695
- *
696
- * @deprecated This comparison is unsafe for shared-origin multi-path or
697
- * multi-tenant deployments. Prefer configuring `resourceMetadata.resource`.
883
+ * Metadata for RFC 9728 OAuth 2.0 Protected Resource Metadata.
884
+ * Controls the response served at /.well-known/oauth-protected-resource.
698
885
  */
699
- resourceMatchOriginOnly?: boolean;
886
+ resourceMetadata: OAuthProtectedResourceMetadata;
887
+ }
888
+ /**
889
+ * Functional authorization-server surface for the role-based API.
890
+ * The application owns the interactive authorization route and uses
891
+ * `getOAuthApi()` to parse and complete it; `fetch()` serves protocol-owned AS
892
+ * endpoints such as metadata, token, revocation, and optional registration.
893
+ */
894
+ type OAuthAuthorizationServerOptions<Env = Cloudflare.Env> = Omit<OAuthProviderOptions<Env>, 'apiRoute' | 'apiHandler' | 'apiHandlers' | 'defaultHandler' | 'resourceMetadata' | 'resolveExternalToken'> & {
895
+ /** Canonical RFC 8414 issuer. */
896
+ issuer: string;
700
897
  /**
701
- * Optional metadata for RFC 9728 OAuth 2.0 Protected Resource Metadata.
702
- * Controls the response served at /.well-known/oauth-protected-resource.
703
- *
704
- * If not provided, the endpoint will be automatically generated using the request origin
705
- * as the resource identifier, and the token endpoint's origin as the authorization server.
898
+ * Canonical identifiers of every protected resource this authorization server issues
899
+ * tokens for, whether hosted in this Worker through `OAuthResourceServer` or by another
900
+ * Worker or service. At least one is required. The registry is fixed at construction,
901
+ * so `defaultResource` and `legacyGrantResource` are checked before the first request,
902
+ * and `validateToken()` rejects for a resource that is not listed.
706
903
  */
707
- resourceMetadata?: {
708
- /**
709
- * The protected resource identifier URL (RFC 9728 `resource` field).
710
- *
711
- * Configuring this value pins grants and access-token audiences to this
712
- * exact resource. An omitted authorization resource defaults to this value,
713
- * and an omitted token-request resource inherits it from the grant. Without
714
- * configuration, explicit RFC 8707 resource indicators are accepted and
715
- * omission remains unbound for backwards compatibility.
716
- */
717
- resource?: string;
718
- /**
719
- * List of authorization server issuer URLs that can issue tokens for this resource.
720
- * If not set, defaults to the token endpoint's origin (consistent with the issuer
721
- * in authorization server metadata).
722
- */
723
- authorization_servers?: string[];
724
- /**
725
- * Minimal scopes required for basic protected resource functionality.
726
- * These scopes are advertised in Protected Resource Metadata and used as
727
- * baseline bearer challenge guidance. `offline_access` is omitted because
728
- * refresh-token issuance is an authorization server capability.
729
- */
730
- scopes_supported?: string[];
731
- /**
732
- * Methods by which bearer tokens can be presented to this resource.
733
- * Defaults to ["header"].
734
- */
735
- bearer_methods_supported?: string[];
736
- /**
737
- * Human-readable name for this resource.
738
- */
739
- resource_name?: string;
740
- };
904
+ resources: readonly string[];
905
+ /** Resource selected for a new authorization request that omits `resource`. */
906
+ defaultResource?: string;
907
+ /**
908
+ * Migration destination for pre-resource grants and access tokens. Changing it
909
+ * re-targets every surviving unbound record, so keep it fixed for the migration window.
910
+ */
911
+ legacyGrantResource?: string;
912
+ };
913
+ /** Audience-checked token context suitable for a private Service Binding. */
914
+ interface ValidatedAccessToken<T = any> {
915
+ props: T;
916
+ audience: string;
917
+ expiresAt: number;
918
+ scope: string[];
919
+ userId: string;
920
+ clientId: string;
921
+ }
922
+ /**
923
+ * The RPC surface a `WorkerEntrypoint` exposes around {@link OAuthAuthorizationServer.validateToken},
924
+ * as a resource Worker sees it through a Service Binding. Hand a resource server the bound
925
+ * method as its validator: `validateToken: (env) => env.AUTH_SERVER.validateToken`.
926
+ */
927
+ interface AuthorizationServerBinding<Props = any> {
928
+ validateToken(resource: string, token: string): Promise<ValidatedAccessToken<Props> | null>;
741
929
  }
742
930
  /**
743
931
  * Helper methods for OAuth operations provided to handler functions
@@ -852,9 +1040,11 @@ interface ExchangeTokenOptions {
852
1040
  */
853
1041
  scope?: string[];
854
1042
  /**
855
- * Optional target audience/resource for the new token (maps to resource parameter per RFC 8707)
1043
+ * Optional canonical target audience/resource for the new token (maps to the
1044
+ * resource parameter per RFC 8707). When present, it must match the
1045
+ * provider's configured resource.
856
1046
  */
857
- aud?: string | string[];
1047
+ aud?: string;
858
1048
  /**
859
1049
  * Optional TTL override for the new token in seconds (must not exceed subject token's remaining lifetime)
860
1050
  */
@@ -893,9 +1083,10 @@ interface AuthRequest {
893
1083
  */
894
1084
  codeChallengeMethod?: string;
895
1085
  /**
896
- * Resource parameter indicating target resource(s) (RFC 8707)
1086
+ * Canonical target resource (RFC 8707). Parsed authorization requests
1087
+ * always contain the provider's configured resource.
897
1088
  */
898
- resource?: string | string[];
1089
+ resource?: string;
899
1090
  /**
900
1091
  * Authorization server issuer recorded while parsing this request.
901
1092
  * Include it as `iss` in successful and error authorization responses.
@@ -1015,10 +1206,12 @@ interface CompleteAuthorizationOptions {
1015
1206
  */
1016
1207
  revokeExistingGrants?: boolean;
1017
1208
  /**
1018
- * Maximum number of grants to fetch per page when revoking existing
1019
- * grants. Only used when revokeExistingGrants is not false.
1020
- * Must be a positive integer. Values above Cloudflare KV's 1000-key page
1021
- * limit are clamped to 1000. Defaults to 50.
1209
+ * How many grants written by a version before 1.0 are read at once while
1210
+ * revoking existing grants. Grants written by 1.0 or later carry their client,
1211
+ * resource and redirect URI as KV key metadata and are matched from `list()`
1212
+ * without being read, so this only bounds the fan-out for older grants. Only
1213
+ * used when revokeExistingGrants is not false. Must be a positive integer;
1214
+ * values above 1000 are clamped. Defaults to 50.
1022
1215
  */
1023
1216
  revokeExistingGrantsBatchSize?: number;
1024
1217
  }
@@ -1125,7 +1318,7 @@ interface TokenResponse {
1125
1318
  * Resource indicator(s) for the issued access token (RFC 8707 Section 2.2)
1126
1319
  * SHOULD be included to indicate the resource server(s) for which the token is valid
1127
1320
  */
1128
- resource?: string | string[];
1321
+ resource: string;
1129
1322
  }
1130
1323
  /**
1131
1324
  * Shared fields for Token and TokenSummary
@@ -1328,6 +1521,8 @@ interface GrantSummary {
1328
1521
  * grants created before this field was introduced.
1329
1522
  */
1330
1523
  redirectUri?: string;
1524
+ /** Canonical protected resource, absent only on a pre-resource legacy grant. */
1525
+ resource?: string | string[];
1331
1526
  }
1332
1527
  /**
1333
1528
  * OAuth 2.0 Provider implementation for Cloudflare Workers
@@ -1360,6 +1555,43 @@ declare class OAuthProvider<Env = Cloudflare.Env> {
1360
1555
  */
1361
1556
  purgeExpiredData(env: Env, options?: PurgeOptions): Promise<PurgeResult>;
1362
1557
  }
1558
+ /**
1559
+ * The authorization-server role on its own. It serves discovery, token, revocation and
1560
+ * registration endpoints from `fetch()`, exposes the interactive authorization flow through
1561
+ * `getOAuthApi()`, and validates its access tokens for any declared resource through
1562
+ * `validateToken()`. Resources are hosted, in this Worker or another, by
1563
+ * `OAuthResourceServer`, whose `validateToken` points back here.
1564
+ */
1565
+ declare class OAuthAuthorizationServer<Env = Cloudflare.Env> {
1566
+ #private;
1567
+ constructor(options: OAuthAuthorizationServerOptions<Env>);
1568
+ /**
1569
+ * Validate an access token for one declared resource: the token's KV record is loaded, its
1570
+ * audience checked against `resource`, and its props decrypted. Resolves `null` for a token
1571
+ * that is not valid for that resource, and rejects for a resource this server does not
1572
+ * declare.
1573
+ *
1574
+ * This is what a resource server calls, whether it shares this Worker or reaches it over a
1575
+ * Service Binding. For the latter, expose it from a `WorkerEntrypoint`:
1576
+ *
1577
+ * ```ts
1578
+ * export default class AuthServer extends WorkerEntrypoint<Env> {
1579
+ * fetch(request: Request) {
1580
+ * return authorizationServer.fetch(request, this.env, this.ctx);
1581
+ * }
1582
+ * validateToken(resource: string, token: string) {
1583
+ * return authorizationServer.validateToken(resource, token, this.env);
1584
+ * }
1585
+ * }
1586
+ * ```
1587
+ */
1588
+ validateToken<T = any>(resource: string, token: string, env: Env): Promise<ValidatedAccessToken<T> | null>;
1589
+ /** Serve protocol-owned authorization-server endpoints, excluding the application authorization UI. */
1590
+ fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response>;
1591
+ /** Obtain helpers for scheduled jobs or custom code outside a fetch dispatch. */
1592
+ getOAuthApi(env: Env): OAuthHelpers;
1593
+ purgeExpiredData(env: Env, options?: PurgeOptions): Promise<PurgeResult>;
1594
+ }
1363
1595
  /**
1364
1596
  * Gets OAuthHelpers for the given environment
1365
1597
  * @param options - Configuration options for the OAuth provider
@@ -1371,6 +1603,24 @@ declare function getOAuthApi<Env = Cloudflare.Env>(options: OAuthProviderOptions
1371
1603
  * Error class for OAuth operations
1372
1604
  * Carries OAuth error code and description for proper error responses
1373
1605
  */
1606
+ /**
1607
+ * The internal reason behind an error response, forwarded to the `onError` hook and never
1608
+ * placed on the wire. `category` is a stable kebab-case subsystem (`client-authentication`,
1609
+ * `authorization-code-grant`, `refresh-token-grant`, `token-exchange-grant`,
1610
+ * `token-endpoint-request`, `token-revocation`, `token-issuance`, `resource-indicator`,
1611
+ * `client-registration`, `client-id-metadata-document`, `protected-resource`,
1612
+ * `enterprise-managed-authorization`, `token-exchange-callback`); `reason` is a stable
1613
+ * snake_case slug naming the exact check that failed. Treat both like enum members in semver.
1614
+ * `detail` may carry structured context such as a caught error; it is never a secret.
1615
+ */
1616
+ interface OAuthErrorInternal {
1617
+ /** Stable kebab-case subsystem that produced the error. */
1618
+ category: string;
1619
+ /** Stable snake_case slug naming the failed check, often more specific than the wire description. */
1620
+ reason: string;
1621
+ /** Optional structured context, e.g. the caught error or the offending parameter. */
1622
+ detail?: unknown;
1623
+ }
1374
1624
  /**
1375
1625
  * Options accepted by the {@link OAuthError} constructor.
1376
1626
  */
@@ -1392,6 +1642,13 @@ interface OAuthErrorOptions {
1392
1642
  * number of seconds or an HTTP-date.
1393
1643
  */
1394
1644
  headers?: Record<string, string>;
1645
+ /**
1646
+ * Internal reason forwarded to the `onError` hook and never sent on the wire. The library
1647
+ * sets it on every error it originates; a `tokenExchangeCallback` may set its own. An
1648
+ * `OAuthError` thrown without one reaches `onError` as
1649
+ * `{ category: 'token-exchange-callback', reason: 'callback_error', detail: error }`.
1650
+ */
1651
+ internal?: OAuthErrorInternal;
1395
1652
  }
1396
1653
  /**
1397
1654
  * Structured OAuth 2.0 token-endpoint error.
@@ -1523,18 +1780,6 @@ declare class CimdFetchError extends Error {
1523
1780
  */
1524
1781
  constructor(metadataUrl: string, cause: unknown);
1525
1782
  }
1526
- /**
1527
- * Validates a resource URI per RFC 8707 Section 2
1528
- * @param uri - The URI string to validate
1529
- * @returns true if valid, false otherwise
1530
- */
1531
- declare function validateResourceUri(uri: string): boolean;
1532
- /**
1533
- * Checks if a requested resource matches a granted resource.
1534
- * When originOnly is true, compares only the origin (scheme + host + port),
1535
- * allowing path-aware resources to match origin-only grants.
1536
- */
1537
- declare function resourceMatches(requested: string, granted: string, originOnly: boolean): boolean;
1538
1783
  /**
1539
1784
  * Decodes a base64url-encoded string to bytes.
1540
1785
  */
@@ -1551,4 +1796,4 @@ declare function getJwtCryptoAlgorithms(alg: string): {
1551
1796
  verifyAlgorithm: Parameters<SubtleCrypto['verify']>[0];
1552
1797
  };
1553
1798
  //#endregion
1554
- export { AuthRequest, AuthorizationError, type AuthorizationErrorCode, type AuthorizationErrorOptions, CimdFetchError, ClientInfo, ClientRegistrationCallbackOptions, ClientRegistrationCallbackResult, CompleteAuthorizationOptions, type EmaClaimsMapper, type EmaClaimsMapperInput, type EmaClaimsMapperResult, type EmaIdJagClaims, type EmaOptions, type EmaTrustedIssuer, type EmaTrustedIssuerResolver, type EmaTrustedIssuerResolverInput, type EmaValidationError, ExchangeTokenOptions, ExternalTokenError, ExternalTokenErrorOptions, Grant, GrantSummary, GrantType, ListOptions, ListResult, OAuthError, OAuthErrorOptions, OAuthHelpers, OAuthProvider, OAuthProvider as default, OAuthProviderOptions, OAuthTokenErrorCode, PurgeOptions, PurgeResult, ResolveExternalTokenInput, ResolveExternalTokenResult, Token, TokenBase, TokenExchangeCallbackOptions, TokenExchangeCallbackResult, TokenSummary, base64UrlToBytes, getJwtCryptoAlgorithms, getOAuthApi, isValidOAuthScopeToken, parseJwtJsonPart, resourceMatches, validateResourceUri };
1799
+ export { AuthRequest, AuthorizationError, type AuthorizationErrorCode, type AuthorizationErrorOptions, AuthorizationServerBinding, CimdFetchError, ClientInfo, ClientRegistrationCallbackOptions, ClientRegistrationCallbackResult, CompleteAuthorizationOptions, type EmaClaimsMapper, type EmaClaimsMapperInput, type EmaClaimsMapperResult, type EmaIdJagClaims, type EmaOptions, type EmaTrustedIssuer, type EmaTrustedIssuerResolver, type EmaTrustedIssuerResolverInput, type EmaValidationError, ExchangeTokenOptions, ExternalTokenError, ExternalTokenErrorOptions, Grant, GrantSummary, GrantType, ListOptions, ListResult, OAuthAuthorizationServer, OAuthAuthorizationServerOptions, OAuthError, OAuthErrorInternal, OAuthErrorOptions, OAuthHelpers, OAuthProtectedResourceMetadata, OAuthProvider, OAuthProvider as default, OAuthProviderOptions, OAuthResourceAuth, OAuthResourceContext, OAuthResourceHandler, OAuthResourceMetadata, OAuthResourceServer, OAuthResourceServerOptions, OAuthResourceTokenValidation, OAuthResourceTokenValidator, OAuthTokenErrorCode, PurgeOptions, PurgeResult, ResolveExternalTokenInput, ResolveExternalTokenResult, Token, TokenBase, TokenExchangeCallbackOptions, TokenExchangeCallbackResult, TokenSummary, ValidatedAccessToken, base64UrlToBytes, getJwtCryptoAlgorithms, getOAuthApi, insufficientScope, isValidOAuthScopeToken, parseJwtJsonPart, resourceMatches, validateResourceUri };