@better-auth/oauth-provider 1.6.15 → 1.6.17

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.
@@ -49,6 +49,20 @@ interface VerifyAccessTokenRemote {
49
49
  * is also still active.
50
50
  */
51
51
  force?: boolean;
52
+ /**
53
+ * Accept introspection responses that omit the `aud` claim even when a
54
+ * required `audience` is configured in `verifyOptions`.
55
+ *
56
+ * By default verification fails closed: if you configure an `audience` and
57
+ * the introspection response has no `aud` (or a mismatching one), the token
58
+ * is rejected. Some authorization servers legitimately omit `aud` from
59
+ * introspection responses (it is OPTIONAL per RFC 7662 §2.2); only enable
60
+ * this if you trust the issuer to bind the token to this resource through
61
+ * another mechanism, as it skips the audience check in that case.
62
+ *
63
+ * @default false
64
+ */
65
+ allowMissingAudience?: boolean;
52
66
  }
53
67
  type VerifyAccessTokenOutput<T> = T extends undefined ? (token: string | undefined, opts: VerifyAccessTokenNoAuthOpts) => Promise<JWTPayload> : (token: string | undefined, opts?: VerifyAccessTokenAuthOpts) => Promise<JWTPayload>;
54
68
  type VerifyAccessTokenAuthOpts = {
@@ -1,5 +1,5 @@
1
- import { S as handleMcpErrors, a as getOAuthProviderPlugin, i as getJwtPlugin } from "./utils-DoYEeMrg.mjs";
2
- import { t as PACKAGE_VERSION } from "./version-CG3TZoBa.mjs";
1
+ import { C as handleMcpErrors, a as getJwtPlugin, o as getOAuthProviderPlugin } from "./utils-BKGiA6QL.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-yLooQum9.mjs";
3
3
  import { verifyAccessToken } from "better-auth/oauth2";
4
4
  import { APIError } from "better-call";
5
5
  import { logger } from "@better-auth/core/env";
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-CG3TZoBa.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-yLooQum9.mjs";
2
2
  import { safeJSONParse } from "@better-auth/core/utils/json";
3
3
  //#region src/client.ts
4
4
  function parseSignedQuery(search) {
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { C as mcpHandler, _ as signedQueryIssuedAtParam, b as validateClientCredentials, c as isPKCERequired, d as parsePrompt, f as postLoginClearedParam, g as searchParamsToQuery, h as resolveSubjectIdentifier, i as getJwtPlugin, l as normalizeTimestampValue, m as resolveSessionAuthTime, n as decryptStoredClientSecret, o as getSignedQueryIssuedAt, p as removePromptFromQuery, r as getClient, s as getStoredToken, t as basicToClientCredentials, u as parseClientMetadata, v as storeClientSecret, x as verifyOAuthQueryParams, y as storeToken } from "./utils-DoYEeMrg.mjs";
2
- import { t as PACKAGE_VERSION } from "./version-CG3TZoBa.mjs";
1
+ import { S as verifyOAuthQueryParams, _ as searchParamsToQuery, a as getJwtPlugin, b as storeToken, c as getStoredToken, d as parseClientMetadata, f as parsePrompt, g as resolveSubjectIdentifier, h as resolveSessionAuthTime, i as getClient, l as isPKCERequired, m as removePromptFromQuery, n as clientAllowsGrant, p as postLoginClearedParam, r as decryptStoredClientSecret, s as getSignedQueryIssuedAt, t as basicToClientCredentials, u as normalizeTimestampValue, v as signedQueryIssuedAtParam, w as mcpHandler, x as validateClientCredentials, y as storeClientSecret } from "./utils-BKGiA6QL.mjs";
2
+ import { t as PACKAGE_VERSION } from "./version-yLooQum9.mjs";
3
3
  import { APIError, createAuthEndpoint, createAuthMiddleware, dispatchAuthEndpoint, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
4
4
  import { generateCodeChallenge, getJwks, verifyJwsAccessToken } from "better-auth/oauth2";
5
5
  import { APIError as APIError$1 } from "better-call";
@@ -143,7 +143,8 @@ async function created(ctx, authorize) {
143
143
  return await authorize(ctx);
144
144
  }
145
145
  async function postLogin(ctx, authorize) {
146
- const _query = (await oAuthState.get())?.query;
146
+ const state = await oAuthState.get();
147
+ const _query = state?.query;
147
148
  if (!_query) throw new APIError("BAD_REQUEST", {
148
149
  error_description: "missing oauth query",
149
150
  error: "invalid_request"
@@ -151,7 +152,8 @@ async function postLogin(ctx, authorize) {
151
152
  const query = new URLSearchParams(_query);
152
153
  ctx.headers?.set("accept", "application/json");
153
154
  ctx.query = searchParamsToQuery(query);
154
- return await authorize(ctx, { postLogin: true });
155
+ const session = await getSessionFromCtx(ctx);
156
+ return await authorize(ctx, { postLogin: state?.postLoginClearedForSession !== void 0 && state.postLoginClearedForSession === session?.session.id });
155
157
  }
156
158
  //#endregion
157
159
  //#region src/types/zod.ts
@@ -503,7 +505,7 @@ async function createUserTokens(ctx, opts, params) {
503
505
  return prev < curr ? prev : curr;
504
506
  }, defaultExp) : defaultExp;
505
507
  const audience = await checkResource(ctx, opts, scopes);
506
- const isRefreshToken = user && (existingRefreshToken?.scopes?.includes("offline_access") || scopes.includes("offline_access"));
508
+ const isRefreshToken = user && clientAllowsGrant(client, "refresh_token") && (existingRefreshToken?.scopes?.includes("offline_access") || scopes.includes("offline_access"));
507
509
  const isJwtAccessToken = audience && !opts.disableJwtPlugin;
508
510
  const isIdToken = user && scopes.includes("openid");
509
511
  const customFields = opts.customTokenResponseFields ? await opts.customTokenResponseFields({
@@ -618,7 +620,7 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
618
620
  error: "invalid_scope"
619
621
  });
620
622
  /** Verify Client */
621
- const client = await validateClientCredentials(ctx, opts, client_id, client_secret, scopes);
623
+ const client = await validateClientCredentials(ctx, opts, client_id, client_secret, scopes, "authorization_code");
622
624
  if (isPKCERequired(client, (verificationValue.query?.scope)?.split(" ") || [])) {
623
625
  if (!isAuthCodeWithPkce) throw new APIError("BAD_REQUEST", {
624
626
  error_description: "PKCE is required for this client",
@@ -701,7 +703,7 @@ async function handleClientCredentialsGrant(ctx, opts) {
701
703
  error_description: "Missing a required client_secret",
702
704
  error: "invalid_grant"
703
705
  });
704
- const client = await validateClientCredentials(ctx, opts, client_id, client_secret);
706
+ const client = await validateClientCredentials(ctx, opts, client_id, client_secret, void 0, "client_credentials");
705
707
  let requestedScopes = scope?.split(" ");
706
708
  if (requestedScopes) {
707
709
  const validScopes = new Set(client.scopes ?? opts.scopes);
@@ -784,7 +786,7 @@ async function handleRefreshTokenGrant(ctx, opts) {
784
786
  error: "invalid_scope"
785
787
  });
786
788
  }
787
- const client = await validateClientCredentials(ctx, opts, client_id, client_secret, requestedScopes ?? scopes);
789
+ const client = await validateClientCredentials(ctx, opts, client_id, client_secret, requestedScopes ?? scopes, "refresh_token");
788
790
  const user = await ctx.context.internalAdapter.findUserById(refreshToken.userId);
789
791
  if (!user) throw new APIError("BAD_REQUEST", {
790
792
  error_description: "user not found",
@@ -825,6 +827,7 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
825
827
  jwksFetch: jwtPluginOptions?.jwks?.remoteUrl ? jwtPluginOptions.jwks.remoteUrl : async () => {
826
828
  return (await jwtPlugin?.endpoints.getJwks(ctx))?.response;
827
829
  },
830
+ jwksCacheKey: jwtPlugin,
828
831
  verifyOptions: {
829
832
  audience: opts.validAudiences ?? ctx.context.baseURL,
830
833
  issuer: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL
@@ -842,12 +845,10 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
842
845
  }
843
846
  throw new Error(error);
844
847
  }
845
- let client;
846
- if (jwtPayload.azp) {
847
- client = await getClient(ctx, opts, jwtPayload.azp);
848
- if (!client || client?.disabled) return { active: false };
849
- if (clientId && jwtPayload.azp !== clientId) return { active: false };
850
- }
848
+ if (!jwtPayload.azp) return { active: false };
849
+ const client = await getClient(ctx, opts, jwtPayload.azp);
850
+ if (!client || client?.disabled) return { active: false };
851
+ if (clientId && jwtPayload.azp !== clientId) return { active: false };
851
852
  const sessionId = jwtPayload.sid;
852
853
  if (sessionId) {
853
854
  const session = await ctx.context.adapter.findOne({
@@ -859,7 +860,7 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
859
860
  });
860
861
  if (!session || session.expiresAt < /* @__PURE__ */ new Date()) jwtPayload.sid = void 0;
861
862
  }
862
- if (jwtPayload.azp) jwtPayload.client_id = jwtPayload.azp;
863
+ jwtPayload.client_id = jwtPayload.azp;
863
864
  jwtPayload.active = true;
864
865
  return jwtPayload;
865
866
  }
@@ -2268,6 +2269,7 @@ async function revokeJwtAccessToken(ctx, opts, token) {
2268
2269
  jwksFetch: jwtPluginOptions?.jwks?.remoteUrl ? jwtPluginOptions.jwks.remoteUrl : async () => {
2269
2270
  return (await jwtPlugin?.endpoints.getJwks(ctx))?.response;
2270
2271
  },
2272
+ jwksCacheKey: jwtPlugin,
2271
2273
  verifyOptions: {
2272
2274
  audience: opts.validAudiences ?? ctx.context.baseURL,
2273
2275
  issuer: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL
@@ -3856,6 +3858,7 @@ async function authorizeEndpoint(ctx, opts, settings) {
3856
3858
  const client = await getClient(ctx, opts, query.client_id);
3857
3859
  if (!client) return handleRedirect(ctx, getErrorURL(ctx, "invalid_client", "client_id is required"));
3858
3860
  if (client.disabled) return handleRedirect(ctx, getErrorURL(ctx, "client_disabled", "client is disabled"));
3861
+ if (!clientAllowsGrant(client, "authorization_code")) return handleRedirect(ctx, getErrorURL(ctx, "unauthorized_client", "client is not authorized to use the authorization_code grant"));
3859
3862
  if (!client.redirectUris?.find((url) => {
3860
3863
  if (url === query.redirect_uri) return true;
3861
3864
  try {
@@ -272,12 +272,27 @@ function basicToClientCredentials(authorization) {
272
272
  }
273
273
  }
274
274
  /**
275
+ * Whether a client is allowed to use a given grant type.
276
+ *
277
+ * A client's registered `grantTypes` defaults to the documented default
278
+ * `["authorization_code"]` when unset (see client registration). Refresh tokens
279
+ * are only ever issued through the authorization_code flow, so a client allowed
280
+ * to use `authorization_code` is implicitly allowed to use `refresh_token`.
281
+ *
282
+ * @internal
283
+ */
284
+ function clientAllowsGrant(client, grantType) {
285
+ const allowedGrants = client.grantTypes && client.grantTypes.length > 0 ? client.grantTypes : ["authorization_code"];
286
+ if (grantType === "refresh_token" && allowedGrants.includes("authorization_code")) return true;
287
+ return allowedGrants.includes(grantType);
288
+ }
289
+ /**
275
290
  * Validates client credentials failing on mismatches
276
291
  * and incorrectly provided information
277
292
  *
278
293
  * @internal
279
294
  */
280
- async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes) {
295
+ async function validateClientCredentials(ctx, options, clientId, clientSecret, scopes, grantType) {
281
296
  const client = await getClient(ctx, options, clientId);
282
297
  if (!client) throw new APIError$1("BAD_REQUEST", {
283
298
  error_description: "missing client",
@@ -306,6 +321,10 @@ async function validateClientCredentials(ctx, options, clientId, clientSecret, s
306
321
  error: "invalid_scope"
307
322
  });
308
323
  }
324
+ if (grantType && !clientAllowsGrant(client, grantType)) throw new APIError$1("BAD_REQUEST", {
325
+ error_description: `client is not authorized to use grant type ${grantType}`,
326
+ error: "unauthorized_client"
327
+ });
309
328
  return client;
310
329
  }
311
330
  /**
@@ -417,4 +436,4 @@ function isPKCERequired(client, requestedScopes) {
417
436
  return false;
418
437
  }
419
438
  //#endregion
420
- export { mcpHandler as C, handleMcpErrors as S, signedQueryIssuedAtParam as _, getOAuthProviderPlugin as a, validateClientCredentials as b, isPKCERequired as c, parsePrompt as d, postLoginClearedParam as f, searchParamsToQuery as g, resolveSubjectIdentifier as h, getJwtPlugin as i, normalizeTimestampValue as l, resolveSessionAuthTime as m, decryptStoredClientSecret as n, getSignedQueryIssuedAt as o, removePromptFromQuery as p, getClient as r, getStoredToken as s, basicToClientCredentials as t, parseClientMetadata as u, storeClientSecret as v, verifyOAuthQueryParams as x, storeToken as y };
439
+ export { handleMcpErrors as C, verifyOAuthQueryParams as S, searchParamsToQuery as _, getJwtPlugin as a, storeToken as b, getStoredToken as c, parseClientMetadata as d, parsePrompt as f, resolveSubjectIdentifier as g, resolveSessionAuthTime as h, getClient as i, isPKCERequired as l, removePromptFromQuery as m, clientAllowsGrant as n, getOAuthProviderPlugin as o, postLoginClearedParam as p, decryptStoredClientSecret as r, getSignedQueryIssuedAt as s, basicToClientCredentials as t, normalizeTimestampValue as u, signedQueryIssuedAtParam as v, mcpHandler as w, validateClientCredentials as x, storeClientSecret as y };
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.6.15";
3
+ const PACKAGE_VERSION = "1.6.17";
4
4
  //#endregion
5
5
  export { PACKAGE_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/oauth-provider",
3
- "version": "1.6.15",
3
+ "version": "1.6.17",
4
4
  "description": "An oauth provider plugin for Better Auth",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -64,15 +64,15 @@
64
64
  "@modelcontextprotocol/sdk": "^1.27.1",
65
65
  "listhen": "^1.9.0",
66
66
  "tsdown": "0.21.1",
67
- "@better-auth/core": "1.6.15",
68
- "better-auth": "1.6.15"
67
+ "better-auth": "1.6.17",
68
+ "@better-auth/core": "1.6.17"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@better-auth/utils": "0.4.1",
72
- "@better-fetch/fetch": "1.1.21",
73
- "better-call": "1.3.5",
74
- "@better-auth/core": "^1.6.15",
75
- "better-auth": "^1.6.15"
72
+ "@better-fetch/fetch": "1.3.0",
73
+ "better-call": "1.3.6",
74
+ "@better-auth/core": "^1.6.17",
75
+ "better-auth": "^1.6.17"
76
76
  },
77
77
  "scripts": {
78
78
  "build": "tsdown",