@better-auth/oauth-provider 1.6.15 → 1.6.16

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-BJguNh6q.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-BJguNh6q.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-BJguNh6q.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",
@@ -842,12 +844,10 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
842
844
  }
843
845
  throw new Error(error);
844
846
  }
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
- }
847
+ if (!jwtPayload.azp) return { active: false };
848
+ const client = await getClient(ctx, opts, jwtPayload.azp);
849
+ if (!client || client?.disabled) return { active: false };
850
+ if (clientId && jwtPayload.azp !== clientId) return { active: false };
851
851
  const sessionId = jwtPayload.sid;
852
852
  if (sessionId) {
853
853
  const session = await ctx.context.adapter.findOne({
@@ -859,7 +859,7 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
859
859
  });
860
860
  if (!session || session.expiresAt < /* @__PURE__ */ new Date()) jwtPayload.sid = void 0;
861
861
  }
862
- if (jwtPayload.azp) jwtPayload.client_id = jwtPayload.azp;
862
+ jwtPayload.client_id = jwtPayload.azp;
863
863
  jwtPayload.active = true;
864
864
  return jwtPayload;
865
865
  }
@@ -3856,6 +3856,7 @@ async function authorizeEndpoint(ctx, opts, settings) {
3856
3856
  const client = await getClient(ctx, opts, query.client_id);
3857
3857
  if (!client) return handleRedirect(ctx, getErrorURL(ctx, "invalid_client", "client_id is required"));
3858
3858
  if (client.disabled) return handleRedirect(ctx, getErrorURL(ctx, "client_disabled", "client is disabled"));
3859
+ if (!clientAllowsGrant(client, "authorization_code")) return handleRedirect(ctx, getErrorURL(ctx, "unauthorized_client", "client is not authorized to use the authorization_code grant"));
3859
3860
  if (!client.redirectUris?.find((url) => {
3860
3861
  if (url === query.redirect_uri) return true;
3861
3862
  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.16";
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.16",
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/core": "1.6.16",
68
+ "better-auth": "1.6.16"
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.2.2",
73
+ "better-call": "1.3.6",
74
+ "@better-auth/core": "^1.6.16",
75
+ "better-auth": "^1.6.16"
76
76
  },
77
77
  "scripts": {
78
78
  "build": "tsdown",