@cloudflare/workers-oauth-provider 0.10.3 → 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.
package/README.md CHANGED
@@ -148,10 +148,60 @@ export default new OAuthProvider<Env>({
148
148
 
149
149
  `apiRoute` and `apiHandler` protect one or more route prefixes with a single handler. Use `apiHandlers` when different prefixes need different handlers.
150
150
 
151
- Before calling a protected handler, the provider reads the bearer token, rejects missing, invalid, or expired credentials, checks its audience, and exposes the authenticated application data through `ctx.props`. The handler does not need to parse or validate the token, but it must still enforce application permissions such as scope, ownership, and tenancy.
151
+ Before calling a protected handler, the provider reads the bearer token, rejects missing, invalid, or expired credentials, checks its audience, and exposes the authenticated application data through `ctx.props` and what it verified about the token through `ctx.auth` (`scope`, `userId`, `clientId`, `audience`, `expiresAt`). The handler does not need to parse or validate the token, but it still enforces application permissions such as scope, ownership, and tenancy; `insufficientScope(ctx.auth, scopes)` builds the MCP `403` challenge when a token lacks what an operation needs.
152
152
 
153
153
  Requests outside the protected route prefixes go to `defaultHandler`. In the example above, that handler owns `/authorize`.
154
154
 
155
+ ## One authorization server with multiple MCP resources
156
+
157
+ `OAuthAuthorizationServer` is the authorization-server role on its own: discovery, token, revocation and registration endpoints from `fetch()`, the interactive flow through `getOAuthApi()`, and `validateToken(resource, token, env)` for any resource in its fixed `resources` registry. Each resource is hosted by `new OAuthResourceServer()`, whose `validateToken` option points back at the authorization server — in this Worker or another. Same host, same `ctx.props`, wherever the resource runs.
158
+
159
+ **Same Worker.** Route the authorization server's origin to `authorizationServer.fetch()`, your `/authorize` page to `getOAuthApi()`, and each resource to its host. The validator is a direct call:
160
+
161
+ ```ts
162
+ const authorizationServer = new OAuthAuthorizationServer<Env>({
163
+ issuer: 'https://auth.example.com',
164
+ resources: ['https://calendar.example.com/mcp'],
165
+ authorizeEndpoint: '/authorize',
166
+ tokenEndpoint: '/oauth/token',
167
+ });
168
+
169
+ const calendar = new OAuthResourceServer<Env, AuthProps>({
170
+ resourceMetadata: {
171
+ resource: 'https://calendar.example.com/mcp',
172
+ authorization_servers: ['https://auth.example.com'],
173
+ },
174
+ validateToken: (env) => (resource, token) => authorizationServer.validateToken(resource, token, env),
175
+ handler: { fetch: (_request, _env, ctx) => Response.json({ userId: ctx.props.userId }) },
176
+ });
177
+ ```
178
+
179
+ **Separate Workers.** The authorization Worker exposes `validateToken` from a `WorkerEntrypoint`; the resource Worker holds a Service Binding to it and hands the host that method. Nothing else to configure, and the validator is a binding, not a URL — it is not reachable from the public internet:
180
+
181
+ ```ts
182
+ // auth Worker
183
+ export default class AuthServer extends WorkerEntrypoint<Env> {
184
+ fetch(request: Request) {
185
+ return authorizationServer.fetch(request, this.env, this.ctx);
186
+ }
187
+ validateToken(resource: string, token: string) {
188
+ return authorizationServer.validateToken(resource, token, this.env);
189
+ }
190
+ }
191
+
192
+ // calendar Worker, with `"services": [{ "binding": "AUTH_SERVER", "service": "auth" }]` in wrangler.jsonc
193
+ export default new OAuthResourceServer<Env, AuthProps>({
194
+ resourceMetadata: {
195
+ resource: 'https://calendar.example.com/mcp',
196
+ authorization_servers: ['https://auth.example.com'],
197
+ },
198
+ validateToken: (env) => env.AUTH_SERVER.validateToken,
199
+ handler,
200
+ });
201
+ ```
202
+
203
+ Either way the resource server publishes its own RFC 9728 metadata, issues Bearer challenges that point at it, checks the returned audience against its canonical resource, and answers `503` when validation infrastructure fails. Tokens are opaque throughout; a validator returns the decrypted `props` the authorization flow stored. See [docs/resource-servers.md](docs/resource-servers.md) for the three-domain Hono example, the `AuthorizationServerBinding` type for your `Env`, and how to validate tokens from another issuer at your own risk.
204
+
155
205
  ## How MCP authorization discovery works
156
206
 
157
207
  An MCP client discovers authorization in two stages, following the [MCP authorization server discovery rules](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/authorization-server-discovery).
@@ -162,7 +212,7 @@ For an MCP endpoint at `https://mcp.example.com/mcp`:
162
212
  2. The provider returns `401 Unauthorized` with a challenge similar to:
163
213
 
164
214
  ```http
165
- WWW-Authenticate: Bearer realm="OAuth", resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"
215
+ WWW-Authenticate: Bearer realm="OAuth", resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp", scope="mcp:read"
166
216
  ```
167
217
 
168
218
  3. The client fetches the protected resource metadata:
@@ -172,7 +222,7 @@ For an MCP endpoint at `https://mcp.example.com/mcp`:
172
222
  ```
173
223
 
174
224
  4. That document identifies one or more authorization server issuers through `authorization_servers`.
175
- 5. The client fetches this provider's RFC 8414 authorization server metadata:
225
+ 5. The client fetches RFC 8414 authorization server metadata from the selected issuer. In the single-origin quick start that is:
176
226
 
177
227
  ```text
178
228
  https://mcp.example.com/.well-known/oauth-authorization-server
@@ -187,21 +237,7 @@ Protected resource metadata and authorization server metadata serve different ro
187
237
 
188
238
  ### Protected resource metadata
189
239
 
190
- The provider always serves RFC 9728 metadata at:
191
-
192
- ```text
193
- /.well-known/oauth-protected-resource
194
- ```
195
-
196
- It also supports path-specific metadata. A request to:
197
-
198
- ```text
199
- /.well-known/oauth-protected-resource/public/mcp
200
- ```
201
-
202
- produces `https://example.com/public/mcp` as the derived resource unless `resourceMetadata.resource` overrides it.
203
-
204
- For MCP deployments, configure the canonical MCP endpoint explicitly:
240
+ Every protected resource needs its own `resourceMetadata.resource`. Configure each canonical HTTPS identifier with a lowercase scheme and host (plain `http` is accepted only on a loopback host, for `wrangler dev`):
205
241
 
206
242
  ```ts
207
243
  resourceMetadata: {
@@ -213,7 +249,17 @@ resourceMetadata: {
213
249
  }
214
250
  ```
215
251
 
216
- `authorization_servers` may contain more than one issuer. The MCP client chooses an authorization server and must keep credentials and tokens separate for each issuer.
252
+ For the example above, an unauthenticated request to the exact canonical URL receives a Bearer challenge pointing to:
253
+
254
+ ```text
255
+ https://mcp.example.com/.well-known/oauth-protected-resource/mcp
256
+ ```
257
+
258
+ That document returns the configured canonical `resource`. The discovery URL is built from the canonical resource: an origin uses `/.well-known/oauth-protected-resource`, and a path and query are inserted after the well-known prefix.
259
+
260
+ A canonical path is the base audience for its path-boundary descendants: a token for `https://mcp.example.com/mcp` is accepted at `/mcp/tools`, and a challenge at `/mcp/tools` advertises the one canonical document for `/mcp`, as RFC 9728 §5.1 permits. A request on another origin, or one that the canonical resource does not cover, gets a challenge without `resource_metadata`. Every protected route must be the canonical resource path or a descendant of it; the provider rejects any other `apiRoute` or `apiHandlers` key at construction, because a token could never validate there.
261
+
262
+ `authorization_servers` may contain more than one issuer. Each value must use canonical HTTPS issuer spelling: lowercase scheme and host, with no userinfo, default port, dot segments, query, or fragment. As with resources, `http` is accepted only on a loopback host. OAuth issuer comparison is exact. The MCP client chooses an authorization server and must keep credentials and tokens separate for each issuer. `new OAuthResourceServer()` requires it explicitly, wherever the resource runs.
217
263
 
218
264
  ### Authorization server metadata
219
265
 
@@ -222,6 +268,7 @@ The provider publishes RFC 8414 metadata containing:
222
268
  - `issuer`
223
269
  - `authorization_endpoint`
224
270
  - `token_endpoint`
271
+ - `protected_resources`, containing the authorization server's registered canonical resources
225
272
  - `registration_endpoint`, when DCR is enabled
226
273
  - supported response and grant types
227
274
  - token endpoint authentication methods
@@ -246,7 +293,7 @@ A typical flow has three steps:
246
293
 
247
294
  `completeAuthorization()` repeats response-type validation before writing a grant or revoking existing grants. Validation errors from reconstructed requests are also typed as `AuthorizationError`, but applications should not construct redirects from untrusted reconstructed values; the redirect context is attached only by `parseAuthRequest()`.
248
295
 
249
- `completeAuthorization()` stores a new grant and, by default, revokes existing grants for the same user and client after the new grant is safely stored. Set `revokeExistingGrants: false` only when the application intentionally allows concurrent grants for the same user and client.
296
+ `completeAuthorization()` stores a new grant and, by default, revokes existing grants for the same user, client, and resource after the new grant is safely stored. A grant for another registered resource is a separate authorization and is not revoked. Set `revokeExistingGrants: false` only when the application intentionally allows concurrent grants within the same resource.
250
297
 
251
298
  For Client ID Metadata Document clients, whose client_id is the metadata URL shared by every installation, default revocation is additionally scoped to grants created from the same redirect URI, so one installation's re-authorization does not revoke another's. Grants created before the redirect URI was recorded are never auto-revoked by CIMD clients.
252
299
 
@@ -324,7 +371,7 @@ clientRegistrationEndpoint: '/oauth/register';
324
371
 
325
372
  MCP 2026-07-28 deprecates DCR for new implementations in favor of CIMD. The endpoint remains useful for compatibility with clients that do not support CIMD.
326
373
 
327
- Registration accepts only authentication methods, grants, and response types implemented by the configured provider, and rejects inconsistent grant/response combinations before storage. Choice-valued `token_endpoint_auth_methods_supported` input is negotiated to one effective `token_endpoint_auth_method`; grant and response registrations remain strict. Omitted metadata uses the RFC 7591 defaults: `client_secret_basic`, `grant_types: ["authorization_code"]`, and `response_types: ["code"]`.
374
+ Registration accepts only authentication methods, grants, and response types implemented by the configured provider, and rejects inconsistent grant/response combinations before storage. Choice-valued `token_endpoint_auth_methods_supported` input is negotiated to one effective `token_endpoint_auth_method`; grant and response registrations remain strict. Omitted metadata uses the RFC 7591 defaults: `client_secret_basic`, `grant_types: ["authorization_code"]`, and `response_types: ["code"]`. The token endpoint enforces each client's registered grant types with `unauthorized_client`; `refresh_token` is implied by `authorization_code`, and a client must register `urn:ietf:params:oauth:grant-type:token-exchange` to use token exchange.
328
375
 
329
376
  The effective `token_endpoint_auth_method` returned by registration is enforced exactly. When both authentication metadata fields are omitted, no explicit-method marker is stored and the client may use either `client_secret_basic` or `client_secret_post`, provided the same stored secret validates. Client records written by earlier releases have no marker and receive the same compatibility. This never crosses between `none` and a secret method and does not apply to CIMD clients.
330
377
 
@@ -332,7 +379,7 @@ Calling `OAuthHelpers.updateClient()` with `tokenEndpointAuthMethod` adds the ma
332
379
 
333
380
  Related options:
334
381
 
335
- - `clientRegistrationTTL` controls the lifetime of dynamically registered clients. The default is 90 days.
382
+ - `clientRegistrationTTL` controls the lifetime of dynamically registered clients. The default is 90 days. A registration still in use does not expire: once it has passed half its lifetime, the next successful token request renews it for the full TTL, so a client that keeps refreshing keeps its `client_id` while an abandoned one is cleaned up. The `client_secret_expires_at` returned at registration describes the initial lifetime; there is no channel to report a renewal, so a client that honours it re-registers on that schedule as before.
336
383
  - `disallowPublicClientRegistration` rejects DCR clients using `token_endpoint_auth_method: "none"`.
337
384
  - `clientRegistrationCallback` can allow or reject registration based on application policy.
338
385
 
@@ -352,21 +399,53 @@ allowPlainPKCE: true;
352
399
 
353
400
  The provider owns `tokenEndpoint`. It exchanges authorization codes for tokens, refreshes access tokens, and handles RFC 7009 revocation. Refresh tokens rotate on use. The immediately previous token remains valid until its replacement is first used, allowing a client to retry after losing a refresh response.
354
401
 
402
+ A grant expires `refreshTokenTTL` seconds after the code exchange (30 days by default) however often it is refreshed. Set `refreshTokenIdleTTL` to make that lifetime slide instead: each successful refresh moves the expiry to that many seconds later, so a grant lives while the client keeps using it and expires once idle. `tokenExchangeCallback` can return `refreshTokenIdleTTL` to set the lifetime for one refresh, which lets a Worker that proxies an upstream OAuth service match the lifetime of the upstream refresh token it just rotated. See [Advanced configuration](docs/advanced-configuration.md#token-and-client-lifetimes).
403
+
355
404
  ## Resources and token audiences
356
405
 
357
- MCP clients are required to send the canonical MCP server URI as `resource` in authorization and token requests. The provider tolerates omission for compatibility: when `resourceMetadata.resource` is configured, it is used as the canonical default and inherited by later token requests; otherwise a token request inherits any resource already stored on the grant. An explicit resource that does not match a bound grant is rejected with `invalid_target`.
406
+ An authorization server may register one or more protected resources. Each resource has one canonical `resourceMetadata.resource`: an absolute HTTPS URI without a fragment, with lowercase `https` and a lowercase host, and an RFC 3986-safe producer serialization. Userinfo, default ports, dot-segment paths, and an empty path before a query are rejected because `Request` would rewrite them before RFC 9728 comparison. A bare origin is the only empty-path exception; use `/` before a query. Query components are supported but discouraged by RFC 9728.
407
+
408
+ For local development, `http` is accepted for resources, `authorization_servers`, the explicit `OAuthAuthorizationServer` issuer, and absolute endpoint URLs only when the host is a loopback address (`localhost`, `127.0.0.0/8`, `::1`), so `wrangler dev` works at `http://localhost:8787`. Any other host must use `https`: Workers are always served over `https`, and OAuth 2.1 requires it. A local MCP client's loopback redirect URI is unaffected by this rule; it is governed by the RFC 8252 loopback handling described under client registration.
358
409
 
359
- Legacy grants may have no stored resource. With no configured canonical resource, omitting `resource` preserves that unbound state. If a client supplies a resource during code exchange or refresh, it applies to that issued token but is not persisted as a new grant binding. Path-aware audiences use path-boundary prefix matching, so a token for `https://example.com/mcp` can be used at `/mcp/tools`, but not at `/mcp-other`.
410
+ Every authorization grant and access token is bound to exactly one registered resource. A central authorization server can therefore issue separate Calendar and Drive tokens from one KV namespace, but it never turns those into one multi-audience bearer token. Completing a new authorization for Drive does not replace the same user and client's Calendar grant.
360
411
 
361
- `resourceMatchOriginOnly` is deprecated; its existing behavior is unchanged. Prefer `resourceMetadata.resource` for new deployments.
412
+ Conforming MCP clients are required to send `resource` in authorization and token requests. Resource selection and compatibility work as follows:
413
+
414
+ - When the authorization server has one registered resource, that sole resource is selected if an authorization request omits `resource`. This preserves existing `OAuthProvider` behavior.
415
+ - When it has multiple registered resources, an authorization request must identify exactly one of them. Set `defaultResource` on `OAuthAuthorizationServer` only when older clients that omit `resource` should be routed to a deliberate compatibility default.
416
+ - An authorization-code or refresh-token request may omit `resource`; the server inherits the resource already stored on the grant. If present, it must match that grant and cannot retarget it.
417
+ - Malformed, unknown, or multi-valued resource input returns `invalid_target` before code consumption, callbacks, refresh rotation, or storage writes.
418
+
419
+ ASCII case differences in the URI scheme and host are accepted, but port, path, query, trailing slash, and array cardinality remain strict. The authorization server always stores and returns the configured lowercase scheme-and-host spelling. The token response includes the selected resource, and the access-token audience contains that resource alone.
420
+
421
+ Token exchange cannot change the resource. Both the subject-token audience and any explicit requested resource must resolve to the same registered canonical value. A token is exchanged by the client its grant was issued to unless `tokenExchangeCallback` returns `allowCrossClientExchange: true`. Internally and externally validated tokens are accepted at a protected route only when their audience matches that route's resource.
422
+
423
+ Path-aware API validation uses path-boundary prefix matching. A canonical audience for `https://example.com/mcp` covers `/mcp` and `/mcp/tools`, but not `/mcp-other`. A canonical trailing slash remains significant.
424
+
425
+ ### Upgrading to 1.0
426
+
427
+ The step-by-step guide, including an "Am I affected" checklist and an agent skill (`skills/migrate-to-1.0/`), is [docs/migration-1.0.md](docs/migration-1.0.md). The compatibility rules it relies on:
428
+
429
+ The existing combined `OAuthProvider` configuration has one `resourceMetadata.resource`. That sole resource automatically acts as both the omitted-authorization default and the migration destination for grants created before resource binding, so existing single-resource clients can continue without adding a `resource` parameter.
430
+
431
+ For a multi-resource `OAuthAuthorizationServer`, `defaultResource` and `legacyGrantResource` solve different compatibility problems:
432
+
433
+ - `defaultResource` selects the resource for a new authorization request that omits `resource`.
434
+ - `legacyGrantResource` is the server-controlled migration destination for an old stored grant or access token that has no resource. A client-supplied token-request parameter cannot choose or change this destination. It is deployment policy rather than an issuance-time claim, so changing it re-targets every surviving unbound record; keep it fixed for the migration window.
435
+
436
+ Both values must name a declared resource and are checked at construction. If a multi-resource server omits `legacyGrantResource`, an old unbound grant cannot be migrated safely. A stored grant already bound to a registered resource keeps that resource, and a stored 0.x array that contains the registered resource resolves to it. A grant bound only to unregistered values fails its refresh with `invalid_grant`, which conformant clients answer by starting a new authorization.
437
+
438
+ Previously issued access tokens with no audience keep working until they expire. They are treated as bound to the server-selected migration resource (the sole resource, or `legacyGrantResource`), and refresh binds the grant and returns a bound replacement token. A multi-resource server without `legacyGrantResource` has no safe destination, so it rejects such tokens and their refresh grants must be reauthorized. Multiple resources can share the same authorization server, provider implementation, and KV namespace; separate storage is an optional deployment boundary, not a resource-binding requirement.
439
+
440
+ The 1.0 API removes `resourceMatchOriginOnly`, and a configuration that still sets it fails at construction. Canonical matching with scheme/host case tolerance replaces it.
362
441
 
363
442
  ## Scopes and step-up authorization
364
443
 
365
- `scopesSupported` is published only in authorization server metadata. Configure `resourceMetadata.scopes_supported` explicitly with the minimal scopes required for basic protected-resource functionality and baseline Bearer challenges.
444
+ `scopesSupported` is published only in authorization server metadata. Configure each protected resource's `resourceMetadata.scopes_supported` explicitly with the minimal scopes required for its basic functionality and baseline Bearer challenges.
366
445
 
367
446
  The application decides which requested scopes to grant through `completeAuthorization({ scope })`. Token and refresh requests can only narrow those scopes.
368
447
 
369
- The provider does not expose a standard effective-token authorization context to API handlers or enforce operation-level scope policy. Protected resource metadata supplies baseline scope guidance in Bearer challenges. Advanced integrations can provide operation-specific step-up guidance through external-token validation.
448
+ Both hosts name `scopes_supported` in the initial `401` challenge, so a client asks for the right scopes first time. Operation-level policy stays in the handler, which reads the token's scopes from `ctx.auth.scope` and answers a shortfall with `insufficientScope(ctx.auth, ['files:write'])`: `403`, `error="insufficient_scope"`, every scope the operation needs in one challenge, and the resource's metadata URL. See [docs/resource-servers.md](docs/resource-servers.md#what-the-handler-sees).
370
449
 
371
450
  ## Advanced features
372
451
 
@@ -378,6 +457,7 @@ The package also supports:
378
457
  - Structured callback errors through the exported `OAuthError` and `ExternalTokenError` classes.
379
458
  - Custom error observation or responses through `onError`.
380
459
  - Experimental MCP Enterprise-Managed Authorization using ID-JAG assertions.
460
+ - One authorization server with multiple same-Worker or separately routed MCP resources.
381
461
  - Multiple protected handlers through `apiHandlers`.
382
462
  - Configurable access token, refresh token, and DCR client lifetimes.
383
463
 
@@ -393,6 +473,8 @@ Sensitive values are not stored in plaintext:
393
473
 
394
474
  See [storage-schema.md](https://github.com/cloudflare/workers-oauth-provider/blob/main/storage-schema.md) for the complete KV layout.
395
475
 
476
+ By default `completeAuthorization()` revokes the user's earlier grants for the same client and resource. It finds them from KV key metadata that every grant written by 1.0 or later carries, so the cost is one `list()` per thousand grants the user has, not a read per grant. Grants written before 1.0 are read individually, `revokeExistingGrantsBatchSize` at a time (default 50), until a refresh rewrites them with metadata.
477
+
396
478
  KV TTLs remove expiring records automatically. `purgeExpiredData()` provides a manual sweep for orphaned or expired grants and tokens:
397
479
 
398
480
  ```ts
@@ -417,29 +499,41 @@ Deleting a client through `OAuthHelpers.deleteClient()` also revokes its grants
417
499
 
418
500
  ## Configuration reference
419
501
 
420
- | Option | Purpose | Default |
421
- | ---------------------------------- | -------------------------------------------------------- | ------------------------------------------- |
422
- | `apiRoute` and `apiHandler` | Protect one or more route prefixes with one handler | Use these or `apiHandlers` |
423
- | `apiHandlers` | Map protected route prefixes to different handlers | Use this or `apiRoute` plus `apiHandler` |
424
- | `defaultHandler` | Handle authorization UI and other unprotected routes | Required |
425
- | `authorizeEndpoint` | Application-owned authorization and consent endpoint | Required |
426
- | `tokenEndpoint` | Provider-owned token and revocation endpoint | Required |
427
- | `clientRegistrationEndpoint` | Enable RFC 7591 DCR | Disabled |
428
- | `scopesSupported` | Publish authorization server scopes | Omitted |
429
- | `resourceMetadata` | Configure RFC 9728 metadata | Derived from the request and token endpoint |
430
- | `clientIdMetadataDocumentEnabled` | Enable CIMD lookup and advertisement | `false` |
431
- | `allowPlainPKCE` | Permit the legacy plain PKCE method | `false` |
432
- | `allowImplicitFlow` | Enable implicit token responses | `false` |
433
- | `disallowPublicClientRegistration` | Reject public clients at DCR | `false` |
434
- | `clientRegistrationCallback` | Apply application policy before storing a DCR client | None |
435
- | `allowTokenExchangeGrant` | Enable RFC 8693 | `false` |
436
- | `tokenExchangeCallback` | Update props, scopes, or lifetimes during token exchange | None |
437
- | `resolveExternalToken` | Validate external bearer credentials (advanced) | None |
438
- | `resourceMatchOriginOnly` | Deprecated origin-only resource comparison | `false` |
439
- | `enterpriseManagedAuthorization` | Enable experimental ID-JAG grant support | Disabled |
440
- | `onError` | Observe or replace OAuth error responses | Logs a warning |
441
-
442
- Consult the exported `OAuthProviderOptions`, callback interfaces, and JSDoc in [`src/oauth-provider.ts`](https://github.com/cloudflare/workers-oauth-provider/blob/main/src/oauth-provider.ts) for the complete typed API.
502
+ The existing `OAuthProvider` combined configuration uses these options:
503
+
504
+ | Option | Purpose | Default |
505
+ | ---------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------- |
506
+ | `apiRoute` and `apiHandler` | Protect one or more route prefixes with one handler | Use these or `apiHandlers` |
507
+ | `apiHandlers` | Map protected route prefixes to different handlers | Use this or `apiRoute` plus `apiHandler` |
508
+ | `defaultHandler` | Handle authorization UI and other unprotected routes | Required |
509
+ | `authorizeEndpoint` | Application-owned authorization and consent endpoint | Required |
510
+ | `tokenEndpoint` | Provider-owned token and revocation endpoint | Required |
511
+ | `clientRegistrationEndpoint` | Enable RFC 7591 DCR | Disabled |
512
+ | `scopesSupported` | Publish authorization server scopes | Omitted |
513
+ | `resourceMetadata.resource` | Canonical HTTPS resource and token audience | Required |
514
+ | `clientIdMetadataDocumentEnabled` | Enable CIMD lookup and advertisement | `false` |
515
+ | `allowPlainPKCE` | Permit the legacy plain PKCE method | `false` |
516
+ | `allowImplicitFlow` | Enable implicit token responses | `false` |
517
+ | `disallowPublicClientRegistration` | Reject public clients at DCR | `false` |
518
+ | `clientRegistrationCallback` | Apply application policy before storing a DCR client | None |
519
+ | `allowTokenExchangeGrant` | Enable RFC 8693 | `false` |
520
+ | `tokenExchangeCallback` | Update props, scopes, or lifetimes during token exchange | None |
521
+ | `resolveExternalToken` | Validate external bearer credentials (advanced) | None |
522
+ | `enterpriseManagedAuthorization` | Enable experimental ID-JAG grant support | Disabled |
523
+ | `onError` | Observe or replace OAuth error responses; `internal` names the failed check | Logs a warning |
524
+
525
+ The functional role API adds these surfaces without removing `OAuthProvider`:
526
+
527
+ | Surface | Purpose |
528
+ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
529
+ | `new OAuthAuthorizationServer({ issuer, resources, … })` | Create the AS role with a canonical RFC 8414 issuer and its fixed resource registry |
530
+ | `validateToken(resource, token, env)` | Validate an access token for one declared resource; what a resource server calls |
531
+ | `defaultResource` | Select a deliberate default for new authorization requests that omit it |
532
+ | `legacyGrantResource` | Select the server-controlled migration target for old unbound grants |
533
+ | `getOAuthApi(env)` | Obtain OAuth helpers for an application-owned authorization route |
534
+ | `new OAuthResourceServer({ … })` | Host one resource, in this Worker or another; `validateToken` points at the AS or a binding |
535
+
536
+ Consult the exported `OAuthProviderOptions`, `OAuthAuthorizationServerOptions`, resource-server callback interfaces, and JSDoc in [`src/oauth-provider.ts`](https://github.com/cloudflare/workers-oauth-provider/blob/main/src/oauth-provider.ts) for the complete typed API.
443
537
 
444
538
  ## OAuth helpers
445
539