@better-auth/oauth-provider 1.6.14 → 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-CrTJknzp.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.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as oauthProvider } from "./oauth-CUqnBdrR.mjs";
1
+ import { n as oauthProvider } from "./oauth-DZ80cNUW.mjs";
2
2
  import * as _better_fetch_fetch0 from "@better-fetch/fetch";
3
3
 
4
4
  //#region src/client.d.ts
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-CrTJknzp.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.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { _ as Scope, a as OAuthClient, b as Awaitable, c as TokenEndpointAuthMethod, d as OAuthConsent, f as OAuthOpaqueAccessToken, g as SchemaClient, h as Prompt, i as GrantType, l as AuthorizePrompt, m as OAuthRefreshToken, n as AuthServerMetadata, o as OIDCMetadata, p as OAuthOptions, r as BearerMethodsSupported, s as ResourceServerMetadata, t as AuthMethod, u as OAuthAuthorizationQuery, v as StoreTokenType, y as VerificationValue } from "./oauth-D74mBkw6.mjs";
2
- import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-CUqnBdrR.mjs";
2
+ import { n as oauthProvider, t as getOAuthProviderState } from "./oauth-DZ80cNUW.mjs";
3
3
  import { verifyAccessToken } from "better-auth/oauth2";
4
4
  import { JWSAlgorithms, JwtOptions } from "better-auth/plugins";
5
5
  import { JWTPayload } from "jose";
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
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-CrTJknzp.mjs";
3
- import { APIError, createAuthEndpoint, createAuthMiddleware, getOAuthState, getSessionFromCtx, sessionMiddleware } from "better-auth/api";
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
+ 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";
6
6
  import { isBrowserFetchRequest } from "@better-auth/core/utils/fetch-metadata";
@@ -16,7 +16,7 @@ import { signJWT, toExpJWT } from "better-auth/plugins";
16
16
  import { SignJWT, compactVerify, createLocalJWKSet, decodeJwt } from "jose";
17
17
  import { SafeUrlSchema } from "@better-auth/core/utils/redirect-uri";
18
18
  //#region src/consent.ts
19
- async function consentEndpoint(ctx, opts) {
19
+ async function consentEndpoint(ctx, opts, authorize) {
20
20
  const oauthRequest = await oAuthState.get();
21
21
  const _query = oauthRequest?.query;
22
22
  if (!_query) throw new APIError("BAD_REQUEST", {
@@ -47,11 +47,7 @@ async function consentEndpoint(ctx, opts) {
47
47
  if (hasLoginPrompt && !hasSatisfiedLoginPrompt) {
48
48
  ctx?.headers?.set("accept", "application/json");
49
49
  ctx.query = searchParamsToQuery(query);
50
- const { url } = await authorizeEndpoint(ctx, opts);
51
- return {
52
- redirect: true,
53
- url
54
- };
50
+ return await authorize(ctx);
55
51
  }
56
52
  const referenceId = await opts.postLogin?.consentReferenceId?.({
57
53
  user: session?.user,
@@ -106,11 +102,7 @@ async function consentEndpoint(ctx, opts) {
106
102
  let authorizationQuery = removePromptFromQuery(query, "consent");
107
103
  if (hasSatisfiedLoginPrompt) authorizationQuery = removePromptFromQuery(authorizationQuery, "login");
108
104
  ctx.query = searchParamsToQuery(authorizationQuery);
109
- const { url } = await authorizeEndpoint(ctx, opts, { postLogin: oauthRequest?.postLoginClearedForSession !== void 0 && oauthRequest.postLoginClearedForSession === session?.session.id });
110
- return {
111
- redirect: true,
112
- url
113
- };
105
+ return await authorize(ctx, { postLogin: oauthRequest?.postLoginClearedForSession !== void 0 && oauthRequest.postLoginClearedForSession === session?.session.id });
114
106
  }
115
107
  function sessionSatisfiesLoginPrompt(sessionCreatedAt, signedQueryIssuedAt) {
116
108
  if (!signedQueryIssuedAt) return false;
@@ -120,16 +112,16 @@ function sessionSatisfiesLoginPrompt(sessionCreatedAt, signedQueryIssuedAt) {
120
112
  }
121
113
  //#endregion
122
114
  //#region src/continue.ts
123
- async function continueEndpoint(ctx, opts) {
124
- if (ctx.body.selected === true) return await selected(ctx, opts);
125
- else if (ctx.body.created === true) return await created(ctx, opts);
126
- else if (ctx.body.postLogin === true) return await postLogin(ctx, opts);
115
+ async function continueEndpoint(ctx, authorize) {
116
+ if (ctx.body.selected === true) return await selected(ctx, authorize);
117
+ else if (ctx.body.created === true) return await created(ctx, authorize);
118
+ else if (ctx.body.postLogin === true) return await postLogin(ctx, authorize);
127
119
  else throw new APIError("BAD_REQUEST", {
128
120
  error_description: "Missing parameters",
129
121
  error: "invalid_request"
130
122
  });
131
123
  }
132
- async function selected(ctx, opts) {
124
+ async function selected(ctx, authorize) {
133
125
  const _query = (await oAuthState.get())?.query;
134
126
  if (!_query) throw new APIError("BAD_REQUEST", {
135
127
  error_description: "missing oauth query",
@@ -137,13 +129,9 @@ async function selected(ctx, opts) {
137
129
  });
138
130
  ctx.headers?.set("accept", "application/json");
139
131
  ctx.query = searchParamsToQuery(removePromptFromQuery(new URLSearchParams(_query), "select_account"));
140
- const { url } = await authorizeEndpoint(ctx, opts);
141
- return {
142
- redirect: true,
143
- url
144
- };
132
+ return await authorize(ctx);
145
133
  }
146
- async function created(ctx, opts) {
134
+ async function created(ctx, authorize) {
147
135
  const _query = (await oAuthState.get())?.query;
148
136
  if (!_query) throw new APIError("BAD_REQUEST", {
149
137
  error_description: "missing oauth query",
@@ -152,14 +140,11 @@ async function created(ctx, opts) {
152
140
  const query = new URLSearchParams(_query);
153
141
  ctx.headers?.set("accept", "application/json");
154
142
  ctx.query = searchParamsToQuery(removePromptFromQuery(query, "create"));
155
- const { url } = await authorizeEndpoint(ctx, opts);
156
- return {
157
- redirect: true,
158
- url
159
- };
143
+ return await authorize(ctx);
160
144
  }
161
- async function postLogin(ctx, opts) {
162
- const _query = (await oAuthState.get())?.query;
145
+ async function postLogin(ctx, authorize) {
146
+ const state = await oAuthState.get();
147
+ const _query = state?.query;
163
148
  if (!_query) throw new APIError("BAD_REQUEST", {
164
149
  error_description: "missing oauth query",
165
150
  error: "invalid_request"
@@ -167,11 +152,8 @@ async function postLogin(ctx, opts) {
167
152
  const query = new URLSearchParams(_query);
168
153
  ctx.headers?.set("accept", "application/json");
169
154
  ctx.query = searchParamsToQuery(query);
170
- const { url } = await authorizeEndpoint(ctx, opts, { postLogin: true });
171
- return {
172
- redirect: true,
173
- url
174
- };
155
+ const session = await getSessionFromCtx(ctx);
156
+ return await authorize(ctx, { postLogin: state?.postLoginClearedForSession !== void 0 && state.postLoginClearedForSession === session?.session.id });
175
157
  }
176
158
  //#endregion
177
159
  //#region src/types/zod.ts
@@ -523,7 +505,7 @@ async function createUserTokens(ctx, opts, params) {
523
505
  return prev < curr ? prev : curr;
524
506
  }, defaultExp) : defaultExp;
525
507
  const audience = await checkResource(ctx, opts, scopes);
526
- 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"));
527
509
  const isJwtAccessToken = audience && !opts.disableJwtPlugin;
528
510
  const isIdToken = user && scopes.includes("openid");
529
511
  const customFields = opts.customTokenResponseFields ? await opts.customTokenResponseFields({
@@ -638,7 +620,7 @@ async function handleAuthorizationCodeGrant(ctx, opts) {
638
620
  error: "invalid_scope"
639
621
  });
640
622
  /** Verify Client */
641
- 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");
642
624
  if (isPKCERequired(client, (verificationValue.query?.scope)?.split(" ") || [])) {
643
625
  if (!isAuthCodeWithPkce) throw new APIError("BAD_REQUEST", {
644
626
  error_description: "PKCE is required for this client",
@@ -721,7 +703,7 @@ async function handleClientCredentialsGrant(ctx, opts) {
721
703
  error_description: "Missing a required client_secret",
722
704
  error: "invalid_grant"
723
705
  });
724
- 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");
725
707
  let requestedScopes = scope?.split(" ");
726
708
  if (requestedScopes) {
727
709
  const validScopes = new Set(client.scopes ?? opts.scopes);
@@ -804,7 +786,7 @@ async function handleRefreshTokenGrant(ctx, opts) {
804
786
  error: "invalid_scope"
805
787
  });
806
788
  }
807
- 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");
808
790
  const user = await ctx.context.internalAdapter.findUserById(refreshToken.userId);
809
791
  if (!user) throw new APIError("BAD_REQUEST", {
810
792
  error_description: "user not found",
@@ -862,12 +844,10 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
862
844
  }
863
845
  throw new Error(error);
864
846
  }
865
- let client;
866
- if (jwtPayload.azp) {
867
- client = await getClient(ctx, opts, jwtPayload.azp);
868
- if (!client || client?.disabled) return { active: false };
869
- if (clientId && jwtPayload.azp !== clientId) return { active: false };
870
- }
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 };
871
851
  const sessionId = jwtPayload.sid;
872
852
  if (sessionId) {
873
853
  const session = await ctx.context.adapter.findOne({
@@ -879,7 +859,7 @@ async function validateJwtAccessToken(ctx, opts, token, clientId) {
879
859
  });
880
860
  if (!session || session.expiresAt < /* @__PURE__ */ new Date()) jwtPayload.sid = void 0;
881
861
  }
882
- if (jwtPayload.azp) jwtPayload.client_id = jwtPayload.azp;
862
+ jwtPayload.client_id = jwtPayload.azp;
883
863
  jwtPayload.active = true;
884
864
  return jwtPayload;
885
865
  }
@@ -2849,6 +2829,140 @@ const oauthProvider = (options) => {
2849
2829
  }
2850
2830
  if (isOpenIdConfigRequest) return { response: createMetadataResponse(oidcServerMetadata(endpointCtx, opts)) };
2851
2831
  };
2832
+ const oauth2AuthorizeEndpoint = createAuthEndpoint("/oauth2/authorize", {
2833
+ method: "GET",
2834
+ query: z.object({
2835
+ response_type: z.enum(["code"]).optional(),
2836
+ client_id: z.string(),
2837
+ redirect_uri: SafeUrlSchema.optional(),
2838
+ scope: z.string().optional(),
2839
+ state: z.string().optional(),
2840
+ request_uri: z.string().optional(),
2841
+ code_challenge: z.string().optional(),
2842
+ code_challenge_method: z.enum(["S256"]).optional(),
2843
+ nonce: z.string().optional(),
2844
+ prompt: z.enum([
2845
+ "none",
2846
+ "consent",
2847
+ "login",
2848
+ "create",
2849
+ "select_account",
2850
+ "login consent",
2851
+ "select_account consent"
2852
+ ]).optional()
2853
+ }),
2854
+ metadata: { openapi: {
2855
+ description: "Authorize an OAuth2 request",
2856
+ parameters: [
2857
+ {
2858
+ name: "response_type",
2859
+ in: "query",
2860
+ required: false,
2861
+ schema: { type: "string" },
2862
+ description: "OAuth2 response type (e.g., 'code')"
2863
+ },
2864
+ {
2865
+ name: "client_id",
2866
+ in: "query",
2867
+ required: true,
2868
+ schema: { type: "string" },
2869
+ description: "OAuth2 client ID"
2870
+ },
2871
+ {
2872
+ name: "redirect_uri",
2873
+ in: "query",
2874
+ required: false,
2875
+ schema: {
2876
+ type: "string",
2877
+ format: "uri"
2878
+ },
2879
+ description: "OAuth2 redirect URI"
2880
+ },
2881
+ {
2882
+ name: "scope",
2883
+ in: "query",
2884
+ required: false,
2885
+ schema: { type: "string" },
2886
+ description: "OAuth2 scopes (space-separated)"
2887
+ },
2888
+ {
2889
+ name: "state",
2890
+ in: "query",
2891
+ required: false,
2892
+ schema: { type: "string" },
2893
+ description: "OAuth2 state parameter"
2894
+ },
2895
+ {
2896
+ name: "request_uri",
2897
+ in: "query",
2898
+ required: false,
2899
+ schema: { type: "string" },
2900
+ description: "Pushed Authorization Request URI referencing stored parameters"
2901
+ },
2902
+ {
2903
+ name: "code_challenge",
2904
+ in: "query",
2905
+ required: false,
2906
+ schema: { type: "string" },
2907
+ description: "PKCE code challenge"
2908
+ },
2909
+ {
2910
+ name: "code_challenge_method",
2911
+ in: "query",
2912
+ required: false,
2913
+ schema: { type: "string" },
2914
+ description: "PKCE code challenge method"
2915
+ },
2916
+ {
2917
+ name: "nonce",
2918
+ in: "query",
2919
+ required: false,
2920
+ schema: { type: "string" },
2921
+ description: "OpenID Connect nonce"
2922
+ },
2923
+ {
2924
+ name: "prompt",
2925
+ in: "query",
2926
+ required: false,
2927
+ schema: { type: "string" },
2928
+ description: "OAuth2 prompt parameter"
2929
+ }
2930
+ ],
2931
+ responses: {
2932
+ "302": {
2933
+ description: "Redirect to client with code or error",
2934
+ headers: { Location: {
2935
+ description: "Redirect URI with code or error",
2936
+ schema: {
2937
+ type: "string",
2938
+ format: "uri"
2939
+ }
2940
+ } }
2941
+ },
2942
+ "400": {
2943
+ description: "Invalid request",
2944
+ content: { "application/json": { schema: {
2945
+ type: "object",
2946
+ properties: {
2947
+ error: { type: "string" },
2948
+ error_description: { type: "string" },
2949
+ state: { type: "string" }
2950
+ },
2951
+ required: ["error"]
2952
+ } } }
2953
+ }
2954
+ }
2955
+ } }
2956
+ }, async (ctx) => {
2957
+ return authorizeEndpoint(ctx, opts, ctx.authorizeSettings ?? { isAuthorize: true });
2958
+ });
2959
+ const runOAuth2Authorize = (ctx, settings) => dispatchAuthEndpoint(oauth2AuthorizeEndpoint, {
2960
+ ...ctx,
2961
+ asResponse: false,
2962
+ returnHeaders: false,
2963
+ returnStatus: false,
2964
+ authorizeSettings: settings ?? {}
2965
+ });
2852
2966
  return {
2853
2967
  id: "oauth-provider",
2854
2968
  version: PACKAGE_VERSION,
@@ -2915,7 +3029,7 @@ const oauthProvider = (options) => {
2915
3029
  const acceptHeader = ctx.request?.headers?.get("accept")?.toLowerCase() ?? "";
2916
3030
  if (!(secFetchMode === "navigate" || !secFetchMode && (acceptHeader.includes("text/html") || acceptHeader.includes("application/xhtml+xml")))) ctx.headers?.set("accept", "application/json");
2917
3031
  ctx.query = searchParamsToQuery(removePromptFromQuery(query, "login"));
2918
- return await authorizeEndpoint(ctx, opts);
3032
+ return await runOAuth2Authorize(ctx);
2919
3033
  })
2920
3034
  }]
2921
3035
  },
@@ -2940,133 +3054,7 @@ const oauthProvider = (options) => {
2940
3054
  if (opts.scopes && !opts.scopes.includes("openid")) throw new APIError("NOT_FOUND");
2941
3055
  return oidcServerMetadata(ctx, opts);
2942
3056
  }),
2943
- oauth2Authorize: createAuthEndpoint("/oauth2/authorize", {
2944
- method: "GET",
2945
- query: z.object({
2946
- response_type: z.enum(["code"]).optional(),
2947
- client_id: z.string(),
2948
- redirect_uri: SafeUrlSchema.optional(),
2949
- scope: z.string().optional(),
2950
- state: z.string().optional(),
2951
- request_uri: z.string().optional(),
2952
- code_challenge: z.string().optional(),
2953
- code_challenge_method: z.enum(["S256"]).optional(),
2954
- nonce: z.string().optional(),
2955
- prompt: z.enum([
2956
- "none",
2957
- "consent",
2958
- "login",
2959
- "create",
2960
- "select_account",
2961
- "login consent",
2962
- "select_account consent"
2963
- ]).optional()
2964
- }),
2965
- metadata: { openapi: {
2966
- description: "Authorize an OAuth2 request",
2967
- parameters: [
2968
- {
2969
- name: "response_type",
2970
- in: "query",
2971
- required: false,
2972
- schema: { type: "string" },
2973
- description: "OAuth2 response type (e.g., 'code')"
2974
- },
2975
- {
2976
- name: "client_id",
2977
- in: "query",
2978
- required: true,
2979
- schema: { type: "string" },
2980
- description: "OAuth2 client ID"
2981
- },
2982
- {
2983
- name: "redirect_uri",
2984
- in: "query",
2985
- required: false,
2986
- schema: {
2987
- type: "string",
2988
- format: "uri"
2989
- },
2990
- description: "OAuth2 redirect URI"
2991
- },
2992
- {
2993
- name: "scope",
2994
- in: "query",
2995
- required: false,
2996
- schema: { type: "string" },
2997
- description: "OAuth2 scopes (space-separated)"
2998
- },
2999
- {
3000
- name: "state",
3001
- in: "query",
3002
- required: false,
3003
- schema: { type: "string" },
3004
- description: "OAuth2 state parameter"
3005
- },
3006
- {
3007
- name: "request_uri",
3008
- in: "query",
3009
- required: false,
3010
- schema: { type: "string" },
3011
- description: "Pushed Authorization Request URI referencing stored parameters"
3012
- },
3013
- {
3014
- name: "code_challenge",
3015
- in: "query",
3016
- required: false,
3017
- schema: { type: "string" },
3018
- description: "PKCE code challenge"
3019
- },
3020
- {
3021
- name: "code_challenge_method",
3022
- in: "query",
3023
- required: false,
3024
- schema: { type: "string" },
3025
- description: "PKCE code challenge method"
3026
- },
3027
- {
3028
- name: "nonce",
3029
- in: "query",
3030
- required: false,
3031
- schema: { type: "string" },
3032
- description: "OpenID Connect nonce"
3033
- },
3034
- {
3035
- name: "prompt",
3036
- in: "query",
3037
- required: false,
3038
- schema: { type: "string" },
3039
- description: "OAuth2 prompt parameter"
3040
- }
3041
- ],
3042
- responses: {
3043
- "302": {
3044
- description: "Redirect to client with code or error",
3045
- headers: { Location: {
3046
- description: "Redirect URI with code or error",
3047
- schema: {
3048
- type: "string",
3049
- format: "uri"
3050
- }
3051
- } }
3052
- },
3053
- "400": {
3054
- description: "Invalid request",
3055
- content: { "application/json": { schema: {
3056
- type: "object",
3057
- properties: {
3058
- error: { type: "string" },
3059
- error_description: { type: "string" },
3060
- state: { type: "string" }
3061
- },
3062
- required: ["error"]
3063
- } } }
3064
- }
3065
- }
3066
- } }
3067
- }, async (ctx) => {
3068
- return authorizeEndpoint(ctx, opts, { isAuthorize: true });
3069
- }),
3057
+ oauth2Authorize: oauth2AuthorizeEndpoint,
3070
3058
  oauth2Consent: createAuthEndpoint("/oauth2/consent", {
3071
3059
  method: "POST",
3072
3060
  body: z.object({
@@ -3091,7 +3079,7 @@ const oauthProvider = (options) => {
3091
3079
  } }
3092
3080
  } }
3093
3081
  }, async (ctx) => {
3094
- return consentEndpoint(ctx, opts);
3082
+ return consentEndpoint(ctx, opts, runOAuth2Authorize);
3095
3083
  }),
3096
3084
  oauth2Continue: createAuthEndpoint("/oauth2/continue", {
3097
3085
  method: "POST",
@@ -3118,7 +3106,7 @@ const oauthProvider = (options) => {
3118
3106
  } }
3119
3107
  } }
3120
3108
  }, async (ctx) => {
3121
- return continueEndpoint(ctx, opts);
3109
+ return continueEndpoint(ctx, runOAuth2Authorize);
3122
3110
  }),
3123
3111
  oauth2Token: createAuthEndpoint("/oauth2/token", {
3124
3112
  method: "POST",
@@ -3432,7 +3420,7 @@ const oauthProvider = (options) => {
3432
3420
  return revokeEndpoint(ctx, opts);
3433
3421
  }),
3434
3422
  oauth2UserInfo: createAuthEndpoint("/oauth2/userinfo", {
3435
- method: "GET",
3423
+ method: ["GET", "POST"],
3436
3424
  metadata: { openapi: {
3437
3425
  description: "Get OpenID Connect user information (UserInfo endpoint)",
3438
3426
  security: [{ bearerAuth: [] }, { OAuth2: [
@@ -3868,6 +3856,7 @@ async function authorizeEndpoint(ctx, opts, settings) {
3868
3856
  const client = await getClient(ctx, opts, query.client_id);
3869
3857
  if (!client) return handleRedirect(ctx, getErrorURL(ctx, "invalid_client", "client_id is required"));
3870
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"));
3871
3860
  if (!client.redirectUris?.find((url) => {
3872
3861
  if (url === query.redirect_uri) return true;
3873
3862
  try {
@@ -234,6 +234,9 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
234
234
  }, {
235
235
  redirect: boolean;
236
236
  url: string;
237
+ } | {
238
+ redirect: boolean;
239
+ url: string;
237
240
  }>;
238
241
  oauth2Continue: better_call0.StrictEndpoint<"/oauth2/continue", {
239
242
  method: "POST";
@@ -661,7 +664,7 @@ declare const oauthProvider: <O extends OAuthOptions<Scope[]>>(options: O) => {
661
664
  };
662
665
  }, null | undefined>;
663
666
  oauth2UserInfo: better_call0.StrictEndpoint<"/oauth2/userinfo", {
664
- method: "GET";
667
+ method: ("GET" | "POST")[];
665
668
  metadata: {
666
669
  openapi: {
667
670
  description: string;
@@ -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.14";
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.14",
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.14",
68
- "better-auth": "1.6.14"
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.14",
75
- "better-auth": "^1.6.14"
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",