@better-auth/core 1.7.0-beta.3 → 1.7.0-beta.5

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 (188) hide show
  1. package/dist/api/index.d.mts +3 -3
  2. package/dist/context/global.mjs +1 -1
  3. package/dist/db/adapter/factory.mjs +62 -0
  4. package/dist/db/adapter/index.d.mts +35 -1
  5. package/dist/db/adapter/types.d.mts +1 -1
  6. package/dist/db/get-tables.mjs +3 -3
  7. package/dist/db/schema/account.d.mts +1 -1
  8. package/dist/db/schema/account.mjs +1 -1
  9. package/dist/db/type.d.mts +12 -0
  10. package/dist/env/env-impl.mjs +1 -1
  11. package/dist/error/codes.d.mts +6 -0
  12. package/dist/error/codes.mjs +6 -0
  13. package/dist/index.d.mts +2 -2
  14. package/dist/instrumentation/tracer.mjs +1 -1
  15. package/dist/oauth2/authorization-params.d.mts +12 -0
  16. package/dist/oauth2/authorization-params.mjs +12 -0
  17. package/dist/oauth2/basic-credentials.d.mts +30 -0
  18. package/dist/oauth2/basic-credentials.mjs +64 -0
  19. package/dist/oauth2/client-assertion.d.mts +38 -22
  20. package/dist/oauth2/client-assertion.mjs +63 -28
  21. package/dist/oauth2/client-credentials-token.d.mts +19 -40
  22. package/dist/oauth2/client-credentials-token.mjs +18 -29
  23. package/dist/oauth2/create-authorization-url.d.mts +13 -2
  24. package/dist/oauth2/create-authorization-url.mjs +28 -7
  25. package/dist/oauth2/index.d.mts +13 -8
  26. package/dist/oauth2/index.mjs +11 -7
  27. package/dist/oauth2/oauth-provider.d.mts +149 -11
  28. package/dist/oauth2/refresh-access-token.d.mts +20 -40
  29. package/dist/oauth2/refresh-access-token.mjs +20 -33
  30. package/dist/oauth2/scopes.d.mts +76 -0
  31. package/dist/oauth2/scopes.mjs +96 -0
  32. package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
  33. package/dist/oauth2/token-endpoint-auth.mjs +89 -0
  34. package/dist/oauth2/utils.d.mts +9 -1
  35. package/dist/oauth2/utils.mjs +14 -2
  36. package/dist/oauth2/validate-authorization-code.d.mts +17 -52
  37. package/dist/oauth2/validate-authorization-code.mjs +17 -30
  38. package/dist/oauth2/verify-id-token.d.mts +26 -0
  39. package/dist/oauth2/verify-id-token.mjs +62 -0
  40. package/dist/oauth2/verify.d.mts +14 -0
  41. package/dist/oauth2/verify.mjs +38 -12
  42. package/dist/social-providers/apple.d.mts +18 -20
  43. package/dist/social-providers/apple.mjs +15 -28
  44. package/dist/social-providers/atlassian.d.mts +8 -2
  45. package/dist/social-providers/atlassian.mjs +9 -6
  46. package/dist/social-providers/cognito.d.mts +29 -3
  47. package/dist/social-providers/cognito.mjs +30 -34
  48. package/dist/social-providers/discord.d.mts +8 -2
  49. package/dist/social-providers/discord.mjs +20 -6
  50. package/dist/social-providers/dropbox.d.mts +8 -2
  51. package/dist/social-providers/dropbox.mjs +10 -9
  52. package/dist/social-providers/facebook.d.mts +24 -3
  53. package/dist/social-providers/facebook.mjs +51 -24
  54. package/dist/social-providers/figma.d.mts +8 -2
  55. package/dist/social-providers/figma.mjs +8 -7
  56. package/dist/social-providers/github.d.mts +8 -2
  57. package/dist/social-providers/github.mjs +9 -8
  58. package/dist/social-providers/gitlab.d.mts +8 -2
  59. package/dist/social-providers/gitlab.mjs +8 -7
  60. package/dist/social-providers/google.d.mts +32 -4
  61. package/dist/social-providers/google.mjs +26 -29
  62. package/dist/social-providers/huggingface.d.mts +8 -2
  63. package/dist/social-providers/huggingface.mjs +11 -10
  64. package/dist/social-providers/index.d.mts +322 -75
  65. package/dist/social-providers/kakao.d.mts +8 -2
  66. package/dist/social-providers/kakao.mjs +11 -10
  67. package/dist/social-providers/kick.d.mts +8 -2
  68. package/dist/social-providers/kick.mjs +7 -6
  69. package/dist/social-providers/line.d.mts +11 -3
  70. package/dist/social-providers/line.mjs +14 -15
  71. package/dist/social-providers/linear.d.mts +8 -2
  72. package/dist/social-providers/linear.mjs +7 -6
  73. package/dist/social-providers/linkedin.d.mts +8 -2
  74. package/dist/social-providers/linkedin.mjs +12 -11
  75. package/dist/social-providers/microsoft-entra-id.d.mts +33 -7
  76. package/dist/social-providers/microsoft-entra-id.mjs +28 -38
  77. package/dist/social-providers/naver.d.mts +8 -2
  78. package/dist/social-providers/naver.mjs +7 -6
  79. package/dist/social-providers/notion.d.mts +8 -2
  80. package/dist/social-providers/notion.mjs +9 -6
  81. package/dist/social-providers/paybin.d.mts +8 -2
  82. package/dist/social-providers/paybin.mjs +12 -11
  83. package/dist/social-providers/paypal.d.mts +8 -3
  84. package/dist/social-providers/paypal.mjs +10 -14
  85. package/dist/social-providers/polar.d.mts +8 -2
  86. package/dist/social-providers/polar.mjs +11 -10
  87. package/dist/social-providers/railway.d.mts +8 -2
  88. package/dist/social-providers/railway.mjs +11 -10
  89. package/dist/social-providers/reddit.d.mts +8 -2
  90. package/dist/social-providers/reddit.mjs +11 -9
  91. package/dist/social-providers/roblox.d.mts +8 -2
  92. package/dist/social-providers/roblox.mjs +15 -5
  93. package/dist/social-providers/salesforce.d.mts +8 -2
  94. package/dist/social-providers/salesforce.mjs +11 -10
  95. package/dist/social-providers/slack.d.mts +8 -2
  96. package/dist/social-providers/slack.mjs +18 -15
  97. package/dist/social-providers/spotify.d.mts +8 -2
  98. package/dist/social-providers/spotify.mjs +7 -6
  99. package/dist/social-providers/tiktok.d.mts +8 -2
  100. package/dist/social-providers/tiktok.mjs +21 -5
  101. package/dist/social-providers/twitch.d.mts +8 -2
  102. package/dist/social-providers/twitch.mjs +7 -6
  103. package/dist/social-providers/twitter.d.mts +7 -2
  104. package/dist/social-providers/twitter.mjs +11 -10
  105. package/dist/social-providers/vercel.d.mts +8 -2
  106. package/dist/social-providers/vercel.mjs +7 -9
  107. package/dist/social-providers/vk.d.mts +8 -2
  108. package/dist/social-providers/vk.mjs +7 -6
  109. package/dist/social-providers/wechat.d.mts +8 -2
  110. package/dist/social-providers/wechat.mjs +16 -6
  111. package/dist/social-providers/zoom.d.mts +10 -3
  112. package/dist/social-providers/zoom.mjs +14 -15
  113. package/dist/types/context.d.mts +33 -11
  114. package/dist/types/index.d.mts +1 -1
  115. package/dist/types/init-options.d.mts +121 -6
  116. package/dist/utils/ip.d.mts +5 -4
  117. package/dist/utils/ip.mjs +3 -3
  118. package/dist/utils/redirect-uri.d.mts +20 -0
  119. package/dist/utils/redirect-uri.mjs +48 -0
  120. package/dist/utils/string.d.mts +5 -1
  121. package/dist/utils/string.mjs +20 -1
  122. package/dist/utils/url.d.mts +18 -1
  123. package/dist/utils/url.mjs +30 -1
  124. package/package.json +13 -12
  125. package/src/db/adapter/factory.ts +126 -0
  126. package/src/db/adapter/index.ts +32 -0
  127. package/src/db/adapter/types.ts +1 -0
  128. package/src/db/get-tables.ts +8 -3
  129. package/src/db/schema/account.ts +14 -2
  130. package/src/db/type.ts +12 -0
  131. package/src/env/env-impl.ts +1 -2
  132. package/src/error/codes.ts +6 -0
  133. package/src/oauth2/authorization-params.ts +28 -0
  134. package/src/oauth2/basic-credentials.ts +87 -0
  135. package/src/oauth2/client-assertion.ts +131 -58
  136. package/src/oauth2/client-credentials-token.ts +48 -72
  137. package/src/oauth2/create-authorization-url.ts +30 -8
  138. package/src/oauth2/index.ts +42 -10
  139. package/src/oauth2/oauth-provider.ts +161 -12
  140. package/src/oauth2/refresh-access-token.ts +52 -78
  141. package/src/oauth2/scopes.ts +118 -0
  142. package/src/oauth2/token-endpoint-auth.ts +221 -0
  143. package/src/oauth2/utils.ts +21 -5
  144. package/src/oauth2/validate-authorization-code.ts +55 -85
  145. package/src/oauth2/verify-id-token.ts +111 -0
  146. package/src/oauth2/verify.ts +82 -15
  147. package/src/social-providers/apple.ts +32 -45
  148. package/src/social-providers/atlassian.ts +20 -9
  149. package/src/social-providers/cognito.ts +51 -48
  150. package/src/social-providers/discord.ts +37 -22
  151. package/src/social-providers/dropbox.ts +20 -12
  152. package/src/social-providers/facebook.ts +108 -57
  153. package/src/social-providers/figma.ts +21 -10
  154. package/src/social-providers/github.ts +16 -10
  155. package/src/social-providers/gitlab.ts +16 -8
  156. package/src/social-providers/google.ts +67 -46
  157. package/src/social-providers/huggingface.ts +20 -9
  158. package/src/social-providers/kakao.ts +18 -9
  159. package/src/social-providers/kick.ts +20 -8
  160. package/src/social-providers/line.ts +39 -37
  161. package/src/social-providers/linear.ts +20 -7
  162. package/src/social-providers/linkedin.ts +16 -10
  163. package/src/social-providers/microsoft-entra-id.ts +66 -64
  164. package/src/social-providers/naver.ts +14 -7
  165. package/src/social-providers/notion.ts +20 -7
  166. package/src/social-providers/paybin.ts +16 -11
  167. package/src/social-providers/paypal.ts +12 -25
  168. package/src/social-providers/polar.ts +20 -9
  169. package/src/social-providers/railway.ts +20 -9
  170. package/src/social-providers/reddit.ts +22 -10
  171. package/src/social-providers/roblox.ts +31 -15
  172. package/src/social-providers/salesforce.ts +21 -10
  173. package/src/social-providers/slack.ts +31 -16
  174. package/src/social-providers/spotify.ts +20 -7
  175. package/src/social-providers/tiktok.ts +32 -13
  176. package/src/social-providers/twitch.ts +14 -9
  177. package/src/social-providers/twitter.ts +18 -8
  178. package/src/social-providers/vercel.ts +24 -11
  179. package/src/social-providers/vk.ts +20 -7
  180. package/src/social-providers/wechat.ts +28 -8
  181. package/src/social-providers/zoom.ts +28 -19
  182. package/src/types/context.ts +33 -12
  183. package/src/types/index.ts +7 -0
  184. package/src/types/init-options.ts +148 -5
  185. package/src/utils/ip.ts +12 -13
  186. package/src/utils/redirect-uri.ts +54 -0
  187. package/src/utils/string.ts +37 -0
  188. package/src/utils/url.ts +28 -0
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -101,24 +102,32 @@ export interface KakaoOptions extends ProviderOptions<KakaoProfile> {
101
102
  clientId: string;
102
103
  }
103
104
 
105
+ const KAKAO_DEFAULT_SCOPES = [
106
+ "account_email",
107
+ "profile_image",
108
+ "profile_nickname",
109
+ ];
110
+
104
111
  export const kakao = (options: KakaoOptions) => {
105
112
  const tokenEndpoint = "https://kauth.kakao.com/oauth/token";
106
113
  return {
107
114
  id: "kakao",
108
115
  name: "Kakao",
109
- createAuthorizationURL({ state, scopes, redirectURI }) {
110
- const _scopes = options.disableDefaultScope
111
- ? []
112
- : ["account_email", "profile_image", "profile_nickname"];
113
- if (options.scope) _scopes.push(...options.scope);
114
- if (scopes) _scopes.push(...scopes);
116
+ callbackPath: "/callback/kakao",
117
+ createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
118
+ const requestedScopes = resolveRequestedScopes(
119
+ options,
120
+ KAKAO_DEFAULT_SCOPES,
121
+ scopes,
122
+ );
115
123
  return createAuthorizationURL({
116
124
  id: "kakao",
117
125
  options,
118
126
  authorizationEndpoint: "https://kauth.kakao.com/oauth/authorize",
119
- scopes: _scopes,
127
+ scopes: requestedScopes,
120
128
  state,
121
129
  redirectURI,
130
+ additionalParams,
122
131
  });
123
132
  },
124
133
  validateAuthorizationCode: async ({ code, redirectURI }) => {
@@ -175,5 +184,5 @@ export const kakao = (options: KakaoOptions) => {
175
184
  };
176
185
  },
177
186
  options,
178
- } satisfies OAuthProvider<KakaoProfile>;
187
+ } satisfies UpstreamProvider<KakaoProfile>;
179
188
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -29,23 +30,34 @@ export interface KickOptions extends ProviderOptions<KickProfile> {
29
30
  clientId: string;
30
31
  }
31
32
 
33
+ const KICK_DEFAULT_SCOPES = ["user:read"];
34
+
32
35
  export const kick = (options: KickOptions) => {
33
36
  return {
34
37
  id: "kick",
35
38
  name: "Kick",
36
- createAuthorizationURL({ state, scopes, redirectURI, codeVerifier }) {
37
- const _scopes = options.disableDefaultScope ? [] : ["user:read"];
38
- if (options.scope) _scopes.push(...options.scope);
39
- if (scopes) _scopes.push(...scopes);
40
-
39
+ callbackPath: "/callback/kick",
40
+ createAuthorizationURL({
41
+ state,
42
+ scopes,
43
+ redirectURI,
44
+ codeVerifier,
45
+ additionalParams,
46
+ }) {
47
+ const requestedScopes = resolveRequestedScopes(
48
+ options,
49
+ KICK_DEFAULT_SCOPES,
50
+ scopes,
51
+ );
41
52
  return createAuthorizationURL({
42
53
  id: "kick",
43
54
  redirectURI,
44
55
  options,
45
56
  authorizationEndpoint: "https://id.kick.com/oauth/authorize",
46
- scopes: _scopes,
57
+ scopes: requestedScopes,
47
58
  codeVerifier,
48
59
  state,
60
+ additionalParams,
49
61
  });
50
62
  },
51
63
  async validateAuthorizationCode({ code, redirectURI, codeVerifier }) {
@@ -105,5 +117,5 @@ export const kick = (options: KickOptions) => {
105
117
  };
106
118
  },
107
119
  options,
108
- } satisfies OAuthProvider<KickProfile>;
120
+ } satisfies UpstreamProvider<KickProfile>;
109
121
  };
@@ -1,9 +1,10 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
2
  import { decodeJwt } from "jose";
3
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
3
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
4
4
  import {
5
5
  createAuthorizationURL,
6
6
  refreshAccessToken,
7
+ resolveRequestedScopes,
7
8
  validateAuthorizationCode,
8
9
  } from "../oauth2";
9
10
 
@@ -32,6 +33,8 @@ export interface LineOptions
32
33
  clientId: string;
33
34
  }
34
35
 
36
+ const LINE_DEFAULT_SCOPES = ["openid", "profile", "email"];
37
+
35
38
  /**
36
39
  * LINE Login v2.1
37
40
  * - Authorization endpoint: https://access.line.me/oauth2/v2.1/authorize
@@ -50,27 +53,30 @@ export const line = (options: LineOptions) => {
50
53
  return {
51
54
  id: "line",
52
55
  name: "LINE",
53
- async createAuthorizationURL({
56
+ callbackPath: "/callback/line",
57
+ createAuthorizationURL({
54
58
  state,
55
59
  scopes,
56
60
  codeVerifier,
57
61
  redirectURI,
58
62
  loginHint,
63
+ additionalParams,
59
64
  }) {
60
- const _scopes = options.disableDefaultScope
61
- ? []
62
- : ["openid", "profile", "email"];
63
- if (options.scope) _scopes.push(...options.scope);
64
- if (scopes) _scopes.push(...scopes);
65
- return await createAuthorizationURL({
65
+ const requestedScopes = resolveRequestedScopes(
66
+ options,
67
+ LINE_DEFAULT_SCOPES,
68
+ scopes,
69
+ );
70
+ return createAuthorizationURL({
66
71
  id: "line",
67
72
  options,
68
73
  authorizationEndpoint,
69
- scopes: _scopes,
74
+ scopes: requestedScopes,
70
75
  state,
71
76
  codeVerifier,
72
77
  redirectURI,
73
78
  loginHint,
79
+ additionalParams,
74
80
  });
75
81
  },
76
82
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
@@ -94,34 +100,30 @@ export const line = (options: LineOptions) => {
94
100
  tokenEndpoint,
95
101
  });
96
102
  },
97
- async verifyIdToken(token, nonce) {
98
- if (options.disableIdTokenSignIn) {
99
- return false;
100
- }
101
- if (options.verifyIdToken) {
102
- return options.verifyIdToken(token, nonce);
103
- }
104
- const body = new URLSearchParams();
105
- body.set("id_token", token);
106
- body.set("client_id", options.clientId);
107
- if (nonce) body.set("nonce", nonce);
108
- const { data, error } = await betterFetch<LineIdTokenPayload>(
109
- verifyIdTokenEndpoint,
110
- {
111
- method: "POST",
112
- headers: {
113
- "content-type": "application/x-www-form-urlencoded",
103
+ idToken: {
104
+ verify: async (token, nonce) => {
105
+ const body = new URLSearchParams();
106
+ body.set("id_token", token);
107
+ body.set("client_id", options.clientId);
108
+ if (nonce) body.set("nonce", nonce);
109
+ const { data, error } = await betterFetch<LineIdTokenPayload>(
110
+ verifyIdTokenEndpoint,
111
+ {
112
+ method: "POST",
113
+ headers: {
114
+ "content-type": "application/x-www-form-urlencoded",
115
+ },
116
+ body,
114
117
  },
115
- body,
116
- },
117
- );
118
- if (error || !data) {
119
- return false;
120
- }
121
- // aud must match clientId; nonce (if provided) must also match nonce
122
- if (data.aud !== options.clientId) return false;
123
- if (data.nonce && data.nonce !== nonce) return false;
124
- return true;
118
+ );
119
+ if (error || !data) {
120
+ return false;
121
+ }
122
+ // aud must match clientId; nonce (if provided) must also match nonce
123
+ if (data.aud !== options.clientId) return false;
124
+ if (data.nonce && data.nonce !== nonce) return false;
125
+ return true;
126
+ },
125
127
  },
126
128
  async getUserInfo(token) {
127
129
  if (options.getUserInfo) {
@@ -165,5 +167,5 @@ export const line = (options: LineOptions) => {
165
167
  };
166
168
  },
167
169
  options,
168
- } satisfies OAuthProvider<LineUserInfo | LineIdTokenPayload, LineOptions>;
170
+ } satisfies UpstreamProvider<LineUserInfo | LineIdTokenPayload, LineOptions>;
169
171
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -26,23 +27,35 @@ export interface LinearOptions extends ProviderOptions<LinearUser> {
26
27
  clientId: string;
27
28
  }
28
29
 
30
+ const LINEAR_DEFAULT_SCOPES = ["read"];
31
+
29
32
  export const linear = (options: LinearOptions) => {
30
33
  const tokenEndpoint = "https://api.linear.app/oauth/token";
31
34
  return {
32
35
  id: "linear",
33
36
  name: "Linear",
34
- createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
35
- const _scopes = options.disableDefaultScope ? [] : ["read"];
36
- if (options.scope) _scopes.push(...options.scope);
37
- if (scopes) _scopes.push(...scopes);
37
+ callbackPath: "/callback/linear",
38
+ createAuthorizationURL({
39
+ state,
40
+ scopes,
41
+ loginHint,
42
+ redirectURI,
43
+ additionalParams,
44
+ }) {
45
+ const requestedScopes = resolveRequestedScopes(
46
+ options,
47
+ LINEAR_DEFAULT_SCOPES,
48
+ scopes,
49
+ );
38
50
  return createAuthorizationURL({
39
51
  id: "linear",
40
52
  options,
41
53
  authorizationEndpoint: "https://linear.app/oauth/authorize",
42
- scopes: _scopes,
54
+ scopes: requestedScopes,
43
55
  state,
44
56
  redirectURI,
45
57
  loginHint,
58
+ additionalParams,
46
59
  });
47
60
  },
48
61
  validateAuthorizationCode: async ({ code, redirectURI }) => {
@@ -117,5 +130,5 @@ export const linear = (options: LinearOptions) => {
117
130
  };
118
131
  },
119
132
  options,
120
- } satisfies OAuthProvider<LinearUser>;
133
+ } satisfies UpstreamProvider<LinearUser>;
121
134
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -24,6 +25,8 @@ export interface LinkedInOptions extends ProviderOptions<LinkedInProfile> {
24
25
  clientId: string;
25
26
  }
26
27
 
28
+ const LINKEDIN_DEFAULT_SCOPES = ["profile", "email", "openid"];
29
+
27
30
  export const linkedin = (options: LinkedInOptions) => {
28
31
  const authorizationEndpoint =
29
32
  "https://www.linkedin.com/oauth/v2/authorization";
@@ -32,25 +35,28 @@ export const linkedin = (options: LinkedInOptions) => {
32
35
  return {
33
36
  id: "linkedin",
34
37
  name: "Linkedin",
35
- createAuthorizationURL: async ({
38
+ callbackPath: "/callback/linkedin",
39
+ createAuthorizationURL: ({
36
40
  state,
37
41
  scopes,
38
42
  redirectURI,
39
43
  loginHint,
44
+ additionalParams,
40
45
  }) => {
41
- const _scopes = options.disableDefaultScope
42
- ? []
43
- : ["profile", "email", "openid"];
44
- if (options.scope) _scopes.push(...options.scope);
45
- if (scopes) _scopes.push(...scopes);
46
- return await createAuthorizationURL({
46
+ const requestedScopes = resolveRequestedScopes(
47
+ options,
48
+ LINKEDIN_DEFAULT_SCOPES,
49
+ scopes,
50
+ );
51
+ return createAuthorizationURL({
47
52
  id: "linkedin",
48
53
  options,
49
54
  authorizationEndpoint,
50
- scopes: _scopes,
55
+ scopes: requestedScopes,
51
56
  state,
52
57
  loginHint,
53
58
  redirectURI,
59
+ additionalParams,
54
60
  });
55
61
  },
56
62
  validateAuthorizationCode: async ({ code, redirectURI }) => {
@@ -106,5 +112,5 @@ export const linkedin = (options: LinkedInOptions) => {
106
112
  };
107
113
  },
108
114
  options,
109
- } satisfies OAuthProvider<LinkedInProfile>;
115
+ } satisfies UpstreamProvider<LinkedInProfile>;
110
116
  };
@@ -1,13 +1,19 @@
1
1
  import { base64 } from "@better-auth/utils/base64";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
- import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
3
+ import { decodeJwt, importJWK } from "jose";
4
4
  import { logger } from "../env";
5
5
  import { APIError, BetterAuthError } from "../error";
6
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
6
+ import type {
7
+ ClientAssertionGetter,
8
+ ProviderOptions,
9
+ TokenEndpointAuth,
10
+ UpstreamProvider,
11
+ } from "../oauth2";
7
12
  import {
8
13
  createAuthorizationURL,
9
14
  getPrimaryClientId,
10
15
  refreshAccessToken,
16
+ resolveRequestedScopes,
11
17
  validateAuthorizationCode,
12
18
  } from "../oauth2";
13
19
 
@@ -122,33 +128,60 @@ export interface MicrosoftOptions
122
128
  * The tenant ID of the Microsoft account
123
129
  * @default "common"
124
130
  */
125
- tenantId?: string | undefined;
131
+ tenantId?: string;
126
132
  /**
127
133
  * The authentication authority URL. Use the default "https://login.microsoftonline.com" for standard Entra ID or "https://<tenant-id>.ciamlogin.com" for CIAM scenarios.
128
134
  * @default "https://login.microsoftonline.com"
129
135
  */
130
- authority?: string | undefined;
136
+ authority?: string;
137
+ /**
138
+ * Function that returns a JWT client assertion for token endpoint authentication.
139
+ *
140
+ * Use this instead of `clientSecret` when your Microsoft Entra ID app is
141
+ * configured for client authentication with assertions (private_key_jwt or
142
+ * workload identity federation).
143
+ */
144
+ clientAssertion?: ClientAssertionGetter;
131
145
  /**
132
146
  * The size of the profile photo
133
147
  * @default 48
134
148
  */
135
- profilePhotoSize?:
136
- | (48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648)
137
- | undefined;
149
+ profilePhotoSize?: 48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648;
138
150
  /**
139
151
  * Disable profile photo
140
152
  */
141
- disableProfilePhoto?: boolean | undefined;
153
+ disableProfilePhoto?: boolean;
142
154
  }
143
155
 
156
+ const MICROSOFT_ENTRA_ID_DEFAULT_SCOPES = [
157
+ "openid",
158
+ "profile",
159
+ "email",
160
+ "User.Read",
161
+ "offline_access",
162
+ ];
163
+
144
164
  export const microsoft = (options: MicrosoftOptions) => {
145
165
  const tenant = options.tenantId || "common";
146
166
  const authority = options.authority || "https://login.microsoftonline.com";
147
167
  const authorizationEndpoint = `${authority}/${tenant}/oauth2/v2.0/authorize`;
148
168
  const tokenEndpoint = `${authority}/${tenant}/oauth2/v2.0/token`;
169
+ if (options.clientSecret && options.clientAssertion) {
170
+ throw new BetterAuthError(
171
+ "Microsoft Entra ID clientAssertion cannot be combined with clientSecret",
172
+ );
173
+ }
174
+ const tokenEndpointAuth: TokenEndpointAuth | undefined =
175
+ options.clientAssertion
176
+ ? {
177
+ method: "private_key_jwt",
178
+ getClientAssertion: options.clientAssertion,
179
+ }
180
+ : undefined;
149
181
  return {
150
182
  id: "microsoft",
151
183
  name: "Microsoft EntraID",
184
+ callbackPath: "/callback/microsoft",
152
185
  createAuthorizationURL(data) {
153
186
  // Microsoft Entra supports public clients (SPA / native apps with
154
187
  // PKCE only), so clientSecret is intentionally not required here.
@@ -159,21 +192,22 @@ export const microsoft = (options: MicrosoftOptions) => {
159
192
  );
160
193
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
161
194
  }
162
- const scopes = options.disableDefaultScope
163
- ? []
164
- : ["openid", "profile", "email", "User.Read", "offline_access"];
165
- if (options.scope) scopes.push(...options.scope);
166
- if (data.scopes) scopes.push(...data.scopes);
195
+ const requestedScopes = resolveRequestedScopes(
196
+ options,
197
+ MICROSOFT_ENTRA_ID_DEFAULT_SCOPES,
198
+ data.scopes,
199
+ );
167
200
  return createAuthorizationURL({
168
201
  id: "microsoft",
169
202
  options,
170
203
  authorizationEndpoint,
171
204
  state: data.state,
172
205
  codeVerifier: data.codeVerifier,
173
- scopes,
206
+ scopes: requestedScopes,
174
207
  redirectURI: data.redirectURI,
175
208
  prompt: options.prompt,
176
209
  loginHint: data.loginHint,
210
+ additionalParams: data.additionalParams,
177
211
  });
178
212
  },
179
213
  validateAuthorizationCode({ code, codeVerifier, redirectURI }) {
@@ -183,57 +217,24 @@ export const microsoft = (options: MicrosoftOptions) => {
183
217
  redirectURI,
184
218
  options,
185
219
  tokenEndpoint,
220
+ tokenEndpointAuth,
186
221
  });
187
222
  },
188
- async verifyIdToken(token, nonce) {
189
- if (options.disableIdTokenSignIn) {
190
- return false;
191
- }
192
- if (options.verifyIdToken) {
193
- return options.verifyIdToken(token, nonce);
194
- }
195
-
196
- try {
197
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
198
- if (!kid || !jwtAlg) return false;
199
-
200
- const publicKey = await getMicrosoftPublicKey(kid, tenant, authority);
201
- const verifyOptions: {
202
- algorithms: [string];
203
- audience: string | string[];
204
- maxTokenAge: string;
205
- issuer?: string;
206
- } = {
207
- algorithms: [jwtAlg],
208
- audience: options.clientId,
209
- maxTokenAge: "1h",
210
- };
211
- /**
212
- * Issuer varies per user's tenant for multi-tenant endpoints, so only validate for specific tenants.
213
- * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
214
- */
215
- if (
216
- tenant !== "common" &&
217
- tenant !== "organizations" &&
218
- tenant !== "consumers"
219
- ) {
220
- verifyOptions.issuer = `${authority}/${tenant}/v2.0`;
221
- }
222
- const { payload: jwtClaims } = await jwtVerify(
223
- token,
224
- publicKey,
225
- verifyOptions,
226
- );
227
-
228
- if (nonce && jwtClaims.nonce !== nonce) {
229
- return false;
230
- }
231
-
232
- return true;
233
- } catch (error) {
234
- logger.error("Failed to verify ID token:", error);
235
- return false;
236
- }
223
+ idToken: {
224
+ jwks: (header) => getMicrosoftPublicKey(header.kid!, tenant, authority),
225
+ audience: options.clientId,
226
+ maxTokenAge: "1h",
227
+ /**
228
+ * Issuer varies per tenant for multi-tenant endpoints, so only validate it for
229
+ * specific tenants.
230
+ * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
231
+ */
232
+ issuer:
233
+ tenant !== "common" &&
234
+ tenant !== "organizations" &&
235
+ tenant !== "consumers"
236
+ ? `${authority}/${tenant}/v2.0`
237
+ : undefined,
237
238
  },
238
239
  async getUserInfo(token) {
239
240
  if (options.getUserInfo) {
@@ -313,10 +314,11 @@ export const microsoft = (options: MicrosoftOptions) => {
313
314
  scope: scopes.join(" "), // Include the scopes in request to microsoft
314
315
  },
315
316
  tokenEndpoint,
317
+ tokenEndpointAuth,
316
318
  });
317
319
  },
318
320
  options,
319
- } satisfies OAuthProvider;
321
+ } satisfies UpstreamProvider;
320
322
  };
321
323
 
322
324
  export const getMicrosoftPublicKey = async (
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -39,22 +40,28 @@ export interface NaverOptions extends ProviderOptions<NaverProfile> {
39
40
  clientId: string;
40
41
  }
41
42
 
43
+ const NAVER_DEFAULT_SCOPES = ["profile", "email"];
44
+
42
45
  export const naver = (options: NaverOptions) => {
43
46
  const tokenEndpoint = "https://nid.naver.com/oauth2.0/token";
44
47
  return {
45
48
  id: "naver",
46
49
  name: "Naver",
47
- createAuthorizationURL({ state, scopes, redirectURI }) {
48
- const _scopes = options.disableDefaultScope ? [] : ["profile", "email"];
49
- if (options.scope) _scopes.push(...options.scope);
50
- if (scopes) _scopes.push(...scopes);
50
+ callbackPath: "/callback/naver",
51
+ createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
52
+ const requestedScopes = resolveRequestedScopes(
53
+ options,
54
+ NAVER_DEFAULT_SCOPES,
55
+ scopes,
56
+ );
51
57
  return createAuthorizationURL({
52
58
  id: "naver",
53
59
  options,
54
60
  authorizationEndpoint: "https://nid.naver.com/oauth2.0/authorize",
55
- scopes: _scopes,
61
+ scopes: requestedScopes,
56
62
  state,
57
63
  redirectURI,
64
+ additionalParams,
58
65
  });
59
66
  },
60
67
  validateAuthorizationCode: async ({ code, redirectURI }) => {
@@ -109,5 +116,5 @@ export const naver = (options: NaverOptions) => {
109
116
  };
110
117
  },
111
118
  options,
112
- } satisfies OAuthProvider<NaverProfile>;
119
+ } satisfies UpstreamProvider<NaverProfile>;
113
120
  };
@@ -1,8 +1,9 @@
1
1
  import { betterFetch } from "@better-fetch/fetch";
2
- import type { OAuthProvider, ProviderOptions } from "../oauth2";
2
+ import type { ProviderOptions, UpstreamProvider } from "../oauth2";
3
3
  import {
4
4
  createAuthorizationURL,
5
5
  refreshAccessToken,
6
+ resolveRequestedScopes,
6
7
  validateAuthorizationCode,
7
8
  } from "../oauth2";
8
9
 
@@ -23,24 +24,36 @@ export interface NotionOptions extends ProviderOptions<NotionProfile> {
23
24
  clientId: string;
24
25
  }
25
26
 
27
+ const NOTION_DEFAULT_SCOPES: string[] = [];
28
+
26
29
  export const notion = (options: NotionOptions) => {
27
30
  const tokenEndpoint = "https://api.notion.com/v1/oauth/token";
28
31
  return {
29
32
  id: "notion",
30
33
  name: "Notion",
31
- createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
32
- const _scopes: string[] = options.disableDefaultScope ? [] : [];
33
- if (options.scope) _scopes.push(...options.scope);
34
- if (scopes) _scopes.push(...scopes);
34
+ callbackPath: "/callback/notion",
35
+ createAuthorizationURL({
36
+ state,
37
+ scopes,
38
+ loginHint,
39
+ redirectURI,
40
+ additionalParams,
41
+ }) {
42
+ const requestedScopes = resolveRequestedScopes(
43
+ options,
44
+ NOTION_DEFAULT_SCOPES,
45
+ scopes,
46
+ );
35
47
  return createAuthorizationURL({
36
48
  id: "notion",
37
49
  options,
38
50
  authorizationEndpoint: "https://api.notion.com/v1/oauth/authorize",
39
- scopes: _scopes,
51
+ scopes: requestedScopes,
40
52
  state,
41
53
  redirectURI,
42
54
  loginHint,
43
55
  additionalParams: {
56
+ ...(additionalParams ?? {}),
44
57
  owner: "user",
45
58
  },
46
59
  });
@@ -104,5 +117,5 @@ export const notion = (options: NotionOptions) => {
104
117
  };
105
118
  },
106
119
  options,
107
- } satisfies OAuthProvider<NotionProfile>;
120
+ } satisfies UpstreamProvider<NotionProfile>;
108
121
  };