@better-auth/oauth-provider 1.7.0-beta.5 → 1.7.0-beta.6

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
+ import * as z from "zod";
1
2
  import { PrivateKeyJwtSigningAlgorithm } from "@better-auth/core/oauth2";
2
- import { JWSAlgorithms } from "better-auth/plugins";
3
3
  import { JWTPayload } from "jose";
4
+ import { JWSAlgorithms } from "better-auth/plugins";
4
5
  import { InferOptionSchema, Session, User } from "better-auth/types";
5
6
  import { GenericEndpointContext, LiteralString } from "@better-auth/core";
6
7
 
@@ -140,6 +141,11 @@ declare const schema: {
140
141
  type: "boolean";
141
142
  required: false;
142
143
  };
144
+ dpopBoundAccessTokens: {
145
+ type: "boolean";
146
+ required: false;
147
+ defaultValue: false;
148
+ };
143
149
  referenceId: {
144
150
  type: "string";
145
151
  required: false;
@@ -150,6 +156,141 @@ declare const schema: {
150
156
  };
151
157
  };
152
158
  };
159
+ /**
160
+ * A protected resource the AS issues access tokens for.
161
+ *
162
+ * Promotes protected resources into a first-class persisted entity with
163
+ * per-resource token policy. A null value
164
+ * on any policy column means "inherit the plugin-level default at token
165
+ * issuance time" — admins can later override without re-seeding.
166
+ *
167
+ * @see RFC 8707 (Resource Indicators) — `identifier` is the `resource` parameter value
168
+ * @see RFC 9068 §2.2 — `customClaims` cannot override reserved JWT claims (enforced server-side)
169
+ */
170
+ oauthResource: {
171
+ modelName: string;
172
+ fields: {
173
+ identifier: {
174
+ type: "string";
175
+ required: true;
176
+ unique: true;
177
+ };
178
+ name: {
179
+ type: "string";
180
+ required: true;
181
+ };
182
+ accessTokenTtl: {
183
+ type: "number";
184
+ required: false;
185
+ };
186
+ refreshTokenTtl: {
187
+ type: "number";
188
+ required: false;
189
+ };
190
+ signingAlgorithm: {
191
+ type: "string";
192
+ required: false;
193
+ };
194
+ signingKeyId: {
195
+ type: "string";
196
+ required: false;
197
+ };
198
+ allowedScopes: {
199
+ type: "string[]";
200
+ required: false;
201
+ };
202
+ customClaims: {
203
+ type: "json";
204
+ required: false;
205
+ };
206
+ dpopBoundAccessTokensRequired: {
207
+ type: "boolean";
208
+ required: false;
209
+ defaultValue: false;
210
+ };
211
+ disabled: {
212
+ type: "boolean";
213
+ required: false;
214
+ defaultValue: false;
215
+ };
216
+ createdAt: {
217
+ type: "date";
218
+ required: false;
219
+ };
220
+ updatedAt: {
221
+ type: "date";
222
+ required: false;
223
+ };
224
+ policyVersion: {
225
+ type: "number";
226
+ required: false;
227
+ defaultValue: number;
228
+ };
229
+ metadata: {
230
+ type: "json";
231
+ required: false;
232
+ };
233
+ };
234
+ };
235
+ /**
236
+ * Join table — which clients are allowed to request which resources.
237
+ *
238
+ * Authoritative only when `enforcePerClientResources: true` on plugin options.
239
+ * When the flag is off, clients implicitly have access to all enabled resources
240
+ * (preserves pre-entity behavior).
241
+ *
242
+ * Composite uniqueness on `(clientId, resourceId)` is load-bearing — the
243
+ * `enforcePerClientResources` linkage check assumes one row per pair.
244
+ *
245
+ * Better Auth's schema layer doesn't expose composite-UNIQUE syntax (no
246
+ * way to declare `UNIQUE(clientId, resourceId)` at the column level). To
247
+ * enforce it at the database level for free, we set the row's `id` to a
248
+ * deterministic `${clientId}::${resourceId}` value at write time and let
249
+ * the implicit `UNIQUE` constraint on the primary key catch duplicates
250
+ * (see `buildClientResourceLinkId` and the `forceAllowId: true` flag on
251
+ * the `adapter.create` call in `oauthResource/endpoints.ts`). The double
252
+ * colon separator is chosen because `::` cannot appear in either a
253
+ * client_id (URL-safe random string) or a resource identifier (RFC 8707
254
+ * absolute URI — `::` would be an IPv6 form rejected by the validator),
255
+ * so the encoding is collision-free.
256
+ *
257
+ * Concurrent inserts of the same pair surface as a UNIQUE-constraint
258
+ * error which the endpoint catches and converts to a 200 "alreadyLinked"
259
+ * response (idempotency).
260
+ */
261
+ oauthClientResource: {
262
+ modelName: string;
263
+ fields: {
264
+ clientId: {
265
+ type: "string";
266
+ required: true;
267
+ references: {
268
+ model: string;
269
+ field: string;
270
+ onDelete: "cascade";
271
+ };
272
+ index: true;
273
+ };
274
+ resourceId: {
275
+ type: "string";
276
+ required: true;
277
+ references: {
278
+ model: string;
279
+ field: string;
280
+ onDelete: "cascade";
281
+ };
282
+ index: true;
283
+ };
284
+ metadata: {
285
+ type: "json";
286
+ required: false;
287
+ };
288
+ createdAt: {
289
+ type: "date";
290
+ required: false;
291
+ };
292
+ };
293
+ };
153
294
  /**
154
295
  * An opaque refresh token created with "offline_access"
155
296
  *
@@ -212,6 +353,10 @@ declare const schema: {
212
353
  type: "date";
213
354
  required: false;
214
355
  };
356
+ confirmation: {
357
+ type: "json";
358
+ required: false;
359
+ };
215
360
  scopes: {
216
361
  type: "string[]";
217
362
  required: true;
@@ -219,7 +364,7 @@ declare const schema: {
219
364
  };
220
365
  };
221
366
  /**
222
- * An opaque access token sent when there is no audience
367
+ * An opaque access token sent when there is no resource audience claim
223
368
  * to assigned to the JWT.
224
369
  *
225
370
  * Access tokens are linked to a session, better-auth
@@ -292,6 +437,10 @@ declare const schema: {
292
437
  type: "date";
293
438
  required: false;
294
439
  };
440
+ confirmation: {
441
+ type: "json";
442
+ required: false;
443
+ };
295
444
  scopes: {
296
445
  type: "string[]";
297
446
  required: true;
@@ -365,8 +514,78 @@ declare const schema: {
365
514
  //#region src/types/helpers.d.ts
366
515
  type Awaitable<T> = Promise<T> | T;
367
516
  //#endregion
517
+ //#region src/types/zod.d.ts
518
+ /**
519
+ * Validates an RFC 8707 resource indicator. The value must be an absolute URI
520
+ * with no fragment (RFC 8707 §2). Unlike a redirect URI it is not restricted to
521
+ * HTTPS, because a resource server identifier may use any absolute URI scheme;
522
+ * configured OAuth resources are the authoritative control over
523
+ * which resources a token may target.
524
+ */
525
+ declare const ResourceUriSchema: z.ZodString;
526
+ /**
527
+ * Request body accepted at `POST /oauth2/register` (RFC 7591 §2 client
528
+ * metadata). This is the single source of truth for the registration contract:
529
+ * the endpoint validates against it and {@link ClientRegistrationRequest} is
530
+ * inferred from it, so the type a `validateInitialAccessToken` callback receives
531
+ * always matches what is actually validated. `grant_types` and
532
+ * `token_endpoint_auth_method` are open strings because extensions can register
533
+ * custom values. Server-assigned fields (`client_id`, `client_secret`, the
534
+ * issued/expiry timestamps) and internal state (`disabled`, `reference_id`) are
535
+ * never part of a registration request.
536
+ *
537
+ * @see https://datatracker.ietf.org/doc/html/rfc7591#section-2
538
+ */
539
+ declare const clientRegistrationRequestSchema: z.ZodObject<{
540
+ redirect_uris: z.ZodOptional<z.ZodArray<z.ZodURL>>;
541
+ scope: z.ZodOptional<z.ZodString>;
542
+ client_name: z.ZodOptional<z.ZodString>;
543
+ client_uri: z.ZodOptional<z.ZodString>;
544
+ logo_uri: z.ZodOptional<z.ZodString>;
545
+ contacts: z.ZodOptional<z.ZodArray<z.ZodString>>;
546
+ tos_uri: z.ZodOptional<z.ZodString>;
547
+ policy_uri: z.ZodOptional<z.ZodString>;
548
+ software_id: z.ZodOptional<z.ZodString>;
549
+ software_version: z.ZodOptional<z.ZodString>;
550
+ software_statement: z.ZodOptional<z.ZodString>;
551
+ post_logout_redirect_uris: z.ZodOptional<z.ZodArray<z.ZodURL>>;
552
+ backchannel_logout_uri: z.ZodOptional<z.ZodURL>;
553
+ backchannel_logout_session_required: z.ZodOptional<z.ZodBoolean>;
554
+ token_endpoint_auth_method: z.ZodOptional<z.ZodString>;
555
+ jwks: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>, z.ZodObject<{
556
+ keys: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
557
+ }, z.core.$strip>]>>;
558
+ jwks_uri: z.ZodOptional<z.ZodString>;
559
+ grant_types: z.ZodOptional<z.ZodArray<z.ZodString>>;
560
+ response_types: z.ZodOptional<z.ZodArray<z.ZodEnum<{
561
+ code: "code";
562
+ }>>>;
563
+ type: z.ZodOptional<z.ZodEnum<{
564
+ web: "web";
565
+ native: "native";
566
+ "user-agent-based": "user-agent-based";
567
+ }>>;
568
+ subject_type: z.ZodOptional<z.ZodEnum<{
569
+ public: "public";
570
+ pairwise: "pairwise";
571
+ }>>;
572
+ dpop_bound_access_tokens: z.ZodOptional<z.ZodBoolean>;
573
+ resources: z.ZodOptional<z.ZodArray<z.ZodString>>;
574
+ skip_consent: z.ZodOptional<z.ZodNever>;
575
+ }, z.core.$strip>;
576
+ /**
577
+ * Client metadata as submitted in an RFC 7591 §2 registration request, inferred
578
+ * from {@link clientRegistrationRequestSchema}. Every value is self-asserted by
579
+ * the caller (RFC 7591 §5) and is the raw request before registration defaults
580
+ * are applied, so a `validateInitialAccessToken` callback should treat it as
581
+ * untrusted and not assume defaulted fields are present.
582
+ *
583
+ * @see https://datatracker.ietf.org/doc/html/rfc7591#section-2
584
+ */
585
+ type ClientRegistrationRequest = z.infer<typeof clientRegistrationRequestSchema>;
586
+ //#endregion
368
587
  //#region src/types/index.d.ts
369
- type StoreTokenType = "access_token" | "refresh_token" | "authorization_code";
588
+ type StoreTokenType = "access_token" | "refresh_token" | "authorization_code" | (string & {});
370
589
  type InternallySupportedScopes = "openid" | "profile" | "email" | "offline_access";
371
590
  type Scope = LiteralString | InternallySupportedScopes;
372
591
  type Prompt = "none" | "consent" | "login" | "create" | "select_account";
@@ -376,11 +595,11 @@ type AuthorizePrompt = Prompt | "login consent" | "select_account consent";
376
595
  * metadata document, a federated registry, an attestation header, etc.) and
377
596
  * what fields that source contributes to discovery metadata.
378
597
  *
379
- * Plugins install one of these onto {@link OAuthOptions.clientDiscovery}.
380
- * The host walks the configured entries in order and returns the first
381
- * non-null `resolve()` result.
598
+ * Plugins contribute one of these through
599
+ * {@link OAuthProviderExtension.clientDiscovery}. The host walks every
600
+ * configured entry in order and returns the first non-null `resolve()` result.
382
601
  */
383
- interface ClientDiscovery<Scopes extends readonly Scope[] = InternallySupportedScopes[]> {
602
+ interface ClientDiscovery {
384
603
  /**
385
604
  * Stable identifier used in error messages and diagnostics. Convention
386
605
  * is to match the plugin id (for example `"cimd"`).
@@ -402,7 +621,7 @@ interface ClientDiscovery<Scopes extends readonly Scope[] = InternallySupportedS
402
621
  * - `null`: `getClient()` falls through to the next matching discovery
403
622
  * or to the database record (if any).
404
623
  */
405
- resolve: (ctx: GenericEndpointContext, clientId: string, existing: SchemaClient<Scopes> | null) => Awaitable<SchemaClient<Scopes> | null>;
624
+ resolve: (ctx: GenericEndpointContext, clientId: string, existing: SchemaClient<Scope[]> | null) => Awaitable<SchemaClient<Scope[]> | null>;
406
625
  /**
407
626
  * Fields merged into `/.well-known/oauth-authorization-server` and
408
627
  * `/.well-known/openid-configuration` responses. Useful for advertising
@@ -411,6 +630,318 @@ interface ClientDiscovery<Scopes extends readonly Scope[] = InternallySupportedS
411
630
  */
412
631
  discoveryMetadata?: Record<string, unknown>;
413
632
  }
633
+ interface OAuthAuthenticatedClient {
634
+ clientId: string;
635
+ client: SchemaClient<Scope[]>;
636
+ method?: TokenEndpointAuthMethod;
637
+ /**
638
+ * A sender-constraint the authentication step already proved (for example a
639
+ * wallet-instance key thumbprint). Pass it to `issueTokens` as
640
+ * {@link OAuthTokenIssueParams.confirmation} to bind the issued token to it.
641
+ * The authorization server writes this as token material and does not verify
642
+ * it again, so a strategy must set it only after proving possession.
643
+ */
644
+ confirmation?: Confirmation;
645
+ }
646
+ interface OAuthClientAuthenticationRequest {
647
+ /**
648
+ * Scopes to validate against the registered client.
649
+ */
650
+ scopes?: string[];
651
+ /**
652
+ * Set to `false` for public extension grants that only require client_id.
653
+ *
654
+ * @default true
655
+ */
656
+ requireCredentials?: boolean;
657
+ }
658
+ interface OAuthTokenIssueParams {
659
+ client: SchemaClient<Scope[]>;
660
+ scopes: string[];
661
+ user?: User;
662
+ referenceId?: string;
663
+ sessionId?: string;
664
+ nonce?: string;
665
+ refreshToken?: OAuthRefreshToken<Scope[]> & {
666
+ id: string;
667
+ };
668
+ authTime?: Date;
669
+ verificationValue?: VerificationValue;
670
+ resources?: string[];
671
+ /** Full original authorized resources for the grant, used to seed refresh tokens. */
672
+ originalResources?: string[];
673
+ /**
674
+ * Additional JWT access-token claims for this single issuance.
675
+ *
676
+ * JWT-only: these are baked into the signed token at mint. Opaque access
677
+ * tokens persist no per-issuance claims, so they do NOT reappear at
678
+ * introspection. A claim that must be visible at opaque-token introspection
679
+ * belongs in a grant-type-stable `claims.accessToken` contributor instead,
680
+ * which the introspection path re-derives. Reserved RFC 9068 claim names stay
681
+ * owned by the authorization server.
682
+ */
683
+ accessTokenClaims?: Record<string, unknown>;
684
+ /**
685
+ * Additional ID-token claims for this single issuance. Additive: they cannot
686
+ * replace identity, authentication-context, or AS-owned claims.
687
+ */
688
+ idTokenClaims?: Record<string, unknown>;
689
+ /**
690
+ * Additional fields for the token response envelope. Standard OAuth token
691
+ * response fields stay owned by the authorization server.
692
+ */
693
+ tokenResponse?: Record<string, unknown>;
694
+ /**
695
+ * Sender-constraint to bind this issuance to (RFC 7800 `cnf`). When set, the
696
+ * issuer stamps it as the access token's `cnf` and derives `token_type` from
697
+ * it, instead of leaving the token a bearer token. Use it to carry a
698
+ * confirmation a client-auth strategy or an out-of-band flow already proved
699
+ * (see {@link OAuthAuthenticatedClient.confirmation}). `cnf` is AS-owned: it
700
+ * is stamped after, and cannot be overridden by, contributed claims.
701
+ */
702
+ confirmation?: Confirmation;
703
+ }
704
+ interface OAuthTokenResponse {
705
+ access_token: string;
706
+ expires_in: number;
707
+ expires_at: number;
708
+ token_type: TokenType;
709
+ refresh_token: string | undefined;
710
+ scope: string;
711
+ id_token: string | undefined;
712
+ [key: string]: unknown;
713
+ }
714
+ type ActiveAccessTokenPayload = JWTPayload & {
715
+ active: true;
716
+ };
717
+ /**
718
+ * The OAuth Provider's server-side capability surface, bound to a request `ctx`.
719
+ * A grant handler receives one as `provider`; a companion plugin's own endpoint
720
+ * obtains one with `getOAuthProviderApi(ctx, opts, grantType?)`. The same object
721
+ * serves both, so issuance, client resolution, and token verification behave the
722
+ * same inside and outside a grant.
723
+ */
724
+ interface OAuthProviderApi {
725
+ /**
726
+ * Resolves a registered client by id, consulting extension client-discovery
727
+ * sources. Returns `null` when no client matches.
728
+ */
729
+ getClient: (clientId: string) => Awaitable<SchemaClient<Scope[]> | null>;
730
+ /**
731
+ * Authenticates the calling client from the request (client secret, assertion,
732
+ * or none). For assertion-based methods, the RFC 7523 audience is bound to the
733
+ * endpoint serving the request, so an assertion cannot be replayed across
734
+ * endpoints. Returns the authenticated client, plus any `confirmation` an
735
+ * assertion strategy proved.
736
+ */
737
+ authenticateClient: (request?: OAuthClientAuthenticationRequest) => Awaitable<OAuthAuthenticatedClient>;
738
+ /**
739
+ * Issues the token set for this grant (access token, optional refresh token,
740
+ * optional ID token, resource policy, response envelope, and any
741
+ * sender-constraint). The grant type is fixed by the `getOAuthProviderApi`
742
+ * binding (the dispatcher's grant for an in-grant handler, the caller's grant
743
+ * for an out-of-grant endpoint), so it cannot be mislabeled per issuance.
744
+ *
745
+ * Authorization is the caller's responsibility. The authorization server does
746
+ * NOT re-check that `params.scopes`, `params.user`, or `params.resources` are
747
+ * a subset of what `authenticateClient` validated: this is a raw minting
748
+ * primitive, and the built-in grants each validate scopes themselves before
749
+ * calling it.
750
+ */
751
+ issueTokens: (params: OAuthTokenIssueParams) => Awaitable<OAuthTokenResponse>;
752
+ /**
753
+ * Computes the stored lookup key for a token value (the same hash the
754
+ * provider persists), so a caller can find or revoke a previously issued
755
+ * opaque token by its value.
756
+ */
757
+ hashToken: (token: string, type: StoreTokenType) => Awaitable<string>;
758
+ /**
759
+ * Validates an access token for introspection-style callers. The returned
760
+ * payload can be inactive; protected-resource endpoints should use
761
+ * `requireActiveAccessToken`.
762
+ */
763
+ validateAccessToken: (token: string, clientId?: string) => Awaitable<JWTPayload>;
764
+ /**
765
+ * Validates an access token for a protected resource and throws the OAuth
766
+ * bearer challenge when the token is inactive or unknown.
767
+ */
768
+ requireActiveAccessToken: (token: string, clientId?: string) => Awaitable<ActiveAccessTokenPayload>;
769
+ }
770
+ interface OAuthExtensionGrantHandlerInput {
771
+ ctx: GenericEndpointContext;
772
+ opts: OAuthOptions<Scope[]>;
773
+ grantType: GrantType;
774
+ /** The provider capability surface, pre-bound to this grant's `grantType`. */
775
+ provider: OAuthProviderApi;
776
+ }
777
+ type OAuthExtensionGrantHandler = (input: OAuthExtensionGrantHandlerInput) => Awaitable<OAuthTokenResponse>;
778
+ interface OAuthClientAuthenticationInput {
779
+ ctx: GenericEndpointContext;
780
+ opts: OAuthOptions<Scope[]>;
781
+ assertion: string;
782
+ assertionType: string;
783
+ clientId?: string;
784
+ /**
785
+ * The endpoint URL the assertion was presented to. A strategy MUST bind the
786
+ * assertion to this audience (see {@link OAuthClientAuthenticationStrategy.authenticate}).
787
+ */
788
+ expectedAudience?: string;
789
+ }
790
+ interface OAuthClientAuthenticationResult {
791
+ /** The client id the assertion proved the caller controls. */
792
+ clientId: string;
793
+ /**
794
+ * A sender-constraint the strategy proved (for example a wallet-instance key
795
+ * thumbprint). The provider stamps it as the issued token's RFC 7800 `cnf`.
796
+ * Set it only after proving possession; the authorization server writes it as
797
+ * token material and does not verify it again.
798
+ */
799
+ confirmation?: Confirmation;
800
+ }
801
+ interface OAuthClientAuthenticationStrategy {
802
+ /**
803
+ * Assertion type URIs this strategy consumes from `client_assertion_type`.
804
+ * Values must be absolute URIs per RFC 7521. When omitted, the strategy key
805
+ * in `OAuthProviderExtension.clientAuthentication` is used and must also be
806
+ * an absolute URI.
807
+ */
808
+ assertionTypes?: string[];
809
+ /**
810
+ * Verifies the presented assertion and returns the proven client id (plus any
811
+ * sender-constraint it established). The strategy proves the caller controls
812
+ * `clientId`; it does not supply the authorization record. The provider
813
+ * resolves and authorizes the client itself, so a strategy cannot influence
814
+ * the client's grants, scopes, or enabled state.
815
+ *
816
+ * The strategy owns the full RFC 7521/7523 verification. After verifying the
817
+ * signature against its own key source, it MUST enforce the assertion-hygiene
818
+ * checks the built-in `private_key_jwt` path enforces, or the provider will
819
+ * accept a forged or replayed assertion:
820
+ * - bind the assertion to `input.expectedAudience` (RFC 7523 §3 rule 3),
821
+ * - require a bounded `exp` (RFC 7523 §3 rule 4),
822
+ * - reject replays via a single-use `jti`.
823
+ *
824
+ * The exported `consumeClientAssertion` helper performs the audience,
825
+ * lifetime, and `jti` single-use checks for a decoded payload; call it after
826
+ * signature verification so an extension method inherits the same guarantees
827
+ * as `private_key_jwt`.
828
+ */
829
+ authenticate: (input: OAuthClientAuthenticationInput) => Awaitable<OAuthClientAuthenticationResult>;
830
+ }
831
+ interface OAuthMetadataExtensionInput {
832
+ ctx: GenericEndpointContext;
833
+ opts: OAuthOptions<Scope[]>;
834
+ type: "oauth-authorization-server" | "openid-configuration";
835
+ /**
836
+ * The discovery document the provider assembled (core authorization-server
837
+ * fields plus any client-discovery metadata). Contributions from other
838
+ * extensions are merged afterwards and are not reflected here, so a
839
+ * contributor decides what to add from provider state alone, independent of
840
+ * extension registration order. The contributor returns the fields to add.
841
+ */
842
+ document: AuthServerMetadata | OIDCMetadata;
843
+ }
844
+ interface OAuthClaimExtensionInput {
845
+ ctx: GenericEndpointContext;
846
+ opts: OAuthOptions<Scope[]>;
847
+ user?: (User & Record<string, unknown>) | null;
848
+ client: SchemaClient<Scope[]>;
849
+ scopes: string[];
850
+ grantType?: GrantType;
851
+ referenceId?: string;
852
+ resources?: string[];
853
+ /** Parsed client metadata, as returned by `parseClientMetadata`. */
854
+ metadata?: Record<string, unknown>;
855
+ }
856
+ interface OAuthUserInfoExtensionInput {
857
+ ctx: GenericEndpointContext;
858
+ opts: OAuthOptions<Scope[]>;
859
+ user: User & Record<string, unknown>;
860
+ scopes: string[];
861
+ jwt: JWTPayload;
862
+ client?: SchemaClient<Scope[]>;
863
+ }
864
+ /**
865
+ * What a companion plugin contributes to the OAuth Provider, registered through
866
+ * `extendOAuthProvider(ctx, extension)` (or the `oauthProvider({ extensions })`
867
+ * option).
868
+ *
869
+ * All five kinds live on one object so a single protocol plugin (for example
870
+ * RFC 8693 token exchange, which adds a grant, advertises metadata, and emits
871
+ * claims) registers atomically and the host validates the combined surface in
872
+ * one place. Every field is independently optional, so a single-concern plugin
873
+ * (such as `@better-auth/cimd`, which contributes only `clientDiscovery`) sets
874
+ * just the one it needs. This is the shape every future OAuth RFC plugin copies.
875
+ *
876
+ * Two contribution disciplines:
877
+ * - Dispatched kinds (`grants`, `clientAuthentication`) must be disjoint across
878
+ * extensions: registering a grant type, auth method, or assertion type that
879
+ * another extension already registered is rejected at setup, since the second
880
+ * would otherwise be silently unreachable.
881
+ * - Additive kinds (`metadata`, `claims`) never override authorization-server
882
+ * core; a key already owned by the provider is kept, and a key two extensions
883
+ * both contribute resolves to the first-registered extension.
884
+ */
885
+ interface OAuthProviderExtension {
886
+ /**
887
+ * Token grants keyed by absolute-URI `grant_type`. The token endpoint
888
+ * dispatches a matching `grant_type` to the handler, which authenticates the
889
+ * client and issues tokens through the shared `provider`.
890
+ */
891
+ grants?: Record<string, OAuthExtensionGrantHandler>;
892
+ /**
893
+ * Assertion-based client authentication, keyed by the advertised
894
+ * `token_endpoint_auth_method`. Consumes the matching `client_assertion_type`
895
+ * at the token, introspection, and revocation endpoints. Built-in method
896
+ * names (`client_secret_basic`/`_post`, `private_key_jwt`, `none`) are
897
+ * reserved.
898
+ */
899
+ clientAuthentication?: Record<string, OAuthClientAuthenticationStrategy>;
900
+ /**
901
+ * Additional discovery metadata fields. Core fields (`issuer`,
902
+ * `token_endpoint`, advertised grants and auth methods, ...) stay owned by
903
+ * the provider; only absent keys are added. To advertise the claim names a
904
+ * claims contributor emits, set `advertisedMetadata.claims_supported`: the
905
+ * provider owns `claims_supported` and does not infer it from contributors.
906
+ */
907
+ metadata?: (input: OAuthMetadataExtensionInput) => Record<string, unknown>;
908
+ /**
909
+ * Additional claims for access tokens, ID tokens, and the UserInfo response.
910
+ * Strictly additive: a contributor can add new claims but never replace an
911
+ * identity, authentication-context, reserved RFC 9068, or other AS-owned
912
+ * claim. Access-token claims are re-derived at opaque-token introspection, so
913
+ * they must be grant-type-stable (a contributor receives `grantType:
914
+ * undefined` there). See the claim-authority overview in the docs for the
915
+ * full per-token precedence ladder.
916
+ */
917
+ claims?: {
918
+ accessToken?: (input: OAuthClaimExtensionInput) => Awaitable<Record<string, unknown>>;
919
+ idToken?: (input: OAuthClaimExtensionInput) => Awaitable<Record<string, unknown>>;
920
+ userInfo?: (input: OAuthUserInfoExtensionInput) => Awaitable<Record<string, unknown>>;
921
+ };
922
+ /**
923
+ * Client-id resolution sources consulted by `getClient()`, plus the
924
+ * discovery-metadata fields they advertise. Entries across all extensions
925
+ * run in order; the first to return a client wins. A plugin that resolves
926
+ * clients from an external source (a metadata-document URL, a federated
927
+ * registry, an attestation header) contributes it here.
928
+ */
929
+ clientDiscovery?: ClientDiscovery | ClientDiscovery[];
930
+ }
931
+ /**
932
+ * Result of authorizing an RFC 7591 initial access token, returned by
933
+ * {@link OAuthOptions.validateInitialAccessToken}.
934
+ */
935
+ interface InitialAccessTokenAuthorization {
936
+ /**
937
+ * Ownership reference to attach to the created OAuth client.
938
+ *
939
+ * Associates machine-provisioned clients with an organization, team, tenant,
940
+ * or other application-level owner. Omit to create an unowned client (the
941
+ * client is stored with neither a `user_id` nor a `reference_id`).
942
+ */
943
+ referenceId?: string;
944
+ }
414
945
  interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScopes[]> {
415
946
  /**
416
947
  * Custom schema definitions
@@ -429,15 +960,89 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
429
960
  */
430
961
  scopes?: Scopes;
431
962
  /**
432
- * List of valid audiences if there are multiple.
963
+ * Protected resources the AS issues access tokens for. Promotes the
964
+ * resource model into a first-class persisted entity with per-resource
965
+ * token policy.
966
+ *
967
+ * - String form: each string becomes an `oauthResource` row using plugin-level
968
+ * defaults.
969
+ * - Object form: explicit per-resource policy (TTL, signing alg, scope
970
+ * allowlist, custom claims, sender-constraint requirements).
433
971
  *
434
- * @default baseURL
435
- * @example [
436
- * "https://api.example.com",
437
- * "https://api.example.com/mcp",
972
+ * Seeding is keyed by `identifier`. Behavior on re-seed is controlled by
973
+ * {@link OAuthOptions.resourceSeedMode}.
974
+ *
975
+ * @see RFC 8707 — `identifier` is the `resource` parameter value
976
+ * @example
977
+ * ```ts
978
+ * resources: [
979
+ * { identifier: "https://api.example.com/admin", accessTokenTtl: 300,
980
+ * allowedScopes: ["admin:read", "admin:write"] },
981
+ * "https://api.example.com/public",
438
982
  * ]
983
+ * ```
439
984
  */
440
- validAudiences?: string[];
985
+ resources?: Array<string | OAuthResourceInput>;
986
+ /**
987
+ * Controls whether boot-time `resources` config overwrites DB-edited rows.
988
+ *
989
+ * - `"insertOnly"` (default, safe): only inserts rows whose `identifier` is
990
+ * not already present. Existing rows are untouched — admin edits via CRUD
991
+ * are never reverted on restart.
992
+ * - `"merge"`: inserts missing rows; updates only fields present in the
993
+ * config object for existing rows.
994
+ * - `"overwrite"`: inserts missing rows; replaces existing rows with the
995
+ * config values. Use only when the config is the source of truth.
996
+ *
997
+ * Defaults to the safe option to prevent accidental policy reverts in
998
+ * production deployments.
999
+ *
1000
+ * @default "insertOnly"
1001
+ */
1002
+ resourceSeedMode?: "insertOnly" | "merge" | "overwrite";
1003
+ /**
1004
+ * Opt-in cache membership for resources by `identifier`. Mirrors the
1005
+ * {@link OAuthOptions.cachedTrustedClients} pattern.
1006
+ *
1007
+ * Cached resources are invalidated on every CRUD write. Resources not in
1008
+ * this set are looked up from the DB on every request — the safe default
1009
+ * when admins edit rows through external tooling.
1010
+ */
1011
+ cachedResources?: Set<string>;
1012
+ /**
1013
+ * When true, `/oauth2/token` and `/oauth2/authorize` require the client to be
1014
+ * linked to every requested resource via `oauthClientResource`. When false,
1015
+ * clients implicitly have access to all enabled resources.
1016
+ *
1017
+ * Defaults to `true`, enabling per-client validation per RFC 8707 §3. An
1018
+ * explicit `false` keeps all enabled resources requestable by any client.
1019
+ *
1020
+ * The resolved value is logged at plugin init so admins see which default
1021
+ * applied.
1022
+ */
1023
+ enforcePerClientResources?: boolean;
1024
+ /**
1025
+ * Customize how a resource `identifier` is validated when resources are
1026
+ * created via CRUD or DCR. The default rejects non-URI identifiers per
1027
+ * RFC 8707 §2 (absolute URI, no fragment). Override only for trusted
1028
+ * internal use cases.
1029
+ *
1030
+ * @default RFC 8707 strict URI validator
1031
+ */
1032
+ identifierValidator?: (identifier: string) => Awaitable<boolean>;
1033
+ /**
1034
+ * RBAC on OAuth resources. Mirrors {@link OAuthOptions.clientPrivileges}.
1035
+ *
1036
+ * Gates the admin resource CRUD endpoints. Return `false` (or `undefined`)
1037
+ * to deny the action.
1038
+ */
1039
+ resourcePrivileges?: (context: {
1040
+ headers: Headers;
1041
+ action: "create" | "read" | "update" | "delete" | "list" | "link" | "unlink";
1042
+ user?: User & Record<string, unknown>;
1043
+ session?: Session & Record<string, unknown>;
1044
+ resourceId?: string;
1045
+ }) => Awaitable<boolean | undefined>;
441
1046
  /**
442
1047
  * Automatically cache trusted clients by client_id.
443
1048
  * Clients are cached at request.
@@ -520,26 +1125,60 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
520
1125
  */
521
1126
  allowUnauthenticatedClientRegistration?: boolean;
522
1127
  /**
523
- * Allow dynamic client registration.
1128
+ * Allow dynamic client registration (RFC 7591) at `POST /oauth2/register`.
1129
+ *
1130
+ * Once enabled, a registration request is authorized through one of three
1131
+ * modes:
1132
+ * - session-backed: a logged-in user with client-create privileges.
1133
+ * - token-backed: a valid initial access token, when
1134
+ * {@link OAuthOptions.validateInitialAccessToken} is defined.
1135
+ * - open public-only: unauthenticated registration constrained to public
1136
+ * clients, when {@link OAuthOptions.allowUnauthenticatedClientRegistration}
1137
+ * is enabled.
524
1138
  *
525
1139
  * @default false
526
1140
  */
527
1141
  allowDynamicClientRegistration?: boolean;
528
1142
  /**
529
- * Discovery implementations consulted by `getClient()` when resolving
530
- * a `client_id`. Each entry decides whether it handles the `client_id`
531
- * via {@link ClientDiscovery.matches}, then creates, refreshes, or
532
- * passes on a client record. Entries run in order; the first one to
533
- * return a client wins.
1143
+ * Validates an RFC 7591 initial access token for protected dynamic client
1144
+ * registration, read from the `Authorization: Bearer <token>` header on
1145
+ * `POST /oauth2/register`.
1146
+ *
1147
+ * Return an {@link InitialAccessTokenAuthorization} (optionally carrying a
1148
+ * `referenceId` owner) to authorize the registration, or `false` to reject
1149
+ * the token. Defining this callback enables the token-backed registration
1150
+ * mode; while it is undefined, a Bearer token presented to the endpoint is
1151
+ * rejected rather than downgraded to open registration.
534
1152
  *
535
- * Each entry also contributes {@link ClientDiscovery.discoveryMetadata}
536
- * into the `/.well-known/oauth-authorization-server` and
537
- * `/.well-known/openid-configuration` responses.
1153
+ * `clientMetadata` is the schema-validated request body. It is self-asserted
1154
+ * (RFC 7591 §5) and not yet semantically validated, so a request authorized
1155
+ * here may still be rejected by a later metadata check. Compare the token in
1156
+ * constant time; issuance, storage, expiration, and revocation are
1157
+ * deployment-specific in RFC 7591 and belong in your application.
1158
+ *
1159
+ * `headers` is the raw request `Headers`, available to correlate the token
1160
+ * with other request context (a tenant header, a forwarded client identity).
1161
+ *
1162
+ * With the `bearer` plugin enabled, a Bearer value that resolves to a valid
1163
+ * user session is handled as that session, not as an initial access token.
1164
+ *
1165
+ * @see InitialAccessTokenAuthorization
1166
+ */
1167
+ validateInitialAccessToken?: (context: {
1168
+ initialAccessToken: string;
1169
+ headers: Headers;
1170
+ clientMetadata: ClientRegistrationRequest;
1171
+ }) => Awaitable<InitialAccessTokenAuthorization | false>;
1172
+ /**
1173
+ * OAuth/OIDC extension points used by companion plugins to add protocol
1174
+ * grants, client authentication methods, metadata, claims, and client-id
1175
+ * discovery without modifying oauth-provider core for each RFC.
538
1176
  *
539
- * Plugins such as `@better-auth/cimd` install an entry here at init
540
- * time; users can also pass discovery implementations directly.
1177
+ * Extension plugins should prefer `extendOAuthProvider(ctx, extension)` in
1178
+ * their `init()` hook so users can compose plugins declaratively. Plugins
1179
+ * such as `@better-auth/cimd` contribute their client discovery this way.
541
1180
  */
542
- clientDiscovery?: ClientDiscovery<Scopes> | ClientDiscovery<Scopes>[];
1181
+ extensions?: OAuthProviderExtension[];
543
1182
  /**
544
1183
  * List of scopes for newly registered clients
545
1184
  * if not requested.
@@ -1122,6 +1761,27 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
1122
1761
  clientId: string;
1123
1762
  ctx: GenericEndpointContext;
1124
1763
  }) => Promise<Record<string, string> | null>;
1764
+ /**
1765
+ * DPoP proof validation settings.
1766
+ *
1767
+ * DPoP is enabled by default when a client or resource asks for DPoP-bound
1768
+ * access tokens. These values tune proof validation without changing that
1769
+ * contract.
1770
+ */
1771
+ dpop?: {
1772
+ /**
1773
+ * Accepted age of a DPoP proof JWT in seconds.
1774
+ *
1775
+ * @default 300
1776
+ */
1777
+ proofMaxAgeSeconds?: number;
1778
+ /**
1779
+ * Supported JWS algorithms for DPoP proof JWTs.
1780
+ *
1781
+ * @default ["EdDSA", "ES256", "ES512", "PS256", "RS256"]
1782
+ */
1783
+ signingAlgorithms?: JWSAlgorithms[];
1784
+ };
1125
1785
  }
1126
1786
  interface OAuthAuthorizationQuery {
1127
1787
  /**
@@ -1237,11 +1897,100 @@ interface OAuthAuthorizationQuery {
1237
1897
  * with the Claim Value being the nonce value sent in the Authentication Request.
1238
1898
  */
1239
1899
  nonce?: string;
1900
+ /**
1901
+ * RFC 9449 authorization request parameter. When present, the authorization
1902
+ * code is bound to this JWK thumbprint and the token request must present a
1903
+ * matching DPoP proof.
1904
+ */
1905
+ dpop_jkt?: string;
1240
1906
  /**
1241
1907
  * Resource parameter as specified by [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html)
1242
1908
  */
1243
1909
  resource?: string | string[];
1244
1910
  }
1911
+ /**
1912
+ * A persisted protected-resource row as stored in `oauthResource`.
1913
+ *
1914
+ * `null` on any policy column means "inherit the plugin-level default at
1915
+ * token issuance time" — admins can later override without re-seeding.
1916
+ */
1917
+ interface OAuthResource {
1918
+ /** Auto-generated primary key */
1919
+ id: string;
1920
+ /**
1921
+ * Business key used in the `aud` claim and as the RFC 8707 `resource` value.
1922
+ */
1923
+ identifier: string;
1924
+ /** Human-friendly label for admin UIs */
1925
+ name: string;
1926
+ /** Access token TTL in seconds; null inherits {@link OAuthOptions.accessTokenExpiresIn} */
1927
+ accessTokenTtl?: number | null;
1928
+ /** Refresh token TTL in seconds; null inherits {@link OAuthOptions.refreshTokenExpiresIn} */
1929
+ refreshTokenTtl?: number | null;
1930
+ /** When set, overrides the JWT plugin's getLatestKey() default at signing time. */
1931
+ signingAlgorithm?: JWSAlgorithms | null;
1932
+ signingKeyId?: string | null;
1933
+ /**
1934
+ * When non-null, requested scopes must intersect this set or the request is
1935
+ * rejected with `invalid_scope`.
1936
+ */
1937
+ allowedScopes?: string[] | null;
1938
+ /**
1939
+ * Per-resource claims merged into the access token JWT payload. Reserved
1940
+ * RFC 9068 claim names (`iss`, `sub`, `aud`, `exp`, `iat`, `jti`,
1941
+ * `client_id`, `scope`, `auth_time`, `acr`, `amr`) are stripped at issuance
1942
+ * with a warning log — never silently dropped.
1943
+ */
1944
+ customClaims?: Record<string, unknown> | null;
1945
+ /**
1946
+ * Require newly issued access tokens for this resource to be DPoP-bound.
1947
+ */
1948
+ dpopBoundAccessTokensRequired?: boolean;
1949
+ /**
1950
+ * Disabled → no new issuance for this resource; existing tokens still verify
1951
+ * until natural expiry. Compare to delete, which hard-rejects existing tokens.
1952
+ */
1953
+ disabled: boolean;
1954
+ createdAt: Date;
1955
+ updatedAt: Date;
1956
+ /**
1957
+ * Forward-migration anchor. Lets the runtime branch behavior when claim
1958
+ * emission or validation semantics change in a future PR without forcing
1959
+ * every row to migrate. PR 1 ships with `policyVersion = 1`.
1960
+ */
1961
+ policyVersion: number;
1962
+ /** Open-ended extension data — not yet promoted to columns. */
1963
+ metadata?: Record<string, unknown> | null;
1964
+ }
1965
+ /**
1966
+ * Plugin-config input for {@link OAuthOptions.resources}. A subset of the
1967
+ * persisted {@link OAuthResource} — only `identifier` is required; the rest
1968
+ * fall back to plugin defaults when omitted.
1969
+ */
1970
+ interface OAuthResourceInput {
1971
+ identifier: string;
1972
+ name?: string;
1973
+ accessTokenTtl?: number;
1974
+ refreshTokenTtl?: number;
1975
+ signingAlgorithm?: JWSAlgorithms;
1976
+ signingKeyId?: string;
1977
+ allowedScopes?: string[];
1978
+ customClaims?: Record<string, unknown>;
1979
+ dpopBoundAccessTokensRequired?: boolean;
1980
+ disabled?: boolean;
1981
+ metadata?: Record<string, unknown>;
1982
+ }
1983
+ /**
1984
+ * A row of `oauthClientResource` linking a client to a resource.
1985
+ *
1986
+ * Authoritative only when {@link OAuthOptions.enforcePerClientResources} is true.
1987
+ */
1988
+ interface OAuthClientResource {
1989
+ clientId: string;
1990
+ resourceId: string;
1991
+ metadata?: Record<string, unknown> | null;
1992
+ createdAt: Date;
1993
+ }
1245
1994
  /**
1246
1995
  * Stored within the verification.value field
1247
1996
  * in JSON format.
@@ -1339,7 +2088,7 @@ interface SchemaClient<Scopes extends readonly Scope[] = InternallySupportedScop
1339
2088
  * @default false
1340
2089
  */
1341
2090
  backchannelLogoutSessionRequired?: boolean;
1342
- tokenEndpointAuthMethod?: "none" | "client_secret_basic" | "client_secret_post" | "private_key_jwt";
2091
+ tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
1343
2092
  grantTypes?: GrantType[];
1344
2093
  responseTypes?: "code"[];
1345
2094
  /** Client's JSON Web Key Set for `private_key_jwt` authentication. Mutually exclusive with `jwksUri`. */
@@ -1377,6 +2126,11 @@ interface SchemaClient<Scopes extends readonly Scope[] = InternallySupportedScop
1377
2126
  * requesting offline_access scope, regardless of this setting.
1378
2127
  */
1379
2128
  requirePKCE?: boolean;
2129
+ /**
2130
+ * RFC 9449 dynamic client metadata. When true, every token request from this
2131
+ * client must include a valid DPoP proof and receive DPoP-bound tokens.
2132
+ */
2133
+ dpopBoundAccessTokens?: boolean;
1380
2134
  /** Used to indicate if consent screen can be skipped */
1381
2135
  skipConsent?: boolean;
1382
2136
  /** Used to enable client to logout via the `/oauth2/end-session` endpoint */
@@ -1446,6 +2200,11 @@ interface OAuthOpaqueAccessToken<Scopes extends readonly Scope[] = InternallySup
1446
2200
  * Resources allowed for this access token.
1447
2201
  */
1448
2202
  resources?: string[];
2203
+ /**
2204
+ * RFC 7800 `cnf` confirmation that sender-constrains this access token (for
2205
+ * example DPoP `{ jkt }`). Surfaced as the `cnf` claim at introspection.
2206
+ */
2207
+ confirmation?: Confirmation;
1449
2208
  }
1450
2209
  /**
1451
2210
  * Refresh Token Database Schema
@@ -1477,6 +2236,11 @@ interface OAuthRefreshToken<Scopes extends readonly Scope[] = InternallySupporte
1477
2236
  * Resources allowed for this refresh token
1478
2237
  */
1479
2238
  resources?: string[];
2239
+ /**
2240
+ * RFC 7800 `cnf` confirmation that sender-constrains this refresh-token
2241
+ * family (for example DPoP `{ jkt }`). Carried forward on rotation.
2242
+ */
2243
+ confirmation?: Confirmation;
1480
2244
  }
1481
2245
  /**
1482
2246
  * Consent Database Schema
@@ -1496,10 +2260,30 @@ type OAuthConsent<Scopes extends readonly Scope[] = InternallySupportedScopes[]>
1496
2260
  /**
1497
2261
  * Supported grant types of the token endpoint
1498
2262
  */
1499
- type GrantType = "authorization_code" | "client_credentials" | "refresh_token";
1500
- type AuthMethod = "client_secret_basic" | "client_secret_post" | "private_key_jwt";
2263
+ type BuiltInGrantType = "authorization_code" | "client_credentials" | "refresh_token";
2264
+ type GrantType = BuiltInGrantType | (string & {});
2265
+ type BuiltInAuthMethod = "client_secret_basic" | "client_secret_post" | "private_key_jwt";
2266
+ type AuthMethod = BuiltInAuthMethod | (string & {});
1501
2267
  type TokenEndpointAuthMethod = AuthMethod | "none";
1502
2268
  type BearerMethodsSupported = "header" | "body";
2269
+ /**
2270
+ * RFC 7800 `cnf` (confirmation) members that sender-constrain an access token to
2271
+ * a key the client must prove possession of. Each binding mechanism populates
2272
+ * one member of the same object: DPoP sets `jkt` (RFC 9449 §6), mTLS sets
2273
+ * `x5t#S256` (RFC 8705 §3.1). The authorization server is the sole authority
2274
+ * over this value; it is token material, never a contributed claim.
2275
+ */
2276
+ type Confirmation = {
2277
+ jkt: string;
2278
+ } | {
2279
+ "x5t#S256": string;
2280
+ };
2281
+ /**
2282
+ * Token presentation scheme returned in `token_type`. A DPoP-bound token uses
2283
+ * `"DPoP"` (RFC 9449 §5); everything else (including mTLS-bound) uses
2284
+ * `"Bearer"`, since the mTLS constraint lives at the TLS layer.
2285
+ */
2286
+ type TokenType = "Bearer" | "DPoP";
1503
2287
  /**
1504
2288
  * Metadata for authentication servers.
1505
2289
  *
@@ -1614,7 +2398,7 @@ interface AuthServerMetadata {
1614
2398
  * @default
1615
2399
  * ["client_secret_basic", "client_secret_post"]
1616
2400
  */
1617
- revocation_endpoint_auth_methods_supported?: AuthMethod[];
2401
+ revocation_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[];
1618
2402
  /**
1619
2403
  * Array containing a list of the JWS signing
1620
2404
  * algorithms ("alg" values) supported by the revocation endpoint for
@@ -1635,7 +2419,7 @@ interface AuthServerMetadata {
1635
2419
  * @default
1636
2420
  * ["client_secret_basic", "client_secret_post"]
1637
2421
  */
1638
- introspection_endpoint_auth_methods_supported?: AuthMethod[];
2422
+ introspection_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[];
1639
2423
  /**
1640
2424
  * Array containing a list of the JWS signing
1641
2425
  * algorithms ("alg" values) supported by the introspection endpoint
@@ -1691,6 +2475,12 @@ interface AuthServerMetadata {
1691
2475
  * @see https://openid.net/specs/openid-connect-backchannel-1_0.html#OPMetadata
1692
2476
  */
1693
2477
  backchannel_logout_session_supported?: boolean;
2478
+ /**
2479
+ * JWS algorithms supported for RFC 9449 DPoP proof JWTs.
2480
+ *
2481
+ * @see https://datatracker.ietf.org/doc/html/rfc9449
2482
+ */
2483
+ dpop_signing_alg_values_supported?: JWSAlgorithms[];
1694
2484
  }
1695
2485
  /**
1696
2486
  * Metadata returned by the openid-configuration endpoint:
@@ -1809,7 +2599,7 @@ interface OAuthClient {
1809
2599
  * @see https://openid.net/specs/openid-connect-backchannel-1_0.html#RPMetadata
1810
2600
  */
1811
2601
  backchannel_logout_session_required?: boolean;
1812
- token_endpoint_auth_method?: "none" | "client_secret_basic" | "client_secret_post" | "private_key_jwt";
2602
+ token_endpoint_auth_method?: TokenEndpointAuthMethod;
1813
2603
  grant_types?: GrantType[];
1814
2604
  response_types?: "code"[];
1815
2605
  public?: boolean;
@@ -1826,6 +2616,12 @@ interface OAuthClient {
1826
2616
  * requesting offline_access scope, regardless of this setting.
1827
2617
  */
1828
2618
  require_pkce?: boolean;
2619
+ /**
2620
+ * RFC 9449 dynamic client metadata. When true, token requests from this
2621
+ * client must include a valid DPoP proof and receive DPoP-bound access
2622
+ * tokens.
2623
+ */
2624
+ dpop_bound_access_tokens?: boolean;
1829
2625
  /**
1830
2626
  * Subject identifier type for this client.
1831
2627
  *
@@ -1873,4 +2669,4 @@ interface ResourceServerMetadata {
1873
2669
  dpop_bound_access_tokens_required?: boolean;
1874
2670
  }
1875
2671
  //#endregion
1876
- export { SchemaClient as _, OAuthClient as a, VerificationValue as b, TokenEndpointAuthMethod as c, OAuthAuthorizationQuery as d, OAuthConsent as f, Prompt as g, OAuthRefreshToken as h, GrantType as i, AuthorizePrompt as l, OAuthOptions as m, AuthServerMetadata as n, OIDCMetadata as o, OAuthOpaqueAccessToken as p, BearerMethodsSupported as r, ResourceServerMetadata as s, AuthMethod as t, ClientDiscovery as u, Scope as v, Awaitable as x, StoreTokenType as y };
2672
+ export { OAuthProviderExtension as A, StoreTokenType as B, OAuthConsent as C, OAuthOpaqueAccessToken as D, OAuthMetadataExtensionInput as E, OAuthTokenResponse as F, ClientRegistrationRequest as H, OAuthUserInfoExtensionInput as I, Prompt as L, OAuthResource as M, OAuthResourceInput as N, OAuthOptions as O, OAuthTokenIssueParams as P, SchemaClient as R, OAuthClientResource as S, OAuthExtensionGrantHandlerInput as T, ResourceUriSchema as U, VerificationValue as V, OAuthClaimExtensionInput as _, GrantType as a, OAuthClientAuthenticationResult as b, ResourceServerMetadata as c, ActiveAccessTokenPayload as d, AuthorizePrompt as f, OAuthAuthorizationQuery as g, OAuthAuthenticatedClient as h, Confirmation as i, OAuthRefreshToken as j, OAuthProviderApi as k, TokenEndpointAuthMethod as l, InitialAccessTokenAuthorization as m, AuthServerMetadata as n, OAuthClient as o, ClientDiscovery as p, BearerMethodsSupported as r, OIDCMetadata as s, AuthMethod as t, TokenType as u, OAuthClientAuthenticationInput as v, OAuthExtensionGrantHandler as w, OAuthClientAuthenticationStrategy as x, OAuthClientAuthenticationRequest as y, Scope as z };