@better-auth/core 1.4.17 → 1.4.19

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.
Files changed (261) hide show
  1. package/.turbo/turbo-build.log +253 -176
  2. package/dist/api/index.d.mts +11 -5
  3. package/dist/api/index.mjs +2 -1
  4. package/dist/api/index.mjs.map +1 -0
  5. package/dist/async_hooks/index.d.mts +2 -1
  6. package/dist/async_hooks/index.mjs +2 -1
  7. package/dist/async_hooks/index.mjs.map +1 -0
  8. package/dist/async_hooks/pure.index.d.mts +2 -1
  9. package/dist/async_hooks/pure.index.mjs +2 -1
  10. package/dist/async_hooks/pure.index.mjs.map +1 -0
  11. package/dist/context/endpoint-context.d.mts +2 -1
  12. package/dist/context/endpoint-context.mjs +2 -1
  13. package/dist/context/endpoint-context.mjs.map +1 -0
  14. package/dist/context/global.d.mts +2 -1
  15. package/dist/context/global.mjs +3 -2
  16. package/dist/context/global.mjs.map +1 -0
  17. package/dist/context/request-state.d.mts +2 -1
  18. package/dist/context/request-state.mjs +2 -1
  19. package/dist/context/request-state.mjs.map +1 -0
  20. package/dist/context/transaction.d.mts +2 -1
  21. package/dist/context/transaction.mjs +2 -1
  22. package/dist/context/transaction.mjs.map +1 -0
  23. package/dist/db/adapter/factory.d.mts +2 -1
  24. package/dist/db/adapter/factory.mjs +9 -27
  25. package/dist/db/adapter/factory.mjs.map +1 -0
  26. package/dist/db/adapter/get-default-field-name.d.mts +2 -1
  27. package/dist/db/adapter/get-default-field-name.mjs +2 -1
  28. package/dist/db/adapter/get-default-field-name.mjs.map +1 -0
  29. package/dist/db/adapter/get-default-model-name.d.mts +2 -1
  30. package/dist/db/adapter/get-default-model-name.mjs +2 -1
  31. package/dist/db/adapter/get-default-model-name.mjs.map +1 -0
  32. package/dist/db/adapter/get-field-attributes.d.mts +2 -1
  33. package/dist/db/adapter/get-field-attributes.mjs +2 -1
  34. package/dist/db/adapter/get-field-attributes.mjs.map +1 -0
  35. package/dist/db/adapter/get-field-name.d.mts +2 -1
  36. package/dist/db/adapter/get-field-name.mjs +2 -1
  37. package/dist/db/adapter/get-field-name.mjs.map +1 -0
  38. package/dist/db/adapter/get-id-field.d.mts +2 -1
  39. package/dist/db/adapter/get-id-field.mjs +2 -1
  40. package/dist/db/adapter/get-id-field.mjs.map +1 -0
  41. package/dist/db/adapter/get-model-name.d.mts +2 -1
  42. package/dist/db/adapter/get-model-name.mjs +2 -1
  43. package/dist/db/adapter/get-model-name.mjs.map +1 -0
  44. package/dist/db/adapter/index.d.mts +5 -1
  45. package/dist/db/adapter/types.d.mts +2 -1
  46. package/dist/db/adapter/utils.d.mts +2 -1
  47. package/dist/db/adapter/utils.mjs +2 -1
  48. package/dist/db/adapter/utils.mjs.map +1 -0
  49. package/dist/db/get-tables.d.mts +2 -1
  50. package/dist/db/get-tables.mjs +8 -2
  51. package/dist/db/get-tables.mjs.map +1 -0
  52. package/dist/db/plugin.d.mts +2 -1
  53. package/dist/db/schema/account.d.mts +2 -1
  54. package/dist/db/schema/account.mjs +2 -1
  55. package/dist/db/schema/account.mjs.map +1 -0
  56. package/dist/db/schema/rate-limit.d.mts +2 -1
  57. package/dist/db/schema/rate-limit.mjs +2 -1
  58. package/dist/db/schema/rate-limit.mjs.map +1 -0
  59. package/dist/db/schema/session.d.mts +2 -1
  60. package/dist/db/schema/session.mjs +2 -1
  61. package/dist/db/schema/session.mjs.map +1 -0
  62. package/dist/db/schema/shared.d.mts +2 -1
  63. package/dist/db/schema/shared.mjs +2 -1
  64. package/dist/db/schema/shared.mjs.map +1 -0
  65. package/dist/db/schema/user.d.mts +2 -1
  66. package/dist/db/schema/user.mjs +2 -1
  67. package/dist/db/schema/user.mjs.map +1 -0
  68. package/dist/db/schema/verification.d.mts +2 -1
  69. package/dist/db/schema/verification.mjs +2 -1
  70. package/dist/db/schema/verification.mjs.map +1 -0
  71. package/dist/db/type.d.mts +2 -1
  72. package/dist/env/color-depth.d.mts +2 -1
  73. package/dist/env/color-depth.mjs +2 -1
  74. package/dist/env/color-depth.mjs.map +1 -0
  75. package/dist/env/env-impl.d.mts +3 -2
  76. package/dist/env/env-impl.mjs +3 -2
  77. package/dist/env/env-impl.mjs.map +1 -0
  78. package/dist/env/logger.d.mts +2 -1
  79. package/dist/env/logger.mjs +3 -2
  80. package/dist/env/logger.mjs.map +1 -0
  81. package/dist/error/codes.d.mts +2 -1
  82. package/dist/error/codes.mjs +2 -1
  83. package/dist/error/codes.mjs.map +1 -0
  84. package/dist/error/index.d.mts +2 -1
  85. package/dist/error/index.mjs +2 -1
  86. package/dist/error/index.mjs.map +1 -0
  87. package/dist/index.d.mts +2 -2
  88. package/dist/oauth2/client-credentials-token.d.mts +2 -1
  89. package/dist/oauth2/client-credentials-token.mjs +2 -1
  90. package/dist/oauth2/client-credentials-token.mjs.map +1 -0
  91. package/dist/oauth2/create-authorization-url.d.mts +2 -1
  92. package/dist/oauth2/create-authorization-url.mjs +2 -1
  93. package/dist/oauth2/create-authorization-url.mjs.map +1 -0
  94. package/dist/oauth2/oauth-provider.d.mts +3 -2
  95. package/dist/oauth2/refresh-access-token.d.mts +2 -1
  96. package/dist/oauth2/refresh-access-token.mjs +2 -1
  97. package/dist/oauth2/refresh-access-token.mjs.map +1 -0
  98. package/dist/oauth2/utils.d.mts +2 -1
  99. package/dist/oauth2/utils.mjs +2 -1
  100. package/dist/oauth2/utils.mjs.map +1 -0
  101. package/dist/oauth2/validate-authorization-code.d.mts +6 -2
  102. package/dist/oauth2/validate-authorization-code.mjs +7 -12
  103. package/dist/oauth2/validate-authorization-code.mjs.map +1 -0
  104. package/dist/oauth2/verify.d.mts +2 -1
  105. package/dist/oauth2/verify.mjs +2 -1
  106. package/dist/oauth2/verify.mjs.map +1 -0
  107. package/dist/social-providers/apple.d.mts +2 -1
  108. package/dist/social-providers/apple.mjs +22 -15
  109. package/dist/social-providers/apple.mjs.map +1 -0
  110. package/dist/social-providers/atlassian.d.mts +2 -1
  111. package/dist/social-providers/atlassian.mjs +2 -1
  112. package/dist/social-providers/atlassian.mjs.map +1 -0
  113. package/dist/social-providers/cognito.d.mts +2 -1
  114. package/dist/social-providers/cognito.mjs +2 -1
  115. package/dist/social-providers/cognito.mjs.map +1 -0
  116. package/dist/social-providers/discord.d.mts +2 -1
  117. package/dist/social-providers/discord.mjs +2 -1
  118. package/dist/social-providers/discord.mjs.map +1 -0
  119. package/dist/social-providers/dropbox.d.mts +2 -1
  120. package/dist/social-providers/dropbox.mjs +2 -1
  121. package/dist/social-providers/dropbox.mjs.map +1 -0
  122. package/dist/social-providers/facebook.d.mts +2 -1
  123. package/dist/social-providers/facebook.mjs +5 -4
  124. package/dist/social-providers/facebook.mjs.map +1 -0
  125. package/dist/social-providers/figma.d.mts +2 -1
  126. package/dist/social-providers/figma.mjs +2 -1
  127. package/dist/social-providers/figma.mjs.map +1 -0
  128. package/dist/social-providers/github.d.mts +3 -2
  129. package/dist/social-providers/github.mjs +22 -5
  130. package/dist/social-providers/github.mjs.map +1 -0
  131. package/dist/social-providers/gitlab.d.mts +2 -1
  132. package/dist/social-providers/gitlab.mjs +2 -1
  133. package/dist/social-providers/gitlab.mjs.map +1 -0
  134. package/dist/social-providers/google.d.mts +2 -1
  135. package/dist/social-providers/google.mjs +18 -13
  136. package/dist/social-providers/google.mjs.map +1 -0
  137. package/dist/social-providers/huggingface.d.mts +2 -1
  138. package/dist/social-providers/huggingface.mjs +2 -1
  139. package/dist/social-providers/huggingface.mjs.map +1 -0
  140. package/dist/social-providers/index.d.mts +5 -3
  141. package/dist/social-providers/index.mjs +3 -2
  142. package/dist/social-providers/index.mjs.map +1 -0
  143. package/dist/social-providers/kakao.d.mts +2 -1
  144. package/dist/social-providers/kakao.mjs +2 -1
  145. package/dist/social-providers/kakao.mjs.map +1 -0
  146. package/dist/social-providers/kick.d.mts +2 -1
  147. package/dist/social-providers/kick.mjs +2 -1
  148. package/dist/social-providers/kick.mjs.map +1 -0
  149. package/dist/social-providers/line.d.mts +2 -1
  150. package/dist/social-providers/line.mjs +2 -1
  151. package/dist/social-providers/line.mjs.map +1 -0
  152. package/dist/social-providers/linear.d.mts +2 -1
  153. package/dist/social-providers/linear.mjs +2 -1
  154. package/dist/social-providers/linear.mjs.map +1 -0
  155. package/dist/social-providers/linkedin.d.mts +2 -1
  156. package/dist/social-providers/linkedin.mjs +2 -1
  157. package/dist/social-providers/linkedin.mjs.map +1 -0
  158. package/dist/social-providers/microsoft-entra-id.d.mts +4 -1
  159. package/dist/social-providers/microsoft-entra-id.mjs +36 -2
  160. package/dist/social-providers/microsoft-entra-id.mjs.map +1 -0
  161. package/dist/social-providers/naver.d.mts +2 -1
  162. package/dist/social-providers/naver.mjs +2 -1
  163. package/dist/social-providers/naver.mjs.map +1 -0
  164. package/dist/social-providers/notion.d.mts +2 -1
  165. package/dist/social-providers/notion.mjs +2 -1
  166. package/dist/social-providers/notion.mjs.map +1 -0
  167. package/dist/social-providers/paybin.d.mts +2 -1
  168. package/dist/social-providers/paybin.mjs +2 -1
  169. package/dist/social-providers/paybin.mjs.map +1 -0
  170. package/dist/social-providers/paypal.d.mts +2 -1
  171. package/dist/social-providers/paypal.mjs +2 -1
  172. package/dist/social-providers/paypal.mjs.map +1 -0
  173. package/dist/social-providers/polar.d.mts +2 -1
  174. package/dist/social-providers/polar.mjs +2 -1
  175. package/dist/social-providers/polar.mjs.map +1 -0
  176. package/dist/social-providers/reddit.d.mts +2 -1
  177. package/dist/social-providers/reddit.mjs +2 -1
  178. package/dist/social-providers/reddit.mjs.map +1 -0
  179. package/dist/social-providers/roblox.d.mts +2 -1
  180. package/dist/social-providers/roblox.mjs +2 -1
  181. package/dist/social-providers/roblox.mjs.map +1 -0
  182. package/dist/social-providers/salesforce.d.mts +2 -1
  183. package/dist/social-providers/salesforce.mjs +2 -1
  184. package/dist/social-providers/salesforce.mjs.map +1 -0
  185. package/dist/social-providers/slack.d.mts +2 -1
  186. package/dist/social-providers/slack.mjs +2 -1
  187. package/dist/social-providers/slack.mjs.map +1 -0
  188. package/dist/social-providers/spotify.d.mts +2 -1
  189. package/dist/social-providers/spotify.mjs +2 -1
  190. package/dist/social-providers/spotify.mjs.map +1 -0
  191. package/dist/social-providers/tiktok.d.mts +2 -1
  192. package/dist/social-providers/tiktok.mjs +2 -1
  193. package/dist/social-providers/tiktok.mjs.map +1 -0
  194. package/dist/social-providers/twitch.d.mts +2 -1
  195. package/dist/social-providers/twitch.mjs +2 -1
  196. package/dist/social-providers/twitch.mjs.map +1 -0
  197. package/dist/social-providers/twitter.d.mts +3 -2
  198. package/dist/social-providers/twitter.mjs +2 -1
  199. package/dist/social-providers/twitter.mjs.map +1 -0
  200. package/dist/social-providers/vercel.d.mts +2 -1
  201. package/dist/social-providers/vercel.mjs +2 -1
  202. package/dist/social-providers/vercel.mjs.map +1 -0
  203. package/dist/social-providers/vk.d.mts +2 -1
  204. package/dist/social-providers/vk.mjs +2 -1
  205. package/dist/social-providers/vk.mjs.map +1 -0
  206. package/dist/social-providers/zoom.d.mts +3 -3
  207. package/dist/social-providers/zoom.mjs +2 -1
  208. package/dist/social-providers/zoom.mjs.map +1 -0
  209. package/dist/types/context.d.mts +7 -3
  210. package/dist/types/cookie.d.mts +2 -1
  211. package/dist/types/helper.d.mts +2 -1
  212. package/dist/types/index.d.mts +1 -1
  213. package/dist/types/init-options.d.mts +36 -33
  214. package/dist/types/plugin-client.d.mts +2 -1
  215. package/dist/types/plugin.d.mts +2 -1
  216. package/dist/utils/db.d.mts +13 -0
  217. package/dist/utils/db.mjs +17 -0
  218. package/dist/utils/db.mjs.map +1 -0
  219. package/dist/utils/deprecate.d.mts +2 -1
  220. package/dist/utils/deprecate.mjs +2 -1
  221. package/dist/utils/deprecate.mjs.map +1 -0
  222. package/dist/utils/error-codes.d.mts +2 -1
  223. package/dist/utils/error-codes.mjs +2 -1
  224. package/dist/utils/error-codes.mjs.map +1 -0
  225. package/dist/utils/id.d.mts +2 -1
  226. package/dist/utils/id.mjs +2 -1
  227. package/dist/utils/id.mjs.map +1 -0
  228. package/dist/utils/index.d.mts +2 -1
  229. package/dist/utils/index.mjs +2 -1
  230. package/dist/utils/ip.d.mts +2 -1
  231. package/dist/utils/ip.mjs +2 -1
  232. package/dist/utils/ip.mjs.map +1 -0
  233. package/dist/utils/json.d.mts +2 -1
  234. package/dist/utils/json.mjs +2 -1
  235. package/dist/utils/json.mjs.map +1 -0
  236. package/dist/utils/string.d.mts +2 -1
  237. package/dist/utils/string.mjs +2 -1
  238. package/dist/utils/string.mjs.map +1 -0
  239. package/dist/utils/url.d.mts +2 -1
  240. package/dist/utils/url.mjs +2 -1
  241. package/dist/utils/url.mjs.map +1 -0
  242. package/package.json +1 -1
  243. package/src/db/adapter/factory.ts +5 -41
  244. package/src/db/adapter/index.ts +3 -0
  245. package/src/db/get-tables.ts +5 -0
  246. package/src/env/env-impl.ts +2 -2
  247. package/src/env/logger.ts +1 -1
  248. package/src/oauth2/oauth-provider.ts +1 -1
  249. package/src/oauth2/validate-authorization-code.ts +13 -26
  250. package/src/oauth2/validate-token.test.ts +241 -0
  251. package/src/social-providers/apple.ts +37 -24
  252. package/src/social-providers/facebook.ts +3 -3
  253. package/src/social-providers/github.ts +25 -3
  254. package/src/social-providers/google.ts +18 -14
  255. package/src/social-providers/microsoft-entra-id.ts +84 -1
  256. package/src/types/context.ts +6 -3
  257. package/src/types/index.ts +6 -1
  258. package/src/types/init-options.ts +44 -36
  259. package/src/utils/db.ts +20 -0
  260. package/src/utils/index.ts +1 -0
  261. package/tsdown.config.ts +3 -0
@@ -111,30 +111,34 @@ export const apple = (options: AppleOptions) => {
111
111
  if (options.verifyIdToken) {
112
112
  return options.verifyIdToken(token, nonce);
113
113
  }
114
- const decodedHeader = decodeProtectedHeader(token);
115
- const { kid, alg: jwtAlg } = decodedHeader;
116
- if (!kid || !jwtAlg) return false;
117
- const publicKey = await getApplePublicKey(kid);
118
- const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
119
- algorithms: [jwtAlg],
120
- issuer: "https://appleid.apple.com",
121
- audience:
122
- options.audience && options.audience.length
123
- ? options.audience
124
- : options.appBundleIdentifier
125
- ? options.appBundleIdentifier
126
- : options.clientId,
127
- maxTokenAge: "1h",
128
- });
129
- ["email_verified", "is_private_email"].forEach((field) => {
130
- if (jwtClaims[field] !== undefined) {
131
- jwtClaims[field] = Boolean(jwtClaims[field]);
114
+ try {
115
+ const decodedHeader = decodeProtectedHeader(token);
116
+ const { kid, alg: jwtAlg } = decodedHeader;
117
+ if (!kid || !jwtAlg) return false;
118
+ const publicKey = await getApplePublicKey(kid);
119
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
120
+ algorithms: [jwtAlg],
121
+ issuer: "https://appleid.apple.com",
122
+ audience:
123
+ options.audience && options.audience.length
124
+ ? options.audience
125
+ : options.appBundleIdentifier
126
+ ? options.appBundleIdentifier
127
+ : options.clientId,
128
+ maxTokenAge: "1h",
129
+ });
130
+ ["email_verified", "is_private_email"].forEach((field) => {
131
+ if (jwtClaims[field] !== undefined) {
132
+ jwtClaims[field] = Boolean(jwtClaims[field]);
133
+ }
134
+ });
135
+ if (nonce && jwtClaims.nonce !== nonce) {
136
+ return false;
132
137
  }
133
- });
134
- if (nonce && jwtClaims.nonce !== nonce) {
138
+ return !!jwtClaims;
139
+ } catch {
135
140
  return false;
136
141
  }
137
- return !!jwtClaims;
138
142
  },
139
143
  refreshAccessToken: options.refreshAccessToken
140
144
  ? options.refreshAccessToken
@@ -160,9 +164,18 @@ export const apple = (options: AppleOptions) => {
160
164
  if (!profile) {
161
165
  return null;
162
166
  }
163
- const name = token.user
164
- ? `${token.user.name?.firstName} ${token.user.name?.lastName}`
165
- : profile.name || profile.email;
167
+
168
+ // TODO: " " masking will be removed when the name field is made optional
169
+ let name: string;
170
+ if (token.user?.name) {
171
+ const firstName = token.user.name.firstName || "";
172
+ const lastName = token.user.name.lastName || "";
173
+ const fullName = `${firstName} ${lastName}`.trim();
174
+ name = fullName || " ";
175
+ } else {
176
+ name = profile.name || " ";
177
+ }
178
+
166
179
  const emailVerified =
167
180
  typeof profile.email_verified === "boolean"
168
181
  ? profile.email_verified
@@ -49,7 +49,7 @@ export const facebook = (options: FacebookOptions) => {
49
49
  return await createAuthorizationURL({
50
50
  id: "facebook",
51
51
  options,
52
- authorizationEndpoint: "https://www.facebook.com/v21.0/dialog/oauth",
52
+ authorizationEndpoint: "https://www.facebook.com/v24.0/dialog/oauth",
53
53
  scopes: _scopes,
54
54
  state,
55
55
  redirectURI,
@@ -66,7 +66,7 @@ export const facebook = (options: FacebookOptions) => {
66
66
  code,
67
67
  redirectURI,
68
68
  options,
69
- tokenEndpoint: "https://graph.facebook.com/oauth/access_token",
69
+ tokenEndpoint: "https://graph.facebook.com/v24.0/oauth/access_token",
70
70
  });
71
71
  },
72
72
  async verifyIdToken(token, nonce) {
@@ -121,7 +121,7 @@ export const facebook = (options: FacebookOptions) => {
121
121
  clientSecret: options.clientSecret,
122
122
  },
123
123
  tokenEndpoint:
124
- "https://graph.facebook.com/v18.0/oauth/access_token",
124
+ "https://graph.facebook.com/v24.0/oauth/access_token",
125
125
  });
126
126
  },
127
127
  async getUserInfo(token) {
@@ -1,10 +1,12 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
+ import { logger } from "../env";
2
3
  import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
4
  import {
4
5
  createAuthorizationURL,
6
+ getOAuth2Tokens,
5
7
  refreshAccessToken,
6
- validateAuthorizationCode,
7
8
  } from "../oauth2";
9
+ import { createAuthorizationCodeRequest } from "../oauth2/validate-authorization-code";
8
10
 
9
11
  export interface GithubProfile {
10
12
  login: string;
@@ -86,13 +88,33 @@ export const github = (options: GithubOptions) => {
86
88
  });
87
89
  },
88
90
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
89
- return validateAuthorizationCode({
91
+ const { body, headers: requestHeaders } = createAuthorizationCodeRequest({
90
92
  code,
91
93
  codeVerifier,
92
94
  redirectURI,
93
95
  options,
94
- tokenEndpoint,
95
96
  });
97
+
98
+ const { data, error } = await betterFetch<
99
+ | { access_token: string; token_type: string; scope: string }
100
+ | { error: string; error_description?: string; error_uri?: string }
101
+ >(tokenEndpoint, {
102
+ method: "POST",
103
+ body: body,
104
+ headers: requestHeaders,
105
+ });
106
+
107
+ if (error) {
108
+ logger.error("GitHub OAuth token exchange failed:", error);
109
+ return null;
110
+ }
111
+
112
+ if ("error" in data) {
113
+ logger.error("GitHub OAuth token exchange failed:", data);
114
+ return null;
115
+ }
116
+
117
+ return getOAuth2Tokens(data);
96
118
  },
97
119
  refreshAccessToken: options.refreshAccessToken
98
120
  ? options.refreshAccessToken
@@ -82,7 +82,7 @@ export const google = (options: GoogleOptions) => {
82
82
  const url = await createAuthorizationURL({
83
83
  id: "google",
84
84
  options,
85
- authorizationEndpoint: "https://accounts.google.com/o/oauth2/auth",
85
+ authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
86
86
  scopes: _scopes,
87
87
  state,
88
88
  codeVerifier,
@@ -117,7 +117,7 @@ export const google = (options: GoogleOptions) => {
117
117
  clientKey: options.clientKey,
118
118
  clientSecret: options.clientSecret,
119
119
  },
120
- tokenEndpoint: "https://www.googleapis.com/oauth2/v4/token",
120
+ tokenEndpoint: "https://oauth2.googleapis.com/token",
121
121
  });
122
122
  },
123
123
  async verifyIdToken(token, nonce) {
@@ -131,22 +131,26 @@ export const google = (options: GoogleOptions) => {
131
131
  // Verify JWT integrity
132
132
  // See https://developers.google.com/identity/sign-in/web/backend-auth#verify-the-integrity-of-the-id-token
133
133
 
134
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
135
- if (!kid || !jwtAlg) return false;
134
+ try {
135
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
136
+ if (!kid || !jwtAlg) return false;
136
137
 
137
- const publicKey = await getGooglePublicKey(kid);
138
- const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
139
- algorithms: [jwtAlg],
140
- issuer: ["https://accounts.google.com", "accounts.google.com"],
141
- audience: options.clientId,
142
- maxTokenAge: "1h",
143
- });
138
+ const publicKey = await getGooglePublicKey(kid);
139
+ const { payload: jwtClaims } = await jwtVerify(token, publicKey, {
140
+ algorithms: [jwtAlg],
141
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
142
+ audience: options.clientId,
143
+ maxTokenAge: "1h",
144
+ });
145
+
146
+ if (nonce && jwtClaims.nonce !== nonce) {
147
+ return false;
148
+ }
144
149
 
145
- if (nonce && jwtClaims.nonce !== nonce) {
150
+ return true;
151
+ } catch {
146
152
  return false;
147
153
  }
148
-
149
- return true;
150
154
  },
151
155
  async getUserInfo(token) {
152
156
  if (options.getUserInfo) {
@@ -1,6 +1,7 @@
1
1
  import { base64 } from "@better-auth/utils/base64";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
- import { decodeJwt } from "jose";
3
+ import { APIError } from "better-call";
4
+ import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
4
5
  import { logger } from "../env";
5
6
  import type { OAuthProvider, ProviderOptions } from "../oauth2";
6
7
  import {
@@ -174,6 +175,56 @@ export const microsoft = (options: MicrosoftOptions) => {
174
175
  tokenEndpoint,
175
176
  });
176
177
  },
178
+ async verifyIdToken(token, nonce) {
179
+ if (options.disableIdTokenSignIn) {
180
+ return false;
181
+ }
182
+ if (options.verifyIdToken) {
183
+ return options.verifyIdToken(token, nonce);
184
+ }
185
+
186
+ try {
187
+ const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
188
+ if (!kid || !jwtAlg) return false;
189
+
190
+ const publicKey = await getMicrosoftPublicKey(kid, tenant, authority);
191
+ const verifyOptions: {
192
+ algorithms: [string];
193
+ audience: string;
194
+ maxTokenAge: string;
195
+ issuer?: string;
196
+ } = {
197
+ algorithms: [jwtAlg],
198
+ audience: options.clientId,
199
+ maxTokenAge: "1h",
200
+ };
201
+ /**
202
+ * Issuer varies per user's tenant for multi-tenant endpoints, so only validate for specific tenants.
203
+ * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
204
+ */
205
+ if (
206
+ tenant !== "common" &&
207
+ tenant !== "organizations" &&
208
+ tenant !== "consumers"
209
+ ) {
210
+ verifyOptions.issuer = `${authority}/${tenant}/v2.0`;
211
+ }
212
+ const { payload: jwtClaims } = await jwtVerify(
213
+ token,
214
+ publicKey,
215
+ verifyOptions,
216
+ );
217
+
218
+ if (nonce && jwtClaims.nonce !== nonce) {
219
+ return false;
220
+ }
221
+
222
+ return true;
223
+ } catch (error) {
224
+ logger.error("Failed to verify ID token:", error);
225
+ return false;
226
+ }
227
+ },
177
228
  async getUserInfo(token) {
178
229
  if (options.getUserInfo) {
179
230
  return options.getUserInfo(token);
@@ -257,3 +308,35 @@ export const microsoft = (options: MicrosoftOptions) => {
257
308
  options,
258
309
  } satisfies OAuthProvider;
259
310
  };
311
+
312
+ export const getMicrosoftPublicKey = async (
313
+ kid: string,
314
+ tenant: string,
315
+ authority: string,
316
+ ) => {
317
+ const { data } = await betterFetch<{
318
+ keys: Array<{
319
+ kid: string;
320
+ alg: string;
321
+ kty: string;
322
+ use: string;
323
+ n: string;
324
+ e: string;
325
+ x5c?: string[];
326
+ x5t?: string;
327
+ }>;
328
+ }>(`${authority}/${tenant}/discovery/v2.0/keys`);
329
+
330
+ if (!data?.keys) {
331
+ throw new APIError("BAD_REQUEST", {
332
+ message: "Keys not found",
333
+ });
334
+ }
335
+
336
+ const jwk = data.keys.find((key) => key.kid === kid);
337
+ if (!jwk) {
338
+ throw new Error(`JWK with kid ${kid} not found`);
339
+ }
340
+
341
+ return await importJWK(jwk, jwk.alg);
342
+ };
@@ -12,6 +12,7 @@ import type { DBAdapter, Where } from "../db/adapter";
12
12
  import type { createLogger } from "../env";
13
13
  import type { OAuthProvider } from "../oauth2";
14
14
  import type { BetterAuthCookie, BetterAuthCookies } from "./cookie";
15
+ import type { Awaitable } from "./helper";
15
16
  import type {
16
17
  BetterAuthOptions,
17
18
  BetterAuthRateLimitOptions,
@@ -177,6 +178,8 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
177
178
  PluginContext &
178
179
  InfoContext & {
179
180
  options: Options;
181
+ appName: string;
182
+ baseURL: string;
180
183
  trustedOrigins: string[];
181
184
  /**
182
185
  * Verifies whether url is a trusted origin according to the "trustedOrigins" configuration
@@ -299,7 +302,7 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
299
302
  * This is inferred from the `options.advanced?.backgroundTasks?.handler` option.
300
303
  * Defaults to a no-op that just runs the promise.
301
304
  */
302
- runInBackground: (promise: Promise<void>) => void;
305
+ runInBackground: (promise: Promise<unknown>) => void;
303
306
  /**
304
307
  * Runs a task in the background if `runInBackground` is configured,
305
308
  * otherwise awaits the task directly.
@@ -309,6 +312,6 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
309
312
  * mitigation), but still ensure the operation completes.
310
313
  */
311
314
  runInBackgroundOrAwait: (
312
- promise: Promise<unknown> | Promise<void> | void | unknown,
313
- ) => Promise<unknown>;
315
+ promise: Promise<unknown> | void,
316
+ ) => Awaitable<unknown>;
314
317
  };
@@ -6,12 +6,17 @@ export type {
6
6
  InternalAdapter,
7
7
  PluginContext,
8
8
  } from "./context";
9
- export type { BetterAuthCookie, BetterAuthCookies } from "./cookie";
9
+ export type {
10
+ BetterAuthCookie,
11
+ BetterAuthCookies,
12
+ } from "./cookie";
10
13
  export type * from "./helper";
11
14
  export type {
12
15
  BetterAuthAdvancedOptions,
13
16
  BetterAuthOptions,
14
17
  BetterAuthRateLimitOptions,
18
+ BetterAuthRateLimitRule,
19
+ BetterAuthRateLimitStorage,
15
20
  GenerateIdFn,
16
21
  } from "./init-options";
17
22
  export type { BetterAuthPlugin, HookEndpointContext } from "./plugin";
@@ -37,25 +37,37 @@ export type GenerateIdFn = (options: {
37
37
  size?: number | undefined;
38
38
  }) => string | false;
39
39
 
40
- export type BetterAuthRateLimitOptions = {
41
- /**
42
- * By default, rate limiting is only
43
- * enabled on production.
44
- */
45
- enabled?: boolean | undefined;
40
+ export interface BetterAuthRateLimitStorage {
41
+ get: (key: string) => Promise<RateLimit | null | undefined>;
42
+ set: (
43
+ key: string,
44
+ value: RateLimit,
45
+ update?: boolean | undefined,
46
+ ) => Promise<void>;
47
+ }
48
+
49
+ export type BetterAuthRateLimitRule = {
46
50
  /**
47
51
  * Default window to use for rate limiting. The value
48
52
  * should be in seconds.
49
53
  *
50
54
  * @default 10 seconds
51
55
  */
52
- window?: number | undefined;
56
+ window: number;
53
57
  /**
54
58
  * The default maximum number of requests allowed within the window.
55
59
  *
56
60
  * @default 100 requests
57
61
  */
58
- max?: number | undefined;
62
+ max: number;
63
+ };
64
+
65
+ export type BetterAuthRateLimitOptions = Optional<BetterAuthRateLimitRule> & {
66
+ /**
67
+ * By default, rate limiting is only
68
+ * enabled on production.
69
+ */
70
+ enabled?: boolean | undefined;
59
71
  /**
60
72
  * Custom rate limit rules to apply to
61
73
  * specific paths.
@@ -63,27 +75,12 @@ export type BetterAuthRateLimitOptions = {
63
75
  customRules?:
64
76
  | {
65
77
  [key: string]:
66
- | {
67
- /**
68
- * The window to use for the custom rule.
69
- */
70
- window: number;
71
- /**
72
- * The maximum number of requests allowed within the window.
73
- */
74
- max: number;
75
- }
78
+ | BetterAuthRateLimitRule
76
79
  | false
77
- | ((request: Request) =>
78
- | { window: number; max: number }
79
- | false
80
- | Promise<
81
- | {
82
- window: number;
83
- max: number;
84
- }
85
- | false
86
- >);
80
+ | ((
81
+ request: Request,
82
+ currentRule: BetterAuthRateLimitRule,
83
+ ) => Awaitable<false | BetterAuthRateLimitRule>);
87
84
  }
88
85
  | undefined;
89
86
  /**
@@ -113,10 +110,7 @@ export type BetterAuthRateLimitOptions = {
113
110
  * NOTE: If custom storage is used storage
114
111
  * is ignored
115
112
  */
116
- customStorage?: {
117
- get: (key: string) => Promise<RateLimit | undefined>;
118
- set: (key: string, value: RateLimit) => Promise<void>;
119
- };
113
+ customStorage?: BetterAuthRateLimitStorage;
120
114
  };
121
115
 
122
116
  export type BetterAuthAdvancedOptions = {
@@ -328,7 +322,7 @@ export type BetterAuthAdvancedOptions = {
328
322
  * }
329
323
  */
330
324
  backgroundTasks?: {
331
- handler: (promise: Promise<void>) => void;
325
+ handler: (promise: Promise<unknown>) => void;
332
326
  };
333
327
  /**
334
328
  * Skip trailing slash validation in route matching
@@ -491,10 +485,13 @@ export type BetterAuthOptions = {
491
485
  request?: Request,
492
486
  ) => Promise<void>;
493
487
  /**
494
- * Send a verification email automatically
495
- * after sign up
488
+ * Send a verification email automatically after sign up.
496
489
  *
497
- * @default false
490
+ * - `true`: Always send verification email on sign up
491
+ * - `false`: Never send verification email on sign up
492
+ * - `undefined`: Follows `requireEmailVerification` behavior
493
+ *
494
+ * @default undefined
498
495
  */
499
496
  sendOnSignUp?: boolean;
500
497
  /**
@@ -947,6 +944,17 @@ export type BetterAuthOptions = {
947
944
  * @default true
948
945
  */
949
946
  enabled?: boolean;
947
+ /**
948
+ * Disable implicit account linking on sign-in.
949
+ *
950
+ * When enabled, accounts will not be automatically linked
951
+ * during OAuth sign-in, even if the email is verified or
952
+ * the provider is trusted. Users must explicitly link
953
+ * accounts using `linkSocial()` while authenticated.
954
+ *
955
+ * @default false
956
+ */
957
+ disableImplicitLinking?: boolean;
950
958
  /**
951
959
  * List of trusted providers
952
960
  */
@@ -0,0 +1,20 @@
1
+ import type { DBFieldAttribute } from "../db";
2
+
3
+ /**
4
+ * Filters output data by removing fields with the `returned: false` attribute.
5
+ * This ensures sensitive fields are not exposed in API responses.
6
+ */
7
+ export function filterOutputFields<T extends Record<string, unknown> | null>(
8
+ data: T,
9
+ additionalFields: Record<string, DBFieldAttribute> | undefined,
10
+ ): T {
11
+ if (!data || !additionalFields) {
12
+ return data;
13
+ }
14
+ const returnFiltered = Object.entries(additionalFields)
15
+ .filter(([, { returned }]) => returned === false)
16
+ .map(([key]) => key);
17
+ return Object.entries(structuredClone(data))
18
+ .filter(([key]) => !returnFiltered.includes(key))
19
+ .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {} as T);
20
+ }
@@ -1,3 +1,4 @@
1
+ export { filterOutputFields } from "./db";
1
2
  export { deprecate } from "./deprecate";
2
3
  export { defineErrorCodes } from "./error-codes";
3
4
  export { generateId } from "./id";
package/tsdown.config.ts CHANGED
@@ -25,7 +25,10 @@ export default defineConfig({
25
25
  external: ["@better-auth/core/async_hooks"],
26
26
  env: {
27
27
  BETTER_AUTH_VERSION: packageJson.version,
28
+ BETTER_AUTH_TELEMETRY_ENDPOINT:
29
+ process.env.BETTER_AUTH_TELEMETRY_ENDPOINT ?? "",
28
30
  },
31
+ sourcemap: true,
29
32
  unbundle: true,
30
33
  clean: true,
31
34
  });