@better-auth/oauth-provider 1.7.0-beta.5 → 1.7.0-beta.7
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/dist/{client-assertion-DmT1B6_6.mjs → client-assertion-CctbJywV.mjs} +88 -64
- package/dist/client-resource.d.mts +17 -2
- package/dist/client-resource.mjs +45 -25
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +3 -13
- package/dist/index.d.mts +100 -17
- package/dist/index.mjs +1239 -1699
- package/dist/introspect-BXNvkz8S.mjs +2119 -0
- package/dist/{oauth-BXrYl5x6.d.mts → oauth-CPWY2Few.d.mts} +836 -33
- package/dist/{oauth-DU6NeviY.d.mts → oauth-CqOygaZd.d.mts} +265 -148
- package/dist/resource-challenge-B-cqv4ur.mjs +63 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/signed-query-CFv2jNMT.mjs +44 -0
- package/dist/{utils-D2dLqo7f.mjs → utils-Baq6atYN.mjs} +310 -68
- package/dist/{version-B1ZiRmxj.mjs → version-DkFgXWfN.mjs} +1 -1
- package/package.json +7 -8
- package/dist/mcp-CYnz-MXn.mjs +0 -56
|
@@ -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
|
|
380
|
-
* The host walks
|
|
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
|
|
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<
|
|
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,325 @@ 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
|
+
/**
|
|
853
|
+
* Session the tokens are issued for, when one is available. Best-effort:
|
|
854
|
+
* set on the session-backed grants (authorization_code, refresh_token),
|
|
855
|
+
* undefined otherwise (client_credentials, introspection, or a session that
|
|
856
|
+
* was deleted or unlinked). Treat as possibly undefined.
|
|
857
|
+
*/
|
|
858
|
+
sessionId?: string;
|
|
859
|
+
resources?: string[];
|
|
860
|
+
/** Parsed client metadata, as returned by `parseClientMetadata`. */
|
|
861
|
+
metadata?: Record<string, unknown>;
|
|
862
|
+
}
|
|
863
|
+
interface OAuthUserInfoExtensionInput {
|
|
864
|
+
ctx: GenericEndpointContext;
|
|
865
|
+
opts: OAuthOptions<Scope[]>;
|
|
866
|
+
user: User & Record<string, unknown>;
|
|
867
|
+
scopes: string[];
|
|
868
|
+
jwt: JWTPayload;
|
|
869
|
+
client?: SchemaClient<Scope[]>;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* What a companion plugin contributes to the OAuth Provider, registered through
|
|
873
|
+
* `extendOAuthProvider(ctx, extension)` (or the `oauthProvider({ extensions })`
|
|
874
|
+
* option).
|
|
875
|
+
*
|
|
876
|
+
* All five kinds live on one object so a single protocol plugin (for example
|
|
877
|
+
* RFC 8693 token exchange, which adds a grant, advertises metadata, and emits
|
|
878
|
+
* claims) registers atomically and the host validates the combined surface in
|
|
879
|
+
* one place. Every field is independently optional, so a single-concern plugin
|
|
880
|
+
* (such as `@better-auth/cimd`, which contributes only `clientDiscovery`) sets
|
|
881
|
+
* just the one it needs. This is the shape every future OAuth RFC plugin copies.
|
|
882
|
+
*
|
|
883
|
+
* Two contribution disciplines:
|
|
884
|
+
* - Dispatched kinds (`grants`, `clientAuthentication`) must be disjoint across
|
|
885
|
+
* extensions: registering a grant type, auth method, or assertion type that
|
|
886
|
+
* another extension already registered is rejected at setup, since the second
|
|
887
|
+
* would otherwise be silently unreachable.
|
|
888
|
+
* - Additive kinds (`metadata`, `claims`) never override authorization-server
|
|
889
|
+
* core; a key already owned by the provider is kept, and a key two extensions
|
|
890
|
+
* both contribute resolves to the first-registered extension.
|
|
891
|
+
*/
|
|
892
|
+
interface OAuthProviderExtension {
|
|
893
|
+
/**
|
|
894
|
+
* Token grants keyed by absolute-URI `grant_type`. The token endpoint
|
|
895
|
+
* dispatches a matching `grant_type` to the handler, which authenticates the
|
|
896
|
+
* client and issues tokens through the shared `provider`.
|
|
897
|
+
*/
|
|
898
|
+
grants?: Record<string, OAuthExtensionGrantHandler>;
|
|
899
|
+
/**
|
|
900
|
+
* Assertion-based client authentication, keyed by the advertised
|
|
901
|
+
* `token_endpoint_auth_method`. Consumes the matching `client_assertion_type`
|
|
902
|
+
* at the token, introspection, and revocation endpoints. Built-in method
|
|
903
|
+
* names (`client_secret_basic`/`_post`, `private_key_jwt`, `none`) are
|
|
904
|
+
* reserved.
|
|
905
|
+
*/
|
|
906
|
+
clientAuthentication?: Record<string, OAuthClientAuthenticationStrategy>;
|
|
907
|
+
/**
|
|
908
|
+
* Additional discovery metadata fields. Core fields (`issuer`,
|
|
909
|
+
* `token_endpoint`, advertised grants and auth methods, ...) stay owned by
|
|
910
|
+
* the provider; only absent keys are added. To advertise the claim names a
|
|
911
|
+
* claims contributor emits, set `advertisedMetadata.claims_supported`: the
|
|
912
|
+
* provider owns `claims_supported` and does not infer it from contributors.
|
|
913
|
+
*/
|
|
914
|
+
metadata?: (input: OAuthMetadataExtensionInput) => Record<string, unknown>;
|
|
915
|
+
/**
|
|
916
|
+
* Additional claims for access tokens, ID tokens, and the UserInfo response.
|
|
917
|
+
* Strictly additive: a contributor can add new claims but never replace an
|
|
918
|
+
* identity, authentication-context, reserved RFC 9068, or other AS-owned
|
|
919
|
+
* claim. Access-token claims are re-derived at opaque-token introspection, so
|
|
920
|
+
* they must be grant-type-stable (a contributor receives `grantType:
|
|
921
|
+
* undefined` there). See the claim-authority overview in the docs for the
|
|
922
|
+
* full per-token precedence ladder.
|
|
923
|
+
*/
|
|
924
|
+
claims?: {
|
|
925
|
+
accessToken?: (input: OAuthClaimExtensionInput) => Awaitable<Record<string, unknown>>;
|
|
926
|
+
idToken?: (input: OAuthClaimExtensionInput) => Awaitable<Record<string, unknown>>;
|
|
927
|
+
userInfo?: (input: OAuthUserInfoExtensionInput) => Awaitable<Record<string, unknown>>;
|
|
928
|
+
};
|
|
929
|
+
/**
|
|
930
|
+
* Client-id resolution sources consulted by `getClient()`, plus the
|
|
931
|
+
* discovery-metadata fields they advertise. Entries across all extensions
|
|
932
|
+
* run in order; the first to return a client wins. A plugin that resolves
|
|
933
|
+
* clients from an external source (a metadata-document URL, a federated
|
|
934
|
+
* registry, an attestation header) contributes it here.
|
|
935
|
+
*/
|
|
936
|
+
clientDiscovery?: ClientDiscovery | ClientDiscovery[];
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Result of authorizing an RFC 7591 initial access token, returned by
|
|
940
|
+
* {@link OAuthOptions.validateInitialAccessToken}.
|
|
941
|
+
*/
|
|
942
|
+
interface InitialAccessTokenAuthorization {
|
|
943
|
+
/**
|
|
944
|
+
* Ownership reference to attach to the created OAuth client.
|
|
945
|
+
*
|
|
946
|
+
* Associates machine-provisioned clients with an organization, team, tenant,
|
|
947
|
+
* or other application-level owner. Omit to create an unowned client (the
|
|
948
|
+
* client is stored with neither a `user_id` nor a `reference_id`).
|
|
949
|
+
*/
|
|
950
|
+
referenceId?: string;
|
|
951
|
+
}
|
|
414
952
|
interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScopes[]> {
|
|
415
953
|
/**
|
|
416
954
|
* Custom schema definitions
|
|
@@ -429,15 +967,89 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
429
967
|
*/
|
|
430
968
|
scopes?: Scopes;
|
|
431
969
|
/**
|
|
432
|
-
*
|
|
970
|
+
* Protected resources the AS issues access tokens for. Promotes the
|
|
971
|
+
* resource model into a first-class persisted entity with per-resource
|
|
972
|
+
* token policy.
|
|
973
|
+
*
|
|
974
|
+
* - String form: each string becomes an `oauthResource` row using plugin-level
|
|
975
|
+
* defaults.
|
|
976
|
+
* - Object form: explicit per-resource policy (TTL, signing alg, scope
|
|
977
|
+
* allowlist, custom claims, sender-constraint requirements).
|
|
978
|
+
*
|
|
979
|
+
* Seeding is keyed by `identifier`. Behavior on re-seed is controlled by
|
|
980
|
+
* {@link OAuthOptions.resourceSeedMode}.
|
|
433
981
|
*
|
|
434
|
-
* @
|
|
435
|
-
* @example
|
|
436
|
-
*
|
|
437
|
-
*
|
|
982
|
+
* @see RFC 8707 — `identifier` is the `resource` parameter value
|
|
983
|
+
* @example
|
|
984
|
+
* ```ts
|
|
985
|
+
* resources: [
|
|
986
|
+
* { identifier: "https://api.example.com/admin", accessTokenTtl: 300,
|
|
987
|
+
* allowedScopes: ["admin:read", "admin:write"] },
|
|
988
|
+
* "https://api.example.com/public",
|
|
438
989
|
* ]
|
|
990
|
+
* ```
|
|
991
|
+
*/
|
|
992
|
+
resources?: Array<string | OAuthResourceInput>;
|
|
993
|
+
/**
|
|
994
|
+
* Controls whether boot-time `resources` config overwrites DB-edited rows.
|
|
995
|
+
*
|
|
996
|
+
* - `"insertOnly"` (default, safe): only inserts rows whose `identifier` is
|
|
997
|
+
* not already present. Existing rows are untouched — admin edits via CRUD
|
|
998
|
+
* are never reverted on restart.
|
|
999
|
+
* - `"merge"`: inserts missing rows; updates only fields present in the
|
|
1000
|
+
* config object for existing rows.
|
|
1001
|
+
* - `"overwrite"`: inserts missing rows; replaces existing rows with the
|
|
1002
|
+
* config values. Use only when the config is the source of truth.
|
|
1003
|
+
*
|
|
1004
|
+
* Defaults to the safe option to prevent accidental policy reverts in
|
|
1005
|
+
* production deployments.
|
|
1006
|
+
*
|
|
1007
|
+
* @default "insertOnly"
|
|
1008
|
+
*/
|
|
1009
|
+
resourceSeedMode?: "insertOnly" | "merge" | "overwrite";
|
|
1010
|
+
/**
|
|
1011
|
+
* Opt-in cache membership for resources by `identifier`. Mirrors the
|
|
1012
|
+
* {@link OAuthOptions.cachedTrustedClients} pattern.
|
|
1013
|
+
*
|
|
1014
|
+
* Cached resources are invalidated on every CRUD write. Resources not in
|
|
1015
|
+
* this set are looked up from the DB on every request — the safe default
|
|
1016
|
+
* when admins edit rows through external tooling.
|
|
1017
|
+
*/
|
|
1018
|
+
cachedResources?: Set<string>;
|
|
1019
|
+
/**
|
|
1020
|
+
* When true, `/oauth2/token` and `/oauth2/authorize` require the client to be
|
|
1021
|
+
* linked to every requested resource via `oauthClientResource`. When false,
|
|
1022
|
+
* clients implicitly have access to all enabled resources.
|
|
1023
|
+
*
|
|
1024
|
+
* Defaults to `true`, enabling per-client validation per RFC 8707 §3. An
|
|
1025
|
+
* explicit `false` keeps all enabled resources requestable by any client.
|
|
1026
|
+
*
|
|
1027
|
+
* The resolved value is logged at plugin init so admins see which default
|
|
1028
|
+
* applied.
|
|
1029
|
+
*/
|
|
1030
|
+
enforcePerClientResources?: boolean;
|
|
1031
|
+
/**
|
|
1032
|
+
* Customize how a resource `identifier` is validated when resources are
|
|
1033
|
+
* created via CRUD or DCR. The default rejects non-URI identifiers per
|
|
1034
|
+
* RFC 8707 §2 (absolute URI, no fragment). Override only for trusted
|
|
1035
|
+
* internal use cases.
|
|
1036
|
+
*
|
|
1037
|
+
* @default RFC 8707 strict URI validator
|
|
1038
|
+
*/
|
|
1039
|
+
identifierValidator?: (identifier: string) => Awaitable<boolean>;
|
|
1040
|
+
/**
|
|
1041
|
+
* RBAC on OAuth resources. Mirrors {@link OAuthOptions.clientPrivileges}.
|
|
1042
|
+
*
|
|
1043
|
+
* Gates the admin resource CRUD endpoints. Return `false` (or `undefined`)
|
|
1044
|
+
* to deny the action.
|
|
439
1045
|
*/
|
|
440
|
-
|
|
1046
|
+
resourcePrivileges?: (context: {
|
|
1047
|
+
headers: Headers;
|
|
1048
|
+
action: "create" | "read" | "update" | "delete" | "list" | "link" | "unlink";
|
|
1049
|
+
user?: User & Record<string, unknown>;
|
|
1050
|
+
session?: Session & Record<string, unknown>;
|
|
1051
|
+
resourceId?: string;
|
|
1052
|
+
}) => Awaitable<boolean | undefined>;
|
|
441
1053
|
/**
|
|
442
1054
|
* Automatically cache trusted clients by client_id.
|
|
443
1055
|
* Clients are cached at request.
|
|
@@ -520,26 +1132,60 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
520
1132
|
*/
|
|
521
1133
|
allowUnauthenticatedClientRegistration?: boolean;
|
|
522
1134
|
/**
|
|
523
|
-
* Allow dynamic client registration
|
|
1135
|
+
* Allow dynamic client registration (RFC 7591) at `POST /oauth2/register`.
|
|
1136
|
+
*
|
|
1137
|
+
* Once enabled, a registration request is authorized through one of three
|
|
1138
|
+
* modes:
|
|
1139
|
+
* - session-backed: a logged-in user with client-create privileges.
|
|
1140
|
+
* - token-backed: a valid initial access token, when
|
|
1141
|
+
* {@link OAuthOptions.validateInitialAccessToken} is defined.
|
|
1142
|
+
* - open public-only: unauthenticated registration constrained to public
|
|
1143
|
+
* clients, when {@link OAuthOptions.allowUnauthenticatedClientRegistration}
|
|
1144
|
+
* is enabled.
|
|
524
1145
|
*
|
|
525
1146
|
* @default false
|
|
526
1147
|
*/
|
|
527
1148
|
allowDynamicClientRegistration?: boolean;
|
|
528
1149
|
/**
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
1150
|
+
* Validates an RFC 7591 initial access token for protected dynamic client
|
|
1151
|
+
* registration, read from the `Authorization: Bearer <token>` header on
|
|
1152
|
+
* `POST /oauth2/register`.
|
|
1153
|
+
*
|
|
1154
|
+
* Return an {@link InitialAccessTokenAuthorization} (optionally carrying a
|
|
1155
|
+
* `referenceId` owner) to authorize the registration, or `false` to reject
|
|
1156
|
+
* the token. Defining this callback enables the token-backed registration
|
|
1157
|
+
* mode; while it is undefined, a Bearer token presented to the endpoint is
|
|
1158
|
+
* rejected rather than downgraded to open registration.
|
|
1159
|
+
*
|
|
1160
|
+
* `clientMetadata` is the schema-validated request body. It is self-asserted
|
|
1161
|
+
* (RFC 7591 §5) and not yet semantically validated, so a request authorized
|
|
1162
|
+
* here may still be rejected by a later metadata check. Compare the token in
|
|
1163
|
+
* constant time; issuance, storage, expiration, and revocation are
|
|
1164
|
+
* deployment-specific in RFC 7591 and belong in your application.
|
|
1165
|
+
*
|
|
1166
|
+
* `headers` is the raw request `Headers`, available to correlate the token
|
|
1167
|
+
* with other request context (a tenant header, a forwarded client identity).
|
|
1168
|
+
*
|
|
1169
|
+
* With the `bearer` plugin enabled, a Bearer value that resolves to a valid
|
|
1170
|
+
* user session is handled as that session, not as an initial access token.
|
|
534
1171
|
*
|
|
535
|
-
*
|
|
536
|
-
|
|
537
|
-
|
|
1172
|
+
* @see InitialAccessTokenAuthorization
|
|
1173
|
+
*/
|
|
1174
|
+
validateInitialAccessToken?: (context: {
|
|
1175
|
+
initialAccessToken: string;
|
|
1176
|
+
headers: Headers;
|
|
1177
|
+
clientMetadata: ClientRegistrationRequest;
|
|
1178
|
+
}) => Awaitable<InitialAccessTokenAuthorization | false>;
|
|
1179
|
+
/**
|
|
1180
|
+
* OAuth/OIDC extension points used by companion plugins to add protocol
|
|
1181
|
+
* grants, client authentication methods, metadata, claims, and client-id
|
|
1182
|
+
* discovery without modifying oauth-provider core for each RFC.
|
|
538
1183
|
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
1184
|
+
* Extension plugins should prefer `extendOAuthProvider(ctx, extension)` in
|
|
1185
|
+
* their `init()` hook so users can compose plugins declaratively. Plugins
|
|
1186
|
+
* such as `@better-auth/cimd` contribute their client discovery this way.
|
|
541
1187
|
*/
|
|
542
|
-
|
|
1188
|
+
extensions?: OAuthProviderExtension[];
|
|
543
1189
|
/**
|
|
544
1190
|
* List of scopes for newly registered clients
|
|
545
1191
|
* if not requested.
|
|
@@ -1122,6 +1768,27 @@ interface OAuthOptions<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
1122
1768
|
clientId: string;
|
|
1123
1769
|
ctx: GenericEndpointContext;
|
|
1124
1770
|
}) => Promise<Record<string, string> | null>;
|
|
1771
|
+
/**
|
|
1772
|
+
* DPoP proof validation settings.
|
|
1773
|
+
*
|
|
1774
|
+
* DPoP is enabled by default when a client or resource asks for DPoP-bound
|
|
1775
|
+
* access tokens. These values tune proof validation without changing that
|
|
1776
|
+
* contract.
|
|
1777
|
+
*/
|
|
1778
|
+
dpop?: {
|
|
1779
|
+
/**
|
|
1780
|
+
* Accepted age of a DPoP proof JWT in seconds.
|
|
1781
|
+
*
|
|
1782
|
+
* @default 300
|
|
1783
|
+
*/
|
|
1784
|
+
proofMaxAgeSeconds?: number;
|
|
1785
|
+
/**
|
|
1786
|
+
* Supported JWS algorithms for DPoP proof JWTs.
|
|
1787
|
+
*
|
|
1788
|
+
* @default ["EdDSA", "ES256", "ES512", "PS256", "RS256"]
|
|
1789
|
+
*/
|
|
1790
|
+
signingAlgorithms?: JWSAlgorithms[];
|
|
1791
|
+
};
|
|
1125
1792
|
}
|
|
1126
1793
|
interface OAuthAuthorizationQuery {
|
|
1127
1794
|
/**
|
|
@@ -1237,11 +1904,100 @@ interface OAuthAuthorizationQuery {
|
|
|
1237
1904
|
* with the Claim Value being the nonce value sent in the Authentication Request.
|
|
1238
1905
|
*/
|
|
1239
1906
|
nonce?: string;
|
|
1907
|
+
/**
|
|
1908
|
+
* RFC 9449 authorization request parameter. When present, the authorization
|
|
1909
|
+
* code is bound to this JWK thumbprint and the token request must present a
|
|
1910
|
+
* matching DPoP proof.
|
|
1911
|
+
*/
|
|
1912
|
+
dpop_jkt?: string;
|
|
1240
1913
|
/**
|
|
1241
1914
|
* Resource parameter as specified by [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html)
|
|
1242
1915
|
*/
|
|
1243
1916
|
resource?: string | string[];
|
|
1244
1917
|
}
|
|
1918
|
+
/**
|
|
1919
|
+
* A persisted protected-resource row as stored in `oauthResource`.
|
|
1920
|
+
*
|
|
1921
|
+
* `null` on any policy column means "inherit the plugin-level default at
|
|
1922
|
+
* token issuance time" — admins can later override without re-seeding.
|
|
1923
|
+
*/
|
|
1924
|
+
interface OAuthResource {
|
|
1925
|
+
/** Auto-generated primary key */
|
|
1926
|
+
id: string;
|
|
1927
|
+
/**
|
|
1928
|
+
* Business key used in the `aud` claim and as the RFC 8707 `resource` value.
|
|
1929
|
+
*/
|
|
1930
|
+
identifier: string;
|
|
1931
|
+
/** Human-friendly label for admin UIs */
|
|
1932
|
+
name: string;
|
|
1933
|
+
/** Access token TTL in seconds; null inherits {@link OAuthOptions.accessTokenExpiresIn} */
|
|
1934
|
+
accessTokenTtl?: number | null;
|
|
1935
|
+
/** Refresh token TTL in seconds; null inherits {@link OAuthOptions.refreshTokenExpiresIn} */
|
|
1936
|
+
refreshTokenTtl?: number | null;
|
|
1937
|
+
/** When set, overrides the JWT plugin's getLatestKey() default at signing time. */
|
|
1938
|
+
signingAlgorithm?: JWSAlgorithms | null;
|
|
1939
|
+
signingKeyId?: string | null;
|
|
1940
|
+
/**
|
|
1941
|
+
* When non-null, requested scopes must intersect this set or the request is
|
|
1942
|
+
* rejected with `invalid_scope`.
|
|
1943
|
+
*/
|
|
1944
|
+
allowedScopes?: string[] | null;
|
|
1945
|
+
/**
|
|
1946
|
+
* Per-resource claims merged into the access token JWT payload. Reserved
|
|
1947
|
+
* RFC 9068 claim names (`iss`, `sub`, `aud`, `exp`, `iat`, `jti`,
|
|
1948
|
+
* `client_id`, `scope`, `auth_time`, `acr`, `amr`) are stripped at issuance
|
|
1949
|
+
* with a warning log — never silently dropped.
|
|
1950
|
+
*/
|
|
1951
|
+
customClaims?: Record<string, unknown> | null;
|
|
1952
|
+
/**
|
|
1953
|
+
* Require newly issued access tokens for this resource to be DPoP-bound.
|
|
1954
|
+
*/
|
|
1955
|
+
dpopBoundAccessTokensRequired?: boolean;
|
|
1956
|
+
/**
|
|
1957
|
+
* Disabled → no new issuance for this resource; existing tokens still verify
|
|
1958
|
+
* until natural expiry. Compare to delete, which hard-rejects existing tokens.
|
|
1959
|
+
*/
|
|
1960
|
+
disabled: boolean;
|
|
1961
|
+
createdAt: Date;
|
|
1962
|
+
updatedAt: Date;
|
|
1963
|
+
/**
|
|
1964
|
+
* Forward-migration anchor. Lets the runtime branch behavior when claim
|
|
1965
|
+
* emission or validation semantics change in a future PR without forcing
|
|
1966
|
+
* every row to migrate. PR 1 ships with `policyVersion = 1`.
|
|
1967
|
+
*/
|
|
1968
|
+
policyVersion: number;
|
|
1969
|
+
/** Open-ended extension data — not yet promoted to columns. */
|
|
1970
|
+
metadata?: Record<string, unknown> | null;
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* Plugin-config input for {@link OAuthOptions.resources}. A subset of the
|
|
1974
|
+
* persisted {@link OAuthResource} — only `identifier` is required; the rest
|
|
1975
|
+
* fall back to plugin defaults when omitted.
|
|
1976
|
+
*/
|
|
1977
|
+
interface OAuthResourceInput {
|
|
1978
|
+
identifier: string;
|
|
1979
|
+
name?: string;
|
|
1980
|
+
accessTokenTtl?: number;
|
|
1981
|
+
refreshTokenTtl?: number;
|
|
1982
|
+
signingAlgorithm?: JWSAlgorithms;
|
|
1983
|
+
signingKeyId?: string;
|
|
1984
|
+
allowedScopes?: string[];
|
|
1985
|
+
customClaims?: Record<string, unknown>;
|
|
1986
|
+
dpopBoundAccessTokensRequired?: boolean;
|
|
1987
|
+
disabled?: boolean;
|
|
1988
|
+
metadata?: Record<string, unknown>;
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* A row of `oauthClientResource` linking a client to a resource.
|
|
1992
|
+
*
|
|
1993
|
+
* Authoritative only when {@link OAuthOptions.enforcePerClientResources} is true.
|
|
1994
|
+
*/
|
|
1995
|
+
interface OAuthClientResource {
|
|
1996
|
+
clientId: string;
|
|
1997
|
+
resourceId: string;
|
|
1998
|
+
metadata?: Record<string, unknown> | null;
|
|
1999
|
+
createdAt: Date;
|
|
2000
|
+
}
|
|
1245
2001
|
/**
|
|
1246
2002
|
* Stored within the verification.value field
|
|
1247
2003
|
* in JSON format.
|
|
@@ -1339,7 +2095,7 @@ interface SchemaClient<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
1339
2095
|
* @default false
|
|
1340
2096
|
*/
|
|
1341
2097
|
backchannelLogoutSessionRequired?: boolean;
|
|
1342
|
-
tokenEndpointAuthMethod?:
|
|
2098
|
+
tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
|
|
1343
2099
|
grantTypes?: GrantType[];
|
|
1344
2100
|
responseTypes?: "code"[];
|
|
1345
2101
|
/** Client's JSON Web Key Set for `private_key_jwt` authentication. Mutually exclusive with `jwksUri`. */
|
|
@@ -1377,6 +2133,11 @@ interface SchemaClient<Scopes extends readonly Scope[] = InternallySupportedScop
|
|
|
1377
2133
|
* requesting offline_access scope, regardless of this setting.
|
|
1378
2134
|
*/
|
|
1379
2135
|
requirePKCE?: boolean;
|
|
2136
|
+
/**
|
|
2137
|
+
* RFC 9449 dynamic client metadata. When true, every token request from this
|
|
2138
|
+
* client must include a valid DPoP proof and receive DPoP-bound tokens.
|
|
2139
|
+
*/
|
|
2140
|
+
dpopBoundAccessTokens?: boolean;
|
|
1380
2141
|
/** Used to indicate if consent screen can be skipped */
|
|
1381
2142
|
skipConsent?: boolean;
|
|
1382
2143
|
/** Used to enable client to logout via the `/oauth2/end-session` endpoint */
|
|
@@ -1446,6 +2207,11 @@ interface OAuthOpaqueAccessToken<Scopes extends readonly Scope[] = InternallySup
|
|
|
1446
2207
|
* Resources allowed for this access token.
|
|
1447
2208
|
*/
|
|
1448
2209
|
resources?: string[];
|
|
2210
|
+
/**
|
|
2211
|
+
* RFC 7800 `cnf` confirmation that sender-constrains this access token (for
|
|
2212
|
+
* example DPoP `{ jkt }`). Surfaced as the `cnf` claim at introspection.
|
|
2213
|
+
*/
|
|
2214
|
+
confirmation?: Confirmation;
|
|
1449
2215
|
}
|
|
1450
2216
|
/**
|
|
1451
2217
|
* Refresh Token Database Schema
|
|
@@ -1477,6 +2243,11 @@ interface OAuthRefreshToken<Scopes extends readonly Scope[] = InternallySupporte
|
|
|
1477
2243
|
* Resources allowed for this refresh token
|
|
1478
2244
|
*/
|
|
1479
2245
|
resources?: string[];
|
|
2246
|
+
/**
|
|
2247
|
+
* RFC 7800 `cnf` confirmation that sender-constrains this refresh-token
|
|
2248
|
+
* family (for example DPoP `{ jkt }`). Carried forward on rotation.
|
|
2249
|
+
*/
|
|
2250
|
+
confirmation?: Confirmation;
|
|
1480
2251
|
}
|
|
1481
2252
|
/**
|
|
1482
2253
|
* Consent Database Schema
|
|
@@ -1496,10 +2267,30 @@ type OAuthConsent<Scopes extends readonly Scope[] = InternallySupportedScopes[]>
|
|
|
1496
2267
|
/**
|
|
1497
2268
|
* Supported grant types of the token endpoint
|
|
1498
2269
|
*/
|
|
1499
|
-
type
|
|
1500
|
-
type
|
|
2270
|
+
type BuiltInGrantType = "authorization_code" | "client_credentials" | "refresh_token";
|
|
2271
|
+
type GrantType = BuiltInGrantType | (string & {});
|
|
2272
|
+
type BuiltInAuthMethod = "client_secret_basic" | "client_secret_post" | "private_key_jwt";
|
|
2273
|
+
type AuthMethod = BuiltInAuthMethod | (string & {});
|
|
1501
2274
|
type TokenEndpointAuthMethod = AuthMethod | "none";
|
|
1502
2275
|
type BearerMethodsSupported = "header" | "body";
|
|
2276
|
+
/**
|
|
2277
|
+
* RFC 7800 `cnf` (confirmation) members that sender-constrain an access token to
|
|
2278
|
+
* a key the client must prove possession of. Each binding mechanism populates
|
|
2279
|
+
* one member of the same object: DPoP sets `jkt` (RFC 9449 §6), mTLS sets
|
|
2280
|
+
* `x5t#S256` (RFC 8705 §3.1). The authorization server is the sole authority
|
|
2281
|
+
* over this value; it is token material, never a contributed claim.
|
|
2282
|
+
*/
|
|
2283
|
+
type Confirmation = {
|
|
2284
|
+
jkt: string;
|
|
2285
|
+
} | {
|
|
2286
|
+
"x5t#S256": string;
|
|
2287
|
+
};
|
|
2288
|
+
/**
|
|
2289
|
+
* Token presentation scheme returned in `token_type`. A DPoP-bound token uses
|
|
2290
|
+
* `"DPoP"` (RFC 9449 §5); everything else (including mTLS-bound) uses
|
|
2291
|
+
* `"Bearer"`, since the mTLS constraint lives at the TLS layer.
|
|
2292
|
+
*/
|
|
2293
|
+
type TokenType = "Bearer" | "DPoP";
|
|
1503
2294
|
/**
|
|
1504
2295
|
* Metadata for authentication servers.
|
|
1505
2296
|
*
|
|
@@ -1614,7 +2405,7 @@ interface AuthServerMetadata {
|
|
|
1614
2405
|
* @default
|
|
1615
2406
|
* ["client_secret_basic", "client_secret_post"]
|
|
1616
2407
|
*/
|
|
1617
|
-
revocation_endpoint_auth_methods_supported?:
|
|
2408
|
+
revocation_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[];
|
|
1618
2409
|
/**
|
|
1619
2410
|
* Array containing a list of the JWS signing
|
|
1620
2411
|
* algorithms ("alg" values) supported by the revocation endpoint for
|
|
@@ -1635,7 +2426,7 @@ interface AuthServerMetadata {
|
|
|
1635
2426
|
* @default
|
|
1636
2427
|
* ["client_secret_basic", "client_secret_post"]
|
|
1637
2428
|
*/
|
|
1638
|
-
introspection_endpoint_auth_methods_supported?:
|
|
2429
|
+
introspection_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[];
|
|
1639
2430
|
/**
|
|
1640
2431
|
* Array containing a list of the JWS signing
|
|
1641
2432
|
* algorithms ("alg" values) supported by the introspection endpoint
|
|
@@ -1691,6 +2482,12 @@ interface AuthServerMetadata {
|
|
|
1691
2482
|
* @see https://openid.net/specs/openid-connect-backchannel-1_0.html#OPMetadata
|
|
1692
2483
|
*/
|
|
1693
2484
|
backchannel_logout_session_supported?: boolean;
|
|
2485
|
+
/**
|
|
2486
|
+
* JWS algorithms supported for RFC 9449 DPoP proof JWTs.
|
|
2487
|
+
*
|
|
2488
|
+
* @see https://datatracker.ietf.org/doc/html/rfc9449
|
|
2489
|
+
*/
|
|
2490
|
+
dpop_signing_alg_values_supported?: JWSAlgorithms[];
|
|
1694
2491
|
}
|
|
1695
2492
|
/**
|
|
1696
2493
|
* Metadata returned by the openid-configuration endpoint:
|
|
@@ -1809,7 +2606,7 @@ interface OAuthClient {
|
|
|
1809
2606
|
* @see https://openid.net/specs/openid-connect-backchannel-1_0.html#RPMetadata
|
|
1810
2607
|
*/
|
|
1811
2608
|
backchannel_logout_session_required?: boolean;
|
|
1812
|
-
token_endpoint_auth_method?:
|
|
2609
|
+
token_endpoint_auth_method?: TokenEndpointAuthMethod;
|
|
1813
2610
|
grant_types?: GrantType[];
|
|
1814
2611
|
response_types?: "code"[];
|
|
1815
2612
|
public?: boolean;
|
|
@@ -1826,6 +2623,12 @@ interface OAuthClient {
|
|
|
1826
2623
|
* requesting offline_access scope, regardless of this setting.
|
|
1827
2624
|
*/
|
|
1828
2625
|
require_pkce?: boolean;
|
|
2626
|
+
/**
|
|
2627
|
+
* RFC 9449 dynamic client metadata. When true, token requests from this
|
|
2628
|
+
* client must include a valid DPoP proof and receive DPoP-bound access
|
|
2629
|
+
* tokens.
|
|
2630
|
+
*/
|
|
2631
|
+
dpop_bound_access_tokens?: boolean;
|
|
1829
2632
|
/**
|
|
1830
2633
|
* Subject identifier type for this client.
|
|
1831
2634
|
*
|
|
@@ -1873,4 +2676,4 @@ interface ResourceServerMetadata {
|
|
|
1873
2676
|
dpop_bound_access_tokens_required?: boolean;
|
|
1874
2677
|
}
|
|
1875
2678
|
//#endregion
|
|
1876
|
-
export { SchemaClient as _,
|
|
2679
|
+
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 };
|