@better-auth/oauth-provider 1.7.0-rc.1 → 1.7.0-rc.3

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,23 +1,121 @@
1
- import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
2
- import { a as getClient } from "./utils-DO8lmoDw.mjs";
1
+ import { N as getClientDiscoveries, a as getClient } from "./utils-GbnW6qPl.mjs";
3
2
  import { isPublicRoutableHost } from "@better-auth/core/utils/host";
4
3
  import { APIError } from "better-call";
5
4
  import { CLIENT_ASSERTION_TYPE, PRIVATE_KEY_JWT_SIGNING_ALGORITHMS } from "@better-auth/core/oauth2";
6
5
  import { base64Url } from "@better-auth/utils/base64";
7
6
  import { createHash } from "@better-auth/utils/hash";
8
7
  import { createLocalJWKSet, decodeJwt, decodeProtectedHeader, jwtVerify } from "jose";
8
+ //#region \0rolldown/runtime.js
9
+ var __defProp = Object.defineProperty;
10
+ var __exportAll = (all, no_symbols) => {
11
+ let target = {};
12
+ for (var name in all) __defProp(target, name, {
13
+ get: all[name],
14
+ enumerable: true
15
+ });
16
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
17
+ return target;
18
+ };
19
+ //#endregion
20
+ //#region src/client-jwks.ts
21
+ const EC_PRIVATE_KEY_JWT_ALGORITHM_BY_CURVE = {
22
+ "P-256": "ES256",
23
+ "P-384": "ES384",
24
+ "P-521": "ES512"
25
+ };
26
+ const OKP_PRIVATE_KEY_JWT_SIGNING_CURVES = ["Ed25519"];
27
+ const PRIVATE_JWK_MEMBER_NAMES = [
28
+ "d",
29
+ "p",
30
+ "q",
31
+ "dp",
32
+ "dq",
33
+ "qi",
34
+ "oth"
35
+ ];
36
+ function isRecord(value) {
37
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38
+ }
39
+ function hasStringMember(key, memberName) {
40
+ return typeof key[memberName] === "string" && key[memberName].length > 0;
41
+ }
42
+ function isSupportedEcSigningCurve(curve) {
43
+ return typeof curve === "string" && Object.prototype.hasOwnProperty.call(EC_PRIVATE_KEY_JWT_ALGORITHM_BY_CURVE, curve);
44
+ }
45
+ function isSupportedOkpSigningCurve(curve) {
46
+ return OKP_PRIVATE_KEY_JWT_SIGNING_CURVES.some((signingCurve) => signingCurve === curve);
47
+ }
48
+ function isSupportedPublicJwk(key) {
49
+ switch (key.kty) {
50
+ case "RSA": return hasStringMember(key, "n") && hasStringMember(key, "e");
51
+ case "EC": return isSupportedEcSigningCurve(key.crv) && hasStringMember(key, "x") && hasStringMember(key, "y");
52
+ case "OKP": return isSupportedOkpSigningCurve(key.crv) && hasStringMember(key, "x");
53
+ default: return false;
54
+ }
55
+ }
56
+ function hasSupportedPrivateKeyJwtAlgorithm(key) {
57
+ if (key.alg === void 0) return true;
58
+ if (typeof key.alg !== "string" || !PRIVATE_KEY_JWT_SIGNING_ALGORITHMS.some((algorithm) => algorithm === key.alg)) return false;
59
+ switch (key.kty) {
60
+ case "RSA": return key.alg.startsWith("RS") || key.alg.startsWith("PS");
61
+ case "EC": return isSupportedEcSigningCurve(key.crv) && EC_PRIVATE_KEY_JWT_ALGORITHM_BY_CURVE[key.crv] === key.alg;
62
+ case "OKP": return isSupportedOkpSigningCurve(key.crv) && key.alg === "EdDSA";
63
+ default: return false;
64
+ }
65
+ }
66
+ /**
67
+ * Validates an OAuth client's public asymmetric JWK set.
68
+ *
69
+ * This boundary accepts only the RFC 7517 `{ keys: [...] }` representation.
70
+ * It performs no I/O and returns the validated set for downstream JOSE
71
+ * verification.
72
+ *
73
+ * @internal
74
+ */
75
+ function validatePublicClientJwks(input) {
76
+ const keys = isRecord(input) && Array.isArray(input.keys) ? input.keys : void 0;
77
+ if (!keys?.length) return {
78
+ valid: false,
79
+ error: "jwks must be an RFC 7517 JWK Set object with a non-empty keys array"
80
+ };
81
+ for (const key of keys) {
82
+ if (!isRecord(key)) return {
83
+ valid: false,
84
+ error: "jwks keys must be supported public JWKs with required key parameters"
85
+ };
86
+ if (key.kty === "oct" || "k" in key || PRIVATE_JWK_MEMBER_NAMES.some((name) => name in key)) return {
87
+ valid: false,
88
+ error: "jwks must contain only public asymmetric keys"
89
+ };
90
+ if (!isSupportedPublicJwk(key)) return {
91
+ valid: false,
92
+ error: "jwks keys must be supported public JWKs with required key parameters"
93
+ };
94
+ if (!hasSupportedPrivateKeyJwtAlgorithm(key)) return {
95
+ valid: false,
96
+ error: "jwks key alg must be supported for private_key_jwt and compatible with its key type and signing curve"
97
+ };
98
+ }
99
+ return {
100
+ valid: true,
101
+ jwks: { keys }
102
+ };
103
+ }
104
+ //#endregion
9
105
  //#region src/utils/client-assertion.ts
10
106
  var client_assertion_exports = /* @__PURE__ */ __exportAll({
11
107
  consumeClientAssertion: () => consumeClientAssertion,
12
108
  isPrivateHostname: () => isPrivateHostname,
13
109
  verifyClientAssertion: () => verifyClientAssertion
14
110
  });
15
- const jwksCache = /* @__PURE__ */ new Map();
111
+ const jwksCaches = /* @__PURE__ */ new WeakMap();
16
112
  const JWKS_CACHE_TTL_MS = 300 * 1e3;
17
113
  const JWKS_CACHE_MAX_ENTRIES = 500;
18
114
  const JWKS_FETCH_TIMEOUT_MS = 5e3;
19
- function setJwksCache(uri, jwks, fetchedAt) {
20
- jwksCache.set(uri, {
115
+ const MAX_JWKS_RESPONSE_BYTES = 64 * 1024;
116
+ const JSON_CONTENT_TYPE = /^application\/(?:[-\w.]+\+)?json\s*(?:;|$)/i;
117
+ function setJwksCache(jwksCache, cacheKey, jwks, fetchedAt) {
118
+ jwksCache.set(cacheKey, {
21
119
  jwks,
22
120
  fetchedAt
23
121
  });
@@ -26,6 +124,16 @@ function setJwksCache(uri, jwks, fetchedAt) {
26
124
  if (oldest !== void 0) jwksCache.delete(oldest);
27
125
  }
28
126
  }
127
+ function getJwksCache(opts) {
128
+ const existingCache = jwksCaches.get(opts);
129
+ if (existingCache) return existingCache;
130
+ const cache = /* @__PURE__ */ new Map();
131
+ jwksCaches.set(opts, cache);
132
+ return cache;
133
+ }
134
+ function getJwksCacheKey(client) {
135
+ return `${client.clientDiscoveryId ?? "managed"}:${client.jwksUri ?? ""}`;
136
+ }
29
137
  const ALGORITHMS_LIST = [...PRIVATE_KEY_JWT_SIGNING_ALGORITHMS];
30
138
  /**
31
139
  * SSRF gate for user-supplied server-side fetch targets (`jwks_uri`,
@@ -49,6 +157,14 @@ function validateJwksUri(ctx, jwksUri, clientIdUrlOrigin) {
49
157
  error_description: "jwks_uri must use HTTPS",
50
158
  error: "invalid_client"
51
159
  });
160
+ if (parsed.username || parsed.password) throw new APIError("BAD_REQUEST", {
161
+ error_description: "jwks_uri must not contain credentials",
162
+ error: "invalid_client"
163
+ });
164
+ if (jwksUri.includes("#")) throw new APIError("BAD_REQUEST", {
165
+ error_description: "jwks_uri must not include a fragment component",
166
+ error: "invalid_client"
167
+ });
52
168
  if (isPrivateHostname(parsed.hostname)) throw new APIError("BAD_REQUEST", {
53
169
  error_description: "jwks_uri must not point to a private or reserved address",
54
170
  error: "invalid_client"
@@ -60,63 +176,120 @@ function validateJwksUri(ctx, jwksUri, clientIdUrlOrigin) {
60
176
  });
61
177
  }
62
178
  function urlClientIdOrigin(clientId) {
63
- if (!clientId.startsWith("https://") && !clientId.startsWith("http://")) return;
64
179
  try {
65
- return new URL(clientId).origin;
180
+ const parsed = new URL(clientId);
181
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return;
182
+ return parsed.origin;
66
183
  } catch {
67
184
  return;
68
185
  }
69
186
  }
70
- async function fetchJwksFromUri(jwksUri) {
187
+ async function readBoundedResponseBody(response) {
188
+ const contentLength = response.headers.get("content-length");
189
+ if (contentLength !== null && Number.isFinite(Number(contentLength)) && Number(contentLength) > MAX_JWKS_RESPONSE_BYTES) {
190
+ await response.body?.cancel();
191
+ throw new Error("JWKS response exceeds 64 KiB");
192
+ }
193
+ if (!response.body) return "";
194
+ const reader = response.body.getReader();
195
+ const chunks = [];
196
+ let totalBytes = 0;
197
+ while (true) {
198
+ const { done, value } = await reader.read();
199
+ if (done) break;
200
+ totalBytes += value.byteLength;
201
+ if (totalBytes > MAX_JWKS_RESPONSE_BYTES) {
202
+ await reader.cancel();
203
+ throw new Error("JWKS response exceeds 64 KiB");
204
+ }
205
+ chunks.push(value);
206
+ }
207
+ const bytes = new Uint8Array(totalBytes);
208
+ let offset = 0;
209
+ for (const chunk of chunks) {
210
+ bytes.set(chunk, offset);
211
+ offset += chunk.byteLength;
212
+ }
213
+ return new TextDecoder().decode(bytes);
214
+ }
215
+ async function fetchJwksFromUri(jwksUri, fetchClientMetadataResource = globalThis.fetch) {
71
216
  const controller = new AbortController();
72
217
  const timeout = setTimeout(() => controller.abort(), JWKS_FETCH_TIMEOUT_MS);
73
218
  try {
74
- const response = await fetch(jwksUri, {
219
+ const response = await fetchClientMetadataResource(jwksUri, {
75
220
  signal: controller.signal,
76
221
  headers: { accept: "application/json" },
77
222
  redirect: "error"
78
223
  });
79
- if (!response.ok) throw new Error(`JWKS fetch returned ${response.status}`);
80
- const jwks = await response.json();
81
- if (!jwks.keys || !Array.isArray(jwks.keys)) throw new Error("JWKS response missing keys array");
82
- return jwks;
224
+ if (response.redirected) throw new Error("JWKS fetch redirected");
225
+ if (response.status !== 200) throw new Error(`JWKS fetch returned ${response.status}`);
226
+ const contentType = response.headers.get("content-type");
227
+ if (!contentType || !JSON_CONTENT_TYPE.test(contentType)) throw new Error("JWKS response must use a JSON media type");
228
+ const responseBody = await readBoundedResponseBody(response);
229
+ let parsedBody;
230
+ try {
231
+ parsedBody = JSON.parse(responseBody);
232
+ } catch {
233
+ return { valid: false };
234
+ }
235
+ const result = validatePublicClientJwks(parsedBody);
236
+ if (!result.valid) return { valid: false };
237
+ return {
238
+ valid: true,
239
+ jwks: result.jwks
240
+ };
83
241
  } finally {
84
242
  clearTimeout(timeout);
85
243
  }
86
244
  }
87
- async function fetchClientJwks(ctx, client) {
245
+ function createClientJwksFetchError() {
246
+ return new APIError("BAD_REQUEST", {
247
+ error_description: "failed to fetch client JWKS",
248
+ error: "invalid_client"
249
+ });
250
+ }
251
+ async function fetchClientJwks(ctx, opts, client) {
88
252
  if (client.jwks) return JSON.parse(client.jwks);
89
253
  if (!client.jwksUri) throw new APIError("BAD_REQUEST", {
90
254
  error_description: "client has no JWKS configured",
91
255
  error: "invalid_client"
92
256
  });
93
- validateJwksUri(ctx, client.jwksUri, urlClientIdOrigin(client.clientId));
257
+ const discovery = client.clientDiscoveryId ? getClientDiscoveries(opts).find((candidate) => candidate.id === client.clientDiscoveryId) : void 0;
258
+ if (client.clientDiscoveryId && !discovery?.fetchClientMetadataResource) throw new APIError("BAD_REQUEST", {
259
+ error_description: "client discovery does not provide a metadata resource transport",
260
+ error: "invalid_client"
261
+ });
262
+ validateJwksUri(ctx, client.jwksUri, client.clientDiscoveryId ? urlClientIdOrigin(client.clientId) : void 0);
94
263
  const now = Date.now();
95
- const cached = jwksCache.get(client.jwksUri);
264
+ const cacheKey = getJwksCacheKey(client);
265
+ const jwksCache = getJwksCache(opts);
266
+ const cached = jwksCache.get(cacheKey);
96
267
  if (cached && now - cached.fetchedAt < JWKS_CACHE_TTL_MS) return cached.jwks;
268
+ let result;
97
269
  try {
98
- const jwks = await fetchJwksFromUri(client.jwksUri);
99
- setJwksCache(client.jwksUri, jwks, now);
100
- return jwks;
270
+ result = await fetchJwksFromUri(client.jwksUri, discovery?.fetchClientMetadataResource);
101
271
  } catch {
102
272
  const staleLimitMs = JWKS_CACHE_TTL_MS * 2;
103
273
  if (cached && now - cached.fetchedAt < staleLimitMs) return cached.jwks;
104
- throw new APIError("BAD_REQUEST", {
105
- error_description: "failed to fetch client JWKS",
106
- error: "invalid_client"
107
- });
274
+ throw createClientJwksFetchError();
108
275
  }
276
+ if (!result.valid) throw createClientJwksFetchError();
277
+ setJwksCache(jwksCache, cacheKey, result.jwks, now);
278
+ return result.jwks;
109
279
  }
110
280
  /**
111
281
  * Refetch JWKS from jwks_uri when signature verification fails with cached keys.
112
282
  * Handles key rotation: the client may have published a new key that isn't in our cache yet.
113
283
  */
114
- async function refetchClientJwks(client) {
284
+ async function refetchClientJwks(opts, client) {
115
285
  if (!client.jwksUri) return null;
286
+ const discovery = client.clientDiscoveryId ? getClientDiscoveries(opts).find((candidate) => candidate.id === client.clientDiscoveryId) : void 0;
287
+ if (client.clientDiscoveryId && !discovery?.fetchClientMetadataResource) return null;
116
288
  try {
117
- const jwks = await fetchJwksFromUri(client.jwksUri);
118
- setJwksCache(client.jwksUri, jwks, Date.now());
119
- return jwks;
289
+ const result = await fetchJwksFromUri(client.jwksUri, discovery?.fetchClientMetadataResource);
290
+ if (!result.valid) return null;
291
+ setJwksCache(getJwksCache(opts), getJwksCacheKey(client), result.jwks, Date.now());
292
+ return result.jwks;
120
293
  } catch {
121
294
  return null;
122
295
  }
@@ -254,7 +427,7 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
254
427
  error_description: "client is not registered for private_key_jwt authentication",
255
428
  error: "invalid_client"
256
429
  });
257
- const jwks = await fetchClientJwks(ctx, client);
430
+ const jwks = await fetchClientJwks(ctx, opts, client);
258
431
  const audience = expectedAudience ?? `${ctx.context.baseURL}/oauth2/token`;
259
432
  const verifyOpts = {
260
433
  issuer: clientId,
@@ -267,7 +440,7 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
267
440
  ({payload} = await jwtVerify(clientAssertion, createLocalJWKSet(jwks), verifyOpts));
268
441
  } catch (verifyErr) {
269
442
  if (verifyErr instanceof Error && /no matching key|no applicable key/i.test(verifyErr.message)) {
270
- const refreshed = await refetchClientJwks(client);
443
+ const refreshed = await refetchClientJwks(opts, client);
271
444
  if (refreshed) try {
272
445
  ({payload} = await jwtVerify(clientAssertion, createLocalJWKSet(refreshed), verifyOpts));
273
446
  } catch {
@@ -293,4 +466,4 @@ async function verifyClientAssertion(ctx, opts, clientAssertion, clientAssertion
293
466
  return { clientId };
294
467
  }
295
468
  //#endregion
296
- export { consumeClientAssertion as n, isPrivateHostname as r, client_assertion_exports as t };
469
+ export { __exportAll as a, validatePublicClientJwks as i, consumeClientAssertion as n, isPrivateHostname as r, client_assertion_exports as t };
@@ -1,14 +1,10 @@
1
- import { c as ResourceServerMetadata } from "./oauth-ScTJEcFV.mjs";
1
+ import { c as ResourceServerMetadata } from "./oauth-Bi2PA_d1.mjs";
2
2
  import { ResourceRequestInput, VerifyAccessTokenRequestOptions } from "better-auth/oauth2";
3
3
  import { JWTPayload, JWTVerifyOptions } from "jose";
4
4
  import { BetterAuthOptions } from "better-auth/types";
5
-
6
5
  //#region src/client-resource.d.ts
7
6
  type ResourceClientAuth = {
8
- options: {
9
- baseURL?: BetterAuthOptions["baseURL"];
10
- basePath?: BetterAuthOptions["basePath"];
11
- };
7
+ options: BetterAuthOptions;
12
8
  $context: Promise<unknown>;
13
9
  };
14
10
  declare const oauthProviderResourceClient: <T extends ResourceClientAuth | undefined = undefined>(auth?: T) => {
@@ -76,9 +72,11 @@ type VerifyAccessTokenOutput<T> = T extends undefined ? (token: string | undefin
76
72
  type VerifyAccessTokenRequestOutput<T> = T extends undefined ? (request: Request | ResourceRequestInput, opts: VerifyAccessTokenRequestNoAuthOpts) => Promise<JWTPayload> : (request: Request | ResourceRequestInput, opts?: VerifyAccessTokenRequestAuthOpts) => Promise<JWTPayload>;
77
73
  type VerifyAccessTokenAuthOpts = {
78
74
  verifyOptions?: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience">>;
79
- scopes?: string[];
75
+ requiredScopes?: readonly string[];
76
+ isScopeSatisfied?: VerifyAccessTokenRequestOptions["isScopeSatisfied"];
80
77
  jwksUrl?: string;
81
- remoteVerify?: VerifyAccessTokenRemote; /** Maps non-url (ie urn, client) resources to resource_metadata */
78
+ remoteVerify?: VerifyAccessTokenRemote;
79
+ /** Maps non-url (ie urn, client) resources to resource_metadata */
82
80
  resourceMetadataMappings?: Record<string, string>;
83
81
  };
84
82
  type VerifyAccessTokenRequestAuthOpts = VerifyAccessTokenAuthOpts & {
@@ -86,15 +84,19 @@ type VerifyAccessTokenRequestAuthOpts = VerifyAccessTokenAuthOpts & {
86
84
  };
87
85
  type VerifyAccessTokenNoAuthOpts = {
88
86
  verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
89
- scopes?: string[];
87
+ requiredScopes?: readonly string[];
88
+ isScopeSatisfied?: VerifyAccessTokenRequestOptions["isScopeSatisfied"];
90
89
  jwksUrl: string;
91
- remoteVerify?: VerifyAccessTokenRemote; /** Maps non-url (ie urn, client) resources to resource_metadata */
90
+ remoteVerify?: VerifyAccessTokenRemote;
91
+ /** Maps non-url (ie urn, client) resources to resource_metadata */
92
92
  resourceMetadataMappings?: Record<string, string>;
93
93
  } | {
94
94
  verifyOptions: JWTVerifyOptions & Required<Pick<JWTVerifyOptions, "audience" | "issuer">>;
95
- scopes?: string[];
95
+ requiredScopes?: readonly string[];
96
+ isScopeSatisfied?: VerifyAccessTokenRequestOptions["isScopeSatisfied"];
96
97
  jwksUrl?: string;
97
- remoteVerify: VerifyAccessTokenRemote; /** Maps non-url (ie urn, client) resources to resource_metadata */
98
+ remoteVerify: VerifyAccessTokenRemote;
99
+ /** Maps non-url (ie urn, client) resources to resource_metadata */
98
100
  resourceMetadataMappings?: Record<string, string>;
99
101
  };
100
102
  type VerifyAccessTokenRequestNoAuthOpts = VerifyAccessTokenNoAuthOpts & {
@@ -1,6 +1,6 @@
1
- import { o as getJwtPlugin, s as getOAuthProviderPlugin } from "./utils-DO8lmoDw.mjs";
2
- import { t as PACKAGE_VERSION } from "./version--aseHtsu.mjs";
3
- import { t as raiseResourceServerChallenge } from "./resource-challenge-B-cqv4ur.mjs";
1
+ import { o as getJwtPlugin, s as getOAuthProviderPlugin } from "./utils-GbnW6qPl.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-xoFrTW0m.mjs";
3
+ import { t as createResourceServerChallenge } from "./resource-challenge-CiJTlsEh.mjs";
4
4
  import { APIError } from "better-call";
5
5
  import { logger } from "@better-auth/core/env";
6
6
  import { BetterAuthError } from "@better-auth/core/error";
@@ -50,36 +50,64 @@ const oauthProviderResourceClient = (auth) => {
50
50
  version: PACKAGE_VERSION,
51
51
  getActions() {
52
52
  return {
53
+ /**
54
+ * Performs verification of an access token for your APIs. Can perform
55
+ * local verification using `jwksUrl` by default. Can also be configured
56
+ * for remote introspection using `remoteVerify` if a confidential client
57
+ * is set up for this API.
58
+ *
59
+ * The optional auth parameter can fill known values automatically.
60
+ */
53
61
  verifyBearerToken: (async (token, opts) => {
54
62
  const verifyOptions = await resolveVerifyAccessTokenOptions(opts);
55
63
  try {
56
64
  if (!token?.length) throw new APIError("UNAUTHORIZED", { message: "missing authorization header" });
57
65
  return await verifyBearerToken(token, verifyOptions);
58
66
  } catch (error) {
59
- raiseResourceServerChallenge(error, verifyOptions.verifyOptions.audience, {
67
+ const challenge = createResourceServerChallenge(error, verifyOptions.verifyOptions.audience, {
60
68
  resourceMetadataMappings: opts?.resourceMetadataMappings,
61
69
  dpopSigningAlgorithms: DPOP_SIGNING_ALGORITHMS
62
70
  });
71
+ if (challenge) throw challenge;
72
+ throw error;
63
73
  }
64
74
  }),
75
+ /**
76
+ * Performs verification of a protected-resource request. Use this for
77
+ * new resource-server integrations so sender-constrained DPoP access
78
+ * tokens are enforced with the request method, URL, Authorization
79
+ * scheme, DPoP proof, `ath`, and `cnf.jkt` binding.
80
+ */
65
81
  verifyAccessTokenRequest: (async (request, opts) => {
66
82
  const verifyOptions = await resolveVerifyAccessTokenOptions(opts);
67
83
  try {
68
84
  return await verifyAccessTokenRequest(toResourceRequestInput(request), verifyOptions);
69
85
  } catch (error) {
70
- raiseResourceServerChallenge(error, verifyOptions.verifyOptions.audience, {
86
+ const challenge = createResourceServerChallenge(error, verifyOptions.verifyOptions.audience, {
71
87
  resourceMetadataMappings: opts?.resourceMetadataMappings,
72
88
  dpopSigningAlgorithms: opts?.dpop?.signingAlgorithms ?? DPOP_SIGNING_ALGORITHMS
73
89
  });
90
+ if (challenge) throw challenge;
91
+ throw error;
74
92
  }
75
93
  }),
94
+ /**
95
+ * An authorization server does not typically publish
96
+ * the `/.well-known/oauth-protected-resource` themselves.
97
+ * Thus, we provide a client-only endpoint to help set up
98
+ * your protected resource metadata.
99
+ *
100
+ * The optional auth parameter can fill known values automatically.
101
+ *
102
+ * @see https://datatracker.ietf.org/doc/html/rfc8414#section-2
103
+ */
76
104
  getProtectedResourceMetadata: (async (overrides, opts) => {
77
105
  const resource = overrides?.resource ?? authServerBaseUrl;
78
106
  const oauthProviderOptions = (await getOauthProviderPlugin())?.options;
79
107
  if (!resource) throw Error("missing required resource");
80
108
  if (oauthProviderOptions?.scopes && opts?.externalScopes && (overrides?.authorization_servers?.length ?? 0) <= 1) throw new BetterAuthError("external scopes should not be provided with one authorization server");
81
109
  if (overrides?.scopes_supported) {
82
- const allValidScopes = new Set([...oauthProviderOptions?.scopes ?? [], ...opts?.externalScopes ?? []]);
110
+ const allValidScopes = /* @__PURE__ */ new Set([...oauthProviderOptions?.scopes ?? [], ...opts?.externalScopes ?? []]);
83
111
  for (const sc of overrides.scopes_supported) {
84
112
  if (sc === "openid") throw new BetterAuthError("Only the Auth Server should utilize the openid scope");
85
113
  if ([
package/dist/client.d.mts CHANGED
@@ -1,6 +1,4 @@
1
- import { r as oauthProvider } from "./oauth-BrNRbP2A.mjs";
2
- import * as _better_fetch_fetch0 from "@better-fetch/fetch";
3
-
1
+ import { r as oauthProvider } from "./oauth-CgWbnA8o.mjs";
4
2
  //#region src/client.d.ts
5
3
  declare const oauthProviderClient: () => {
6
4
  id: "oauth-provider-client";
@@ -10,7 +8,7 @@ declare const oauthProviderClient: () => {
10
8
  name: string;
11
9
  description: string;
12
10
  hooks: {
13
- onRequest<T extends Record<string, any>>(ctx: _better_fetch_fetch0.RequestContext<T>): Promise<void>;
11
+ onRequest<T extends Record<string, any>>(ctx: import("@better-fetch/fetch").RequestContext<T>): Promise<void>;
14
12
  };
15
13
  }[];
16
14
  $InferServerPlugin: ReturnType<typeof oauthProvider>;
package/dist/client.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { t as buildSignedOAuthQuery } from "./signed-query-Df1MNiSH.mjs";
2
- import { t as PACKAGE_VERSION } from "./version--aseHtsu.mjs";
1
+ import { t as buildSignedOAuthQuery } from "./signed-query-BQAwsV_w.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-xoFrTW0m.mjs";
3
3
  import { safeJSONParse } from "@better-auth/core/utils/json";
4
4
  //#region src/client.ts
5
5
  const oauthProviderClient = () => {
package/dist/index.d.mts CHANGED
@@ -1,10 +1,45 @@
1
- import { A as OAuthProviderExtension, B as StoreTokenType, C as OAuthConsent, D as OAuthOpaqueAccessToken, E as OAuthMetadataExtensionInput, F as OAuthTokenResponse, H as VerificationValue, I as OAuthUserInfoExtensionInput, L as Prompt, M as OAuthResource, N as OAuthResourceInput, O as OAuthOptions, P as OAuthTokenIssueParams, R as SchemaClient, S as OAuthClientResource, T as OAuthExtensionGrantHandlerInput, U as ClientRegistrationRequest, V as StoredAuthorizationQuery, W as ResourceUriSchema, _ as OAuthClaimExtensionInput, a as GrantType, b as OAuthClientAuthenticationResult, c as ResourceServerMetadata, d as ActiveAccessTokenPayload, f as AuthorizePrompt, g as OAuthAuthorizationQuery, h as OAuthAuthenticatedClient, i as Confirmation, j as OAuthRefreshToken, k as OAuthProviderApi, l as TokenEndpointAuthMethod, m as InitialAccessTokenAuthorization, n as AuthServerMetadata, o as OAuthClient, p as ClientDiscovery, r as BearerMethodsSupported, s as OIDCMetadata, t as AuthMethod, u as TokenType, v as OAuthClientAuthenticationInput, w as OAuthExtensionGrantHandler, x as OAuthClientAuthenticationStrategy, y as OAuthClientAuthenticationRequest, z as Scope } from "./oauth-ScTJEcFV.mjs";
2
- import { a as OAuthEndpointErrorResult, c as OAuthFieldErrorCode, i as getIssuer, l as OAuthFieldErrorCodeMap, n as getOAuthProviderState, o as OAuthEndpointRedirectContext, r as oauthProvider, s as OAuthErrorCode, t as DEFAULT_OAUTH_SCOPES, u as OAuthRedirectOnError } from "./oauth-BrNRbP2A.mjs";
3
- import { getSessionFromCtx } from "better-auth/api";
1
+ import { A as OAuthOpaqueAccessToken, B as Prompt, C as OAuthClientAuthenticationStrategy, D as OAuthExtensionGrantHandler, E as OAuthConsent, F as OAuthResource, G as VerificationValue, H as Scope, I as OAuthResourceInput, J as ResourceUriSchema, K as ClientRegistrationRequest, L as OAuthTokenIssueParams, M as OAuthProviderApi, N as OAuthProviderExtension, O as OAuthExtensionGrantHandlerInput, P as OAuthRefreshToken, R as OAuthTokenResponse, S as OAuthClientAuthenticationResult, T as OAuthClientResource, U as StoreTokenType, V as SchemaClient, W as StoredAuthorizationQuery, Y as oauthClientMetadataSchema, _ as OAuthAuthorizationQuery, a as GrantType, b as OAuthClientAuthenticationInput, c as ResourceServerMetadata, d as ActiveAccessTokenPayload, f as AuthorizePrompt, g as OAuthAuthenticatedClient, h as InitialAccessTokenAuthorization, i as Confirmation, j as OAuthOptions, k as OAuthMetadataExtensionInput, l as TokenEndpointAuthMethod, m as ClientMetadataResourceFetch, n as AuthServerMetadata, o as OAuthClient, p as ClientDiscovery, q as OAuthClientMetadata, r as BearerMethodsSupported, s as OIDCMetadata, t as AuthMethod, u as TokenType, v as OAuthClaimExtensionInput, w as OAuthClientRegistrationResponse, x as OAuthClientAuthenticationRequest, y as OAuthClientAdministrativeResponse, z as OAuthUserInfoExtensionInput } from "./oauth-Bi2PA_d1.mjs";
2
+ import { a as OAuthEndpointErrorResult, c as OAuthFieldErrorCode, i as getIssuer, l as OAuthFieldErrorCodeMap, n as getOAuthProviderState, o as OAuthEndpointRedirectContext, r as oauthProvider, s as OAuthErrorCode, t as DEFAULT_OAUTH_SCOPES, u as OAuthRedirectOnError } from "./oauth-CgWbnA8o.mjs";
3
+ import { APIError } from "better-call";
4
4
  import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
5
+ import { BetterAuthPlugin } from "better-auth/types";
5
6
  import { AuthContext, GenericEndpointContext } from "@better-auth/core";
6
- import * as better_auth0 from "better-auth";
7
-
7
+ //#region src/device-code.d.ts
8
+ /**
9
+ * RFC 8628 device authorization grant type. A registered OAuth client polls the
10
+ * token endpoint with this `grant_type` to exchange an approved device code for
11
+ * a first-class OAuth token set.
12
+ */
13
+ declare const DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
14
+ /**
15
+ * Bridges the {@link https://datatracker.ietf.org/doc/html/rfc8628 RFC 8628}
16
+ * device authorization grant into the OAuth Provider. Pair it with the
17
+ * `device-authorization` plugin (which owns the `/device/code` request endpoint,
18
+ * the user verification flow, and the `deviceCode` table) and the
19
+ * `oauthProvider` plugin: this registers a `device_code` token grant on
20
+ * `/oauth2/token` that issues real OAuth tokens for a registered OAuth client,
21
+ * and advertises `device_authorization_endpoint` in discovery metadata.
22
+ *
23
+ * First-party device login (the device-authorization plugin's own
24
+ * `/device/token`, which mints a Better Auth session token) keeps working
25
+ * unchanged. To stop a registered OAuth client's device code from being redeemed
26
+ * there for a session token, a `before` hook rejects `/device/token` requests
27
+ * whose `client_id` resolves to a registered OAuth client, directing them to
28
+ * `/oauth2/token`.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const auth = betterAuth({
33
+ * plugins: [
34
+ * deviceAuthorization(),
35
+ * oauthProvider({ ... }),
36
+ * deviceCodeGrant(),
37
+ * ],
38
+ * });
39
+ * ```
40
+ */
41
+ declare function deviceCodeGrant(): BetterAuthPlugin;
42
+ //#endregion
8
43
  //#region src/extensions.d.ts
9
44
  /**
10
45
  * Registers an {@link OAuthProviderExtension} with the OAuth Provider plugin
@@ -19,8 +54,8 @@ import * as better_auth0 from "better-auth";
19
54
  * twice. It throws if the oauth-provider plugin is not installed, if a grant
20
55
  * type or assertion type is not an absolute URI, if a client authentication
21
56
  * method reuses a built-in name, or if the extension registers a grant type,
22
- * auth method, or assertion type that another extension already registered
23
- * (contributions must be disjoint).
57
+ * auth method, assertion type, or client discovery identifier that another
58
+ * extension already registered (contributions must be disjoint).
24
59
  *
25
60
  * @example
26
61
  * ```ts
@@ -67,17 +102,17 @@ declare function oidcServerMetadata(ctx: GenericEndpointContext, opts: OAuthOpti
67
102
  response_modes_supported: "query"[];
68
103
  grant_types_supported: GrantType[];
69
104
  token_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | undefined;
70
- token_endpoint_auth_signing_alg_values_supported?: better_auth0.PrivateKeyJwtSigningAlgorithm[] | undefined;
105
+ token_endpoint_auth_signing_alg_values_supported?: import("better-auth").PrivateKeyJwtSigningAlgorithm[] | undefined;
71
106
  service_documentation?: string | undefined;
72
107
  ui_locales_supported?: string[] | undefined;
73
108
  op_policy_uri?: string | undefined;
74
109
  op_tos_uri?: string | undefined;
75
110
  revocation_endpoint?: string | undefined;
76
111
  revocation_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | undefined;
77
- revocation_endpoint_auth_signing_alg_values_supported?: better_auth0.PrivateKeyJwtSigningAlgorithm[] | undefined;
112
+ revocation_endpoint_auth_signing_alg_values_supported?: import("better-auth").PrivateKeyJwtSigningAlgorithm[] | undefined;
78
113
  introspection_endpoint?: string | undefined;
79
114
  introspection_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[] | undefined;
80
- introspection_endpoint_auth_signing_alg_values_supported?: better_auth0.PrivateKeyJwtSigningAlgorithm[] | undefined;
115
+ introspection_endpoint_auth_signing_alg_values_supported?: import("better-auth").PrivateKeyJwtSigningAlgorithm[] | undefined;
81
116
  code_challenge_methods_supported: "S256"[];
82
117
  authorization_response_iss_parameter_supported?: boolean | undefined;
83
118
  client_id_metadata_document_supported?: boolean | undefined;
@@ -118,36 +153,37 @@ declare const oauthProviderOpenIdConfigMetadata: <Auth extends {
118
153
  headers?: HeadersInit;
119
154
  }) => (request: Request) => Promise<Response>;
120
155
  //#endregion
121
- //#region src/register.d.ts
122
- declare function checkOAuthClient(client: OAuthClient, opts: OAuthOptions<Scope[]>, settings?: {
123
- isRegister?: boolean;
124
- ctx?: GenericEndpointContext;
125
- }): Promise<void>;
126
- /**
127
- * Converts an OAuth 2.0 Dynamic Client Schema to a Database Schema
128
- *
129
- * @param input
130
- * @returns
131
- */
132
- declare function oauthToSchema(input: OAuthClient): SchemaClient<Scope[]>;
133
- //#endregion
134
156
  //#region src/resource-challenge.d.ts
135
157
  /**
136
- * Raise an OAuth resource-server challenge for a failed access-token request.
158
+ * Create an OAuth resource-server challenge for a failed access-token request.
137
159
  *
138
160
  * Missing/invalid bearer credentials are reported with RFC 6750 plus the RFC
139
- * 9728 `resource_metadata` pointer. DPoP-bound-token failures are reported with
140
- * RFC 9449's `DPoP` challenge so clients know which proof algorithms to use.
141
- * Non-URL resources (for example a `urn:` or a client id) resolve their
142
- * metadata URL through `resourceMetadataMappings`.
161
+ * 9728 `resource_metadata` pointer. Insufficient-scope failures (built with
162
+ * `createInsufficientScopeError`) are reported with RFC 6750 §3.1's
163
+ * `insufficient_scope` challenge on a 403 naming the scopes the token lacks, so
164
+ * clients can step up their authorization. DPoP-bound-token failures are
165
+ * reported with RFC 9449's `DPoP` challenge so clients know which proof
166
+ * algorithms to use. Non-URL resources (for example a `urn:` or a client id)
167
+ * resolve their metadata URL through `resourceMetadataMappings`.
143
168
  *
144
- * @internal
169
+ * Every other error returns `undefined`, including a plain `FORBIDDEN`: a
170
+ * permission denial that re-authorizing cannot fix must not be answered with a
171
+ * challenge that sends the user through consent for scopes they already hold.
172
+ *
173
+ * @external
145
174
  */
146
- declare function raiseResourceServerChallenge(error: unknown, resource: string | string[], opts?: {
147
- /** Maps non-URL (urn, client) resources to their resource_metadata URL. */resourceMetadataMappings?: Record<string, string>; /** DPoP JWS algorithms to advertise in RFC 9449 challenges. */
148
- dpopSigningAlgorithms?: readonly string[]; /** Space-delimited scopes to advertise in RFC 6750 bearer challenges. */
149
- scope?: string;
150
- }): never;
175
+ declare function createResourceServerChallenge(error: unknown, resource: string | string[], opts?: {
176
+ /** Maps non-URL (urn, client) resources to their resource_metadata URL. */
177
+ resourceMetadataMappings?: Record<string, string>;
178
+ /** DPoP JWS algorithms to advertise in RFC 9449 challenges. */
179
+ dpopSigningAlgorithms?: readonly string[];
180
+ /**
181
+ * Scopes to advertise in RFC 6750 bearer challenges,
182
+ * hinting what an unauthenticated client should request. An
183
+ * insufficient-scope failure advertises the scopes it names instead.
184
+ */
185
+ challengeScopes?: readonly string[];
186
+ }): APIError | undefined;
151
187
  //#endregion
152
188
  //#region src/token.d.ts
153
189
  /**
@@ -194,4 +230,4 @@ declare function consumeClientAssertion(ctx: GenericEndpointContext, opts: OAuth
194
230
  expectedAudience: string;
195
231
  }): Promise<void>;
196
232
  //#endregion
197
- export { ActiveAccessTokenPayload, AuthMethod, AuthServerMetadata, AuthorizePrompt, BearerMethodsSupported, ClientDiscovery, ClientRegistrationRequest, Confirmation, DEFAULT_OAUTH_SCOPES, GrantType, InitialAccessTokenAuthorization, OAuthAuthenticatedClient, OAuthAuthorizationQuery, OAuthClaimExtensionInput, type OAuthClient, OAuthClientAuthenticationInput, OAuthClientAuthenticationRequest, OAuthClientAuthenticationResult, OAuthClientAuthenticationStrategy, OAuthClientResource, OAuthConsent, type OAuthEndpointErrorResult, type OAuthEndpointRedirectContext, type OAuthErrorCode, OAuthExtensionGrantHandler, OAuthExtensionGrantHandlerInput, type OAuthFieldErrorCode, type OAuthFieldErrorCodeMap, OAuthMetadataExtensionInput, OAuthOpaqueAccessToken, OAuthOptions, OAuthProviderApi, OAuthProviderExtension, type OAuthRedirectOnError, OAuthRefreshToken, OAuthResource, OAuthResourceInput, OAuthTokenIssueParams, OAuthTokenResponse, OAuthUserInfoExtensionInput, OIDCMetadata, Prompt, type ResourceServerMetadata, ResourceUriSchema, SchemaClient, Scope, StoreTokenType, StoredAuthorizationQuery, TokenEndpointAuthMethod, TokenType, VerificationValue, authServerMetadata, checkOAuthClient, consumeClientAssertion, extendOAuthProvider, getIssuer, getOAuthProviderApi, getOAuthProviderState, metadataResponse, oauthAuthorizationServerMetadata, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oauthToSchema, oidcServerMetadata, raiseResourceServerChallenge };
233
+ export { type ActiveAccessTokenPayload, type AuthMethod, type AuthServerMetadata, type AuthorizePrompt, type BearerMethodsSupported, type ClientDiscovery, type ClientMetadataResourceFetch, type ClientRegistrationRequest, type Confirmation, DEFAULT_OAUTH_SCOPES, DEVICE_CODE_GRANT_TYPE, type GrantType, type InitialAccessTokenAuthorization, type OAuthAuthenticatedClient, type OAuthAuthorizationQuery, type OAuthClaimExtensionInput, type OAuthClient, type OAuthClientAdministrativeResponse, type OAuthClientAuthenticationInput, type OAuthClientAuthenticationRequest, type OAuthClientAuthenticationResult, type OAuthClientAuthenticationStrategy, type OAuthClientMetadata, type OAuthClientRegistrationResponse, type OAuthClientResource, type OAuthConsent, type OAuthEndpointErrorResult, type OAuthEndpointRedirectContext, type OAuthErrorCode, type OAuthExtensionGrantHandler, type OAuthExtensionGrantHandlerInput, type OAuthFieldErrorCode, type OAuthFieldErrorCodeMap, type OAuthMetadataExtensionInput, type OAuthOpaqueAccessToken, type OAuthOptions, type OAuthProviderApi, type OAuthProviderExtension, type OAuthRedirectOnError, type OAuthRefreshToken, type OAuthResource, type OAuthResourceInput, type OAuthTokenIssueParams, type OAuthTokenResponse, type OAuthUserInfoExtensionInput, type OIDCMetadata, type Prompt, type ResourceServerMetadata, ResourceUriSchema, type SchemaClient, type Scope, type StoreTokenType, type StoredAuthorizationQuery, type TokenEndpointAuthMethod, type TokenType, type VerificationValue, authServerMetadata, consumeClientAssertion, createResourceServerChallenge, deviceCodeGrant, extendOAuthProvider, getIssuer, getOAuthProviderApi, getOAuthProviderState, metadataResponse, oauthAuthorizationServerMetadata, oauthClientMetadataSchema, oauthProvider, oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata, oidcServerMetadata };