@better-auth/core 1.7.0-beta.4 → 1.7.0-beta.6

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 (170) hide show
  1. package/dist/api/index.d.mts +47 -4
  2. package/dist/api/index.mjs +40 -1
  3. package/dist/context/global.mjs +1 -1
  4. package/dist/context/transaction.d.mts +7 -4
  5. package/dist/context/transaction.mjs +6 -3
  6. package/dist/db/adapter/factory.mjs +57 -31
  7. package/dist/db/adapter/index.d.mts +54 -10
  8. package/dist/db/adapter/types.d.mts +1 -1
  9. package/dist/db/get-tables.mjs +3 -3
  10. package/dist/db/schema/account.d.mts +1 -1
  11. package/dist/db/schema/account.mjs +1 -1
  12. package/dist/db/type.d.mts +12 -7
  13. package/dist/env/env-impl.mjs +1 -1
  14. package/dist/error/codes.d.mts +5 -0
  15. package/dist/error/codes.mjs +5 -0
  16. package/dist/index.d.mts +2 -2
  17. package/dist/instrumentation/tracer.mjs +1 -1
  18. package/dist/oauth2/create-authorization-url.d.mts +4 -1
  19. package/dist/oauth2/create-authorization-url.mjs +5 -2
  20. package/dist/oauth2/dpop.d.mts +142 -0
  21. package/dist/oauth2/dpop.mjs +246 -0
  22. package/dist/oauth2/index.d.mts +6 -3
  23. package/dist/oauth2/index.mjs +5 -2
  24. package/dist/oauth2/oauth-provider.d.mts +128 -9
  25. package/dist/oauth2/refresh-access-token.mjs +1 -1
  26. package/dist/oauth2/scopes.d.mts +76 -0
  27. package/dist/oauth2/scopes.mjs +96 -0
  28. package/dist/oauth2/utils.mjs +2 -1
  29. package/dist/oauth2/verify-id-token.d.mts +26 -0
  30. package/dist/oauth2/verify-id-token.mjs +62 -0
  31. package/dist/oauth2/verify.d.mts +88 -15
  32. package/dist/oauth2/verify.mjs +187 -19
  33. package/dist/social-providers/apple.d.mts +14 -2
  34. package/dist/social-providers/apple.mjs +12 -36
  35. package/dist/social-providers/atlassian.d.mts +5 -1
  36. package/dist/social-providers/atlassian.mjs +4 -4
  37. package/dist/social-providers/cognito.d.mts +13 -2
  38. package/dist/social-providers/cognito.mjs +24 -32
  39. package/dist/social-providers/discord.d.mts +5 -1
  40. package/dist/social-providers/discord.mjs +7 -6
  41. package/dist/social-providers/dropbox.d.mts +5 -1
  42. package/dist/social-providers/dropbox.mjs +5 -5
  43. package/dist/social-providers/facebook.d.mts +21 -2
  44. package/dist/social-providers/facebook.mjs +46 -22
  45. package/dist/social-providers/figma.d.mts +5 -1
  46. package/dist/social-providers/figma.mjs +5 -5
  47. package/dist/social-providers/github.d.mts +5 -1
  48. package/dist/social-providers/github.mjs +4 -4
  49. package/dist/social-providers/gitlab.d.mts +5 -1
  50. package/dist/social-providers/gitlab.mjs +6 -6
  51. package/dist/social-providers/google.d.mts +29 -3
  52. package/dist/social-providers/google.mjs +24 -30
  53. package/dist/social-providers/huggingface.d.mts +5 -1
  54. package/dist/social-providers/huggingface.mjs +8 -8
  55. package/dist/social-providers/index.d.mts +222 -42
  56. package/dist/social-providers/kakao.d.mts +5 -1
  57. package/dist/social-providers/kakao.mjs +8 -8
  58. package/dist/social-providers/kick.d.mts +5 -1
  59. package/dist/social-providers/kick.mjs +4 -4
  60. package/dist/social-providers/line.d.mts +8 -2
  61. package/dist/social-providers/line.mjs +12 -14
  62. package/dist/social-providers/linear.d.mts +5 -1
  63. package/dist/social-providers/linear.mjs +4 -4
  64. package/dist/social-providers/linkedin.d.mts +5 -1
  65. package/dist/social-providers/linkedin.mjs +10 -10
  66. package/dist/social-providers/microsoft-entra-id.d.mts +41 -6
  67. package/dist/social-providers/microsoft-entra-id.mjs +40 -36
  68. package/dist/social-providers/naver.d.mts +5 -1
  69. package/dist/social-providers/naver.mjs +4 -4
  70. package/dist/social-providers/notion.d.mts +5 -1
  71. package/dist/social-providers/notion.mjs +4 -4
  72. package/dist/social-providers/paybin.d.mts +5 -1
  73. package/dist/social-providers/paybin.mjs +10 -10
  74. package/dist/social-providers/paypal.d.mts +5 -2
  75. package/dist/social-providers/paypal.mjs +8 -13
  76. package/dist/social-providers/polar.d.mts +5 -1
  77. package/dist/social-providers/polar.mjs +8 -8
  78. package/dist/social-providers/railway.d.mts +5 -1
  79. package/dist/social-providers/railway.mjs +9 -9
  80. package/dist/social-providers/reddit.d.mts +5 -1
  81. package/dist/social-providers/reddit.mjs +9 -8
  82. package/dist/social-providers/roblox.d.mts +5 -1
  83. package/dist/social-providers/roblox.mjs +5 -5
  84. package/dist/social-providers/salesforce.d.mts +5 -1
  85. package/dist/social-providers/salesforce.mjs +8 -8
  86. package/dist/social-providers/slack.d.mts +5 -1
  87. package/dist/social-providers/slack.mjs +9 -9
  88. package/dist/social-providers/spotify.d.mts +5 -1
  89. package/dist/social-providers/spotify.mjs +5 -5
  90. package/dist/social-providers/tiktok.d.mts +5 -1
  91. package/dist/social-providers/tiktok.mjs +9 -5
  92. package/dist/social-providers/twitch.d.mts +5 -1
  93. package/dist/social-providers/twitch.mjs +4 -4
  94. package/dist/social-providers/twitter.d.mts +6 -4
  95. package/dist/social-providers/twitter.mjs +9 -9
  96. package/dist/social-providers/vercel.d.mts +5 -1
  97. package/dist/social-providers/vercel.mjs +4 -7
  98. package/dist/social-providers/vk.d.mts +5 -1
  99. package/dist/social-providers/vk.mjs +5 -5
  100. package/dist/social-providers/wechat.d.mts +5 -1
  101. package/dist/social-providers/wechat.mjs +10 -6
  102. package/dist/social-providers/zoom.d.mts +6 -1
  103. package/dist/social-providers/zoom.mjs +15 -9
  104. package/dist/types/context.d.mts +27 -8
  105. package/dist/types/index.d.mts +1 -1
  106. package/dist/types/init-options.d.mts +137 -6
  107. package/dist/types/plugin-client.d.mts +12 -2
  108. package/dist/utils/host.mjs +4 -0
  109. package/dist/utils/url.mjs +4 -3
  110. package/package.json +7 -7
  111. package/src/api/index.ts +82 -0
  112. package/src/context/transaction.ts +45 -12
  113. package/src/db/adapter/factory.ts +127 -64
  114. package/src/db/adapter/index.ts +54 -9
  115. package/src/db/adapter/types.ts +1 -0
  116. package/src/db/get-tables.ts +8 -3
  117. package/src/db/schema/account.ts +14 -2
  118. package/src/db/type.ts +12 -7
  119. package/src/env/env-impl.ts +1 -2
  120. package/src/error/codes.ts +5 -0
  121. package/src/oauth2/create-authorization-url.ts +2 -2
  122. package/src/oauth2/dpop.ts +568 -0
  123. package/src/oauth2/index.ts +61 -2
  124. package/src/oauth2/oauth-provider.ts +140 -10
  125. package/src/oauth2/refresh-access-token.ts +2 -2
  126. package/src/oauth2/scopes.ts +118 -0
  127. package/src/oauth2/utils.ts +2 -5
  128. package/src/oauth2/verify-id-token.ts +111 -0
  129. package/src/oauth2/verify.ts +372 -58
  130. package/src/social-providers/apple.ts +24 -61
  131. package/src/social-providers/atlassian.ts +12 -8
  132. package/src/social-providers/cognito.ts +25 -47
  133. package/src/social-providers/discord.ts +19 -8
  134. package/src/social-providers/dropbox.ts +13 -7
  135. package/src/social-providers/facebook.ts +97 -51
  136. package/src/social-providers/figma.ts +13 -9
  137. package/src/social-providers/github.ts +12 -8
  138. package/src/social-providers/gitlab.ts +14 -8
  139. package/src/social-providers/google.ts +66 -47
  140. package/src/social-providers/huggingface.ts +12 -8
  141. package/src/social-providers/kakao.ts +16 -8
  142. package/src/social-providers/kick.ts +12 -7
  143. package/src/social-providers/line.ts +37 -37
  144. package/src/social-providers/linear.ts +12 -6
  145. package/src/social-providers/linkedin.ts +14 -10
  146. package/src/social-providers/microsoft-entra-id.ts +103 -59
  147. package/src/social-providers/naver.ts +12 -6
  148. package/src/social-providers/notion.ts +12 -6
  149. package/src/social-providers/paybin.ts +14 -11
  150. package/src/social-providers/paypal.ts +6 -25
  151. package/src/social-providers/polar.ts +12 -8
  152. package/src/social-providers/railway.ts +13 -9
  153. package/src/social-providers/reddit.ts +25 -10
  154. package/src/social-providers/roblox.ts +18 -7
  155. package/src/social-providers/salesforce.ts +12 -8
  156. package/src/social-providers/slack.ts +18 -9
  157. package/src/social-providers/spotify.ts +13 -7
  158. package/src/social-providers/tiktok.ts +13 -7
  159. package/src/social-providers/twitch.ts +12 -8
  160. package/src/social-providers/twitter.ts +17 -8
  161. package/src/social-providers/vercel.ts +16 -10
  162. package/src/social-providers/vk.ts +13 -7
  163. package/src/social-providers/wechat.ts +28 -9
  164. package/src/social-providers/zoom.ts +19 -6
  165. package/src/types/context.ts +26 -8
  166. package/src/types/index.ts +7 -0
  167. package/src/types/init-options.ts +159 -8
  168. package/src/types/plugin-client.ts +16 -2
  169. package/src/utils/host.ts +15 -0
  170. package/src/utils/url.ts +10 -4
@@ -93,6 +93,7 @@ interface KakaoOptions extends ProviderOptions<KakaoProfile> {
93
93
  declare const kakao: (options: KakaoOptions) => {
94
94
  id: "kakao";
95
95
  name: string;
96
+ callbackPath: string;
96
97
  createAuthorizationURL({
97
98
  state,
98
99
  scopes,
@@ -106,7 +107,10 @@ declare const kakao: (options: KakaoOptions) => {
106
107
  display?: string | undefined;
107
108
  loginHint?: string | undefined;
108
109
  additionalParams?: Record<string, string> | undefined;
109
- }): Promise<URL>;
110
+ }): Promise<{
111
+ url: URL;
112
+ requestedScopes: string[];
113
+ }>;
110
114
  validateAuthorizationCode: ({
111
115
  code,
112
116
  redirectURI
@@ -1,26 +1,26 @@
1
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
1
2
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
2
3
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
3
4
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
4
5
  import { betterFetch } from "@better-fetch/fetch";
5
6
  //#region src/social-providers/kakao.ts
7
+ const KAKAO_DEFAULT_SCOPES = [
8
+ "account_email",
9
+ "profile_image",
10
+ "profile_nickname"
11
+ ];
6
12
  const kakao = (options) => {
7
13
  const tokenEndpoint = "https://kauth.kakao.com/oauth/token";
8
14
  return {
9
15
  id: "kakao",
10
16
  name: "Kakao",
17
+ callbackPath: "/callback/kakao",
11
18
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
12
- const _scopes = options.disableDefaultScope ? [] : [
13
- "account_email",
14
- "profile_image",
15
- "profile_nickname"
16
- ];
17
- if (options.scope) _scopes.push(...options.scope);
18
- if (scopes) _scopes.push(...scopes);
19
19
  return createAuthorizationURL({
20
20
  id: "kakao",
21
21
  options,
22
22
  authorizationEndpoint: "https://kauth.kakao.com/oauth/authorize",
23
- scopes: _scopes,
23
+ scopes: resolveRequestedScopes(options, KAKAO_DEFAULT_SCOPES, scopes),
24
24
  state,
25
25
  redirectURI,
26
26
  additionalParams
@@ -24,6 +24,7 @@ interface KickOptions extends ProviderOptions<KickProfile> {
24
24
  declare const kick: (options: KickOptions) => {
25
25
  id: "kick";
26
26
  name: string;
27
+ callbackPath: string;
27
28
  createAuthorizationURL({
28
29
  state,
29
30
  scopes,
@@ -38,7 +39,10 @@ declare const kick: (options: KickOptions) => {
38
39
  display?: string | undefined;
39
40
  loginHint?: string | undefined;
40
41
  additionalParams?: Record<string, string> | undefined;
41
- }): Promise<URL>;
42
+ }): Promise<{
43
+ url: URL;
44
+ requestedScopes: string[];
45
+ }>;
42
46
  validateAuthorizationCode({
43
47
  code,
44
48
  redirectURI,
@@ -1,22 +1,22 @@
1
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
1
2
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
2
3
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
3
4
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
4
5
  import { betterFetch } from "@better-fetch/fetch";
5
6
  //#region src/social-providers/kick.ts
7
+ const KICK_DEFAULT_SCOPES = ["user:read"];
6
8
  const kick = (options) => {
7
9
  return {
8
10
  id: "kick",
9
11
  name: "Kick",
12
+ callbackPath: "/callback/kick",
10
13
  createAuthorizationURL({ state, scopes, redirectURI, codeVerifier, additionalParams }) {
11
- const _scopes = options.disableDefaultScope ? [] : ["user:read"];
12
- if (options.scope) _scopes.push(...options.scope);
13
- if (scopes) _scopes.push(...scopes);
14
14
  return createAuthorizationURL({
15
15
  id: "kick",
16
16
  redirectURI,
17
17
  options,
18
18
  authorizationEndpoint: "https://id.kick.com/oauth/authorize",
19
- scopes: _scopes,
19
+ scopes: resolveRequestedScopes(options, KICK_DEFAULT_SCOPES, scopes),
20
20
  codeVerifier,
21
21
  state,
22
22
  additionalParams
@@ -33,6 +33,7 @@ interface LineOptions extends ProviderOptions<LineUserInfo | LineIdTokenPayload>
33
33
  declare const line: (options: LineOptions) => {
34
34
  id: "line";
35
35
  name: string;
36
+ callbackPath: string;
36
37
  createAuthorizationURL({
37
38
  state,
38
39
  scopes,
@@ -48,7 +49,10 @@ declare const line: (options: LineOptions) => {
48
49
  display?: string | undefined;
49
50
  loginHint?: string | undefined;
50
51
  additionalParams?: Record<string, string> | undefined;
51
- }): Promise<URL>;
52
+ }): Promise<{
53
+ url: URL;
54
+ requestedScopes: string[];
55
+ }>;
52
56
  validateAuthorizationCode: ({
53
57
  code,
54
58
  codeVerifier,
@@ -60,7 +64,9 @@ declare const line: (options: LineOptions) => {
60
64
  deviceId?: string | undefined;
61
65
  }) => Promise<OAuth2Tokens>;
62
66
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
63
- verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
67
+ idToken: {
68
+ verify: (token: string, nonce: string | undefined) => Promise<boolean>;
69
+ };
64
70
  getUserInfo(token: OAuth2Tokens & {
65
71
  user?: {
66
72
  name?: {
@@ -1,9 +1,15 @@
1
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
1
2
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
2
3
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
3
4
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
4
5
  import { decodeJwt } from "jose";
5
6
  import { betterFetch } from "@better-fetch/fetch";
6
7
  //#region src/social-providers/line.ts
8
+ const LINE_DEFAULT_SCOPES = [
9
+ "openid",
10
+ "profile",
11
+ "email"
12
+ ];
7
13
  /**
8
14
  * LINE Login v2.1
9
15
  * - Authorization endpoint: https://access.line.me/oauth2/v2.1/authorize
@@ -21,19 +27,13 @@ const line = (options) => {
21
27
  return {
22
28
  id: "line",
23
29
  name: "LINE",
24
- async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint, additionalParams }) {
25
- const _scopes = options.disableDefaultScope ? [] : [
26
- "openid",
27
- "profile",
28
- "email"
29
- ];
30
- if (options.scope) _scopes.push(...options.scope);
31
- if (scopes) _scopes.push(...scopes);
32
- return await createAuthorizationURL({
30
+ callbackPath: "/callback/line",
31
+ createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, loginHint, additionalParams }) {
32
+ return createAuthorizationURL({
33
33
  id: "line",
34
34
  options,
35
35
  authorizationEndpoint,
36
- scopes: _scopes,
36
+ scopes: resolveRequestedScopes(options, LINE_DEFAULT_SCOPES, scopes),
37
37
  state,
38
38
  codeVerifier,
39
39
  redirectURI,
@@ -60,9 +60,7 @@ const line = (options) => {
60
60
  tokenEndpoint
61
61
  });
62
62
  },
63
- async verifyIdToken(token, nonce) {
64
- if (options.disableIdTokenSignIn) return false;
65
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
63
+ idToken: { verify: async (token, nonce) => {
66
64
  const body = new URLSearchParams();
67
65
  body.set("id_token", token);
68
66
  body.set("client_id", options.clientId);
@@ -76,7 +74,7 @@ const line = (options) => {
76
74
  if (data.aud !== options.clientId) return false;
77
75
  if (data.nonce && data.nonce !== nonce) return false;
78
76
  return true;
79
- },
77
+ } },
80
78
  async getUserInfo(token) {
81
79
  if (options.getUserInfo) return options.getUserInfo(token);
82
80
  let profile = null;
@@ -20,6 +20,7 @@ interface LinearOptions extends ProviderOptions<LinearUser> {
20
20
  declare const linear: (options: LinearOptions) => {
21
21
  id: "linear";
22
22
  name: string;
23
+ callbackPath: string;
23
24
  createAuthorizationURL({
24
25
  state,
25
26
  scopes,
@@ -34,7 +35,10 @@ declare const linear: (options: LinearOptions) => {
34
35
  display?: string | undefined;
35
36
  loginHint?: string | undefined;
36
37
  additionalParams?: Record<string, string> | undefined;
37
- }): Promise<URL>;
38
+ }): Promise<{
39
+ url: URL;
40
+ requestedScopes: string[];
41
+ }>;
38
42
  validateAuthorizationCode: ({
39
43
  code,
40
44
  redirectURI
@@ -1,22 +1,22 @@
1
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
1
2
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
2
3
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
3
4
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
4
5
  import { betterFetch } from "@better-fetch/fetch";
5
6
  //#region src/social-providers/linear.ts
7
+ const LINEAR_DEFAULT_SCOPES = ["read"];
6
8
  const linear = (options) => {
7
9
  const tokenEndpoint = "https://api.linear.app/oauth/token";
8
10
  return {
9
11
  id: "linear",
10
12
  name: "Linear",
13
+ callbackPath: "/callback/linear",
11
14
  createAuthorizationURL({ state, scopes, loginHint, redirectURI, additionalParams }) {
12
- const _scopes = options.disableDefaultScope ? [] : ["read"];
13
- if (options.scope) _scopes.push(...options.scope);
14
- if (scopes) _scopes.push(...scopes);
15
15
  return createAuthorizationURL({
16
16
  id: "linear",
17
17
  options,
18
18
  authorizationEndpoint: "https://linear.app/oauth/authorize",
19
- scopes: _scopes,
19
+ scopes: resolveRequestedScopes(options, LINEAR_DEFAULT_SCOPES, scopes),
20
20
  state,
21
21
  redirectURI,
22
22
  loginHint,
@@ -19,6 +19,7 @@ interface LinkedInOptions extends ProviderOptions<LinkedInProfile> {
19
19
  declare const linkedin: (options: LinkedInOptions) => {
20
20
  id: "linkedin";
21
21
  name: string;
22
+ callbackPath: string;
22
23
  createAuthorizationURL: ({
23
24
  state,
24
25
  scopes,
@@ -33,7 +34,10 @@ declare const linkedin: (options: LinkedInOptions) => {
33
34
  display?: string | undefined;
34
35
  loginHint?: string | undefined;
35
36
  additionalParams?: Record<string, string> | undefined;
36
- }) => Promise<URL>;
37
+ }) => Promise<{
38
+ url: URL;
39
+ requestedScopes: string[];
40
+ }>;
37
41
  validateAuthorizationCode: ({
38
42
  code,
39
43
  redirectURI
@@ -1,27 +1,27 @@
1
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
1
2
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
2
3
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
3
4
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
4
5
  import { betterFetch } from "@better-fetch/fetch";
5
6
  //#region src/social-providers/linkedin.ts
7
+ const LINKEDIN_DEFAULT_SCOPES = [
8
+ "profile",
9
+ "email",
10
+ "openid"
11
+ ];
6
12
  const linkedin = (options) => {
7
13
  const authorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
8
14
  const tokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
9
15
  return {
10
16
  id: "linkedin",
11
17
  name: "Linkedin",
12
- createAuthorizationURL: async ({ state, scopes, redirectURI, loginHint, additionalParams }) => {
13
- const _scopes = options.disableDefaultScope ? [] : [
14
- "profile",
15
- "email",
16
- "openid"
17
- ];
18
- if (options.scope) _scopes.push(...options.scope);
19
- if (scopes) _scopes.push(...scopes);
20
- return await createAuthorizationURL({
18
+ callbackPath: "/callback/linkedin",
19
+ createAuthorizationURL: ({ state, scopes, redirectURI, loginHint, additionalParams }) => {
20
+ return createAuthorizationURL({
21
21
  id: "linkedin",
22
22
  options,
23
23
  authorizationEndpoint,
24
- scopes: _scopes,
24
+ scopes: resolveRequestedScopes(options, LINKEDIN_DEFAULT_SCOPES, scopes),
25
25
  state,
26
26
  loginHint,
27
27
  redirectURI,
@@ -1,4 +1,7 @@
1
+ import { ClientAssertionGetter } from "../oauth2/client-assertion.mjs";
1
2
  import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
3
+ import * as jose from "jose";
4
+
2
5
  //#region src/social-providers/microsoft-entra-id.d.ts
3
6
  /**
4
7
  * @see [Microsoft Identity Platform - Optional claims reference](https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims-reference)
@@ -109,25 +112,34 @@ interface MicrosoftOptions extends ProviderOptions<MicrosoftEntraIDProfile> {
109
112
  * The tenant ID of the Microsoft account
110
113
  * @default "common"
111
114
  */
112
- tenantId?: string | undefined;
115
+ tenantId?: string;
113
116
  /**
114
117
  * The authentication authority URL. Use the default "https://login.microsoftonline.com" for standard Entra ID or "https://<tenant-id>.ciamlogin.com" for CIAM scenarios.
115
118
  * @default "https://login.microsoftonline.com"
116
119
  */
117
- authority?: string | undefined;
120
+ authority?: string;
121
+ /**
122
+ * Function that returns a JWT client assertion for token endpoint authentication.
123
+ *
124
+ * Use this instead of `clientSecret` when your Microsoft Entra ID app is
125
+ * configured for client authentication with assertions (private_key_jwt or
126
+ * workload identity federation).
127
+ */
128
+ clientAssertion?: ClientAssertionGetter;
118
129
  /**
119
130
  * The size of the profile photo
120
131
  * @default 48
121
132
  */
122
- profilePhotoSize?: (48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648) | undefined;
133
+ profilePhotoSize?: 48 | 64 | 96 | 120 | 240 | 360 | 432 | 504 | 648;
123
134
  /**
124
135
  * Disable profile photo
125
136
  */
126
- disableProfilePhoto?: boolean | undefined;
137
+ disableProfilePhoto?: boolean;
127
138
  }
128
139
  declare const microsoft: (options: MicrosoftOptions) => {
129
140
  id: "microsoft";
130
141
  name: string;
142
+ callbackPath: string;
131
143
  createAuthorizationURL(data: {
132
144
  state: string;
133
145
  codeVerifier: string;
@@ -136,7 +148,10 @@ declare const microsoft: (options: MicrosoftOptions) => {
136
148
  display?: string | undefined;
137
149
  loginHint?: string | undefined;
138
150
  additionalParams?: Record<string, string> | undefined;
139
- }): Promise<URL>;
151
+ }): Promise<{
152
+ url: URL;
153
+ requestedScopes: string[];
154
+ }>;
140
155
  validateAuthorizationCode({
141
156
  code,
142
157
  codeVerifier,
@@ -147,7 +162,27 @@ declare const microsoft: (options: MicrosoftOptions) => {
147
162
  codeVerifier?: string | undefined;
148
163
  deviceId?: string | undefined;
149
164
  }): Promise<OAuth2Tokens>;
150
- verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
165
+ idToken: {
166
+ jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
167
+ audience: string | string[];
168
+ maxTokenAge: string;
169
+ /**
170
+ * Issuer varies per tenant for multi-tenant endpoints, so only validate it for
171
+ * specific tenants.
172
+ * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
173
+ */
174
+ issuer: string | undefined;
175
+ /**
176
+ * The multi-tenant endpoints (common/organizations/consumers) skip the
177
+ * issuer check above because the issuer varies per tenant, and the
178
+ * organizations and consumers JWKS sets overlap. Enforce the tenant
179
+ * binding explicitly so a token from a disallowed account class cannot
180
+ * pass: the issuer must name the token's own tenant, and the account
181
+ * class must match the configured restriction.
182
+ * @see https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference
183
+ */
184
+ verifyClaims: (claims: Record<string, unknown>) => boolean;
185
+ };
151
186
  getUserInfo(token: OAuth2Tokens & {
152
187
  user?: {
153
188
  name?: {
@@ -1,42 +1,56 @@
1
1
  import { APIError, BetterAuthError } from "../error/index.mjs";
2
2
  import { logger } from "../env/logger.mjs";
3
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
3
4
  import { getPrimaryClientId } from "../oauth2/utils.mjs";
4
5
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
5
6
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
6
7
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
7
8
  import { base64 } from "@better-auth/utils/base64";
8
- import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
9
+ import { decodeJwt, importJWK } from "jose";
9
10
  import { betterFetch } from "@better-fetch/fetch";
10
11
  //#region src/social-providers/microsoft-entra-id.ts
12
+ /**
13
+ * Microsoft's fixed tenant id for personal (consumer) Microsoft accounts. Every
14
+ * personal-account token carries it as the `tid` claim, so it distinguishes the
15
+ * consumer account class from work/school tenants.
16
+ * @see https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference
17
+ */
18
+ const MICROSOFT_CONSUMER_TENANT_ID = "9188040d-6c67-4c5b-b112-36a304b66dad";
19
+ const MICROSOFT_ENTRA_ID_DEFAULT_SCOPES = [
20
+ "openid",
21
+ "profile",
22
+ "email",
23
+ "User.Read",
24
+ "offline_access"
25
+ ];
11
26
  const microsoft = (options) => {
12
27
  const tenant = options.tenantId || "common";
13
- const authority = options.authority || "https://login.microsoftonline.com";
28
+ let authority = options.authority || "https://login.microsoftonline.com";
29
+ while (authority.endsWith("/")) authority = authority.slice(0, -1);
14
30
  const authorizationEndpoint = `${authority}/${tenant}/oauth2/v2.0/authorize`;
15
31
  const tokenEndpoint = `${authority}/${tenant}/oauth2/v2.0/token`;
32
+ if (options.clientSecret && options.clientAssertion) throw new BetterAuthError("Microsoft Entra ID clientAssertion cannot be combined with clientSecret");
33
+ const tokenEndpointAuth = options.clientAssertion ? {
34
+ method: "private_key_jwt",
35
+ getClientAssertion: options.clientAssertion
36
+ } : void 0;
16
37
  return {
17
38
  id: "microsoft",
18
39
  name: "Microsoft EntraID",
40
+ callbackPath: "/callback/microsoft",
19
41
  createAuthorizationURL(data) {
20
42
  if (!getPrimaryClientId(options.clientId)) {
21
43
  logger.error("Client Id is required for Microsoft Entra ID. Make sure to provide it in the options.");
22
44
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
23
45
  }
24
- const scopes = options.disableDefaultScope ? [] : [
25
- "openid",
26
- "profile",
27
- "email",
28
- "User.Read",
29
- "offline_access"
30
- ];
31
- if (options.scope) scopes.push(...options.scope);
32
- if (data.scopes) scopes.push(...data.scopes);
46
+ const requestedScopes = resolveRequestedScopes(options, MICROSOFT_ENTRA_ID_DEFAULT_SCOPES, data.scopes);
33
47
  return createAuthorizationURL({
34
48
  id: "microsoft",
35
49
  options,
36
50
  authorizationEndpoint,
37
51
  state: data.state,
38
52
  codeVerifier: data.codeVerifier,
39
- scopes,
53
+ scopes: requestedScopes,
40
54
  redirectURI: data.redirectURI,
41
55
  prompt: options.prompt,
42
56
  loginHint: data.loginHint,
@@ -49,32 +63,21 @@ const microsoft = (options) => {
49
63
  codeVerifier,
50
64
  redirectURI,
51
65
  options,
52
- tokenEndpoint
66
+ tokenEndpoint,
67
+ tokenEndpointAuth
53
68
  });
54
69
  },
55
- async verifyIdToken(token, nonce) {
56
- if (options.disableIdTokenSignIn) return false;
57
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
58
- try {
59
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
60
- if (!kid || !jwtAlg) return false;
61
- const publicKey = await getMicrosoftPublicKey(kid, tenant, authority);
62
- const verifyOptions = {
63
- algorithms: [jwtAlg],
64
- audience: options.clientId,
65
- maxTokenAge: "1h"
66
- };
67
- /**
68
- * Issuer varies per user's tenant for multi-tenant endpoints, so only validate for specific tenants.
69
- * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols#endpoints
70
- */
71
- if (tenant !== "common" && tenant !== "organizations" && tenant !== "consumers") verifyOptions.issuer = `${authority}/${tenant}/v2.0`;
72
- const { payload: jwtClaims } = await jwtVerify(token, publicKey, verifyOptions);
73
- if (nonce && jwtClaims.nonce !== nonce) return false;
70
+ idToken: {
71
+ jwks: (header) => getMicrosoftPublicKey(header.kid, tenant, authority),
72
+ audience: options.clientId,
73
+ maxTokenAge: "1h",
74
+ issuer: tenant !== "common" && tenant !== "organizations" && tenant !== "consumers" ? `${authority}/${tenant}/v2.0` : void 0,
75
+ verifyClaims: (claims) => {
76
+ const tid = claims.tid;
77
+ if (typeof tid !== "string" || claims.iss !== `${authority}/${tid}/v2.0`) return false;
78
+ if (tenant === "organizations" && tid === MICROSOFT_CONSUMER_TENANT_ID) return false;
79
+ if (tenant === "consumers" && tid !== MICROSOFT_CONSUMER_TENANT_ID) return false;
74
80
  return true;
75
- } catch (error) {
76
- logger.error("Failed to verify ID token:", error);
77
- return false;
78
81
  }
79
82
  },
80
83
  async getUserInfo(token) {
@@ -124,7 +127,8 @@ const microsoft = (options) => {
124
127
  clientSecret: options.clientSecret
125
128
  },
126
129
  extraParams: { scope: scopes.join(" ") },
127
- tokenEndpoint
130
+ tokenEndpoint,
131
+ tokenEndpointAuth
128
132
  });
129
133
  },
130
134
  options
@@ -24,6 +24,7 @@ interface NaverOptions extends ProviderOptions<NaverProfile> {
24
24
  declare const naver: (options: NaverOptions) => {
25
25
  id: "naver";
26
26
  name: string;
27
+ callbackPath: string;
27
28
  createAuthorizationURL({
28
29
  state,
29
30
  scopes,
@@ -37,7 +38,10 @@ declare const naver: (options: NaverOptions) => {
37
38
  display?: string | undefined;
38
39
  loginHint?: string | undefined;
39
40
  additionalParams?: Record<string, string> | undefined;
40
- }): Promise<URL>;
41
+ }): Promise<{
42
+ url: URL;
43
+ requestedScopes: string[];
44
+ }>;
41
45
  validateAuthorizationCode: ({
42
46
  code,
43
47
  redirectURI
@@ -1,22 +1,22 @@
1
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
1
2
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
2
3
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
3
4
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
4
5
  import { betterFetch } from "@better-fetch/fetch";
5
6
  //#region src/social-providers/naver.ts
7
+ const NAVER_DEFAULT_SCOPES = ["profile", "email"];
6
8
  const naver = (options) => {
7
9
  const tokenEndpoint = "https://nid.naver.com/oauth2.0/token";
8
10
  return {
9
11
  id: "naver",
10
12
  name: "Naver",
13
+ callbackPath: "/callback/naver",
11
14
  createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
12
- const _scopes = options.disableDefaultScope ? [] : ["profile", "email"];
13
- if (options.scope) _scopes.push(...options.scope);
14
- if (scopes) _scopes.push(...scopes);
15
15
  return createAuthorizationURL({
16
16
  id: "naver",
17
17
  options,
18
18
  authorizationEndpoint: "https://nid.naver.com/oauth2.0/authorize",
19
- scopes: _scopes,
19
+ scopes: resolveRequestedScopes(options, NAVER_DEFAULT_SCOPES, scopes),
20
20
  state,
21
21
  redirectURI,
22
22
  additionalParams
@@ -16,6 +16,7 @@ interface NotionOptions extends ProviderOptions<NotionProfile> {
16
16
  declare const notion: (options: NotionOptions) => {
17
17
  id: "notion";
18
18
  name: string;
19
+ callbackPath: string;
19
20
  createAuthorizationURL({
20
21
  state,
21
22
  scopes,
@@ -30,7 +31,10 @@ declare const notion: (options: NotionOptions) => {
30
31
  display?: string | undefined;
31
32
  loginHint?: string | undefined;
32
33
  additionalParams?: Record<string, string> | undefined;
33
- }): Promise<URL>;
34
+ }): Promise<{
35
+ url: URL;
36
+ requestedScopes: string[];
37
+ }>;
34
38
  validateAuthorizationCode: ({
35
39
  code,
36
40
  redirectURI
@@ -1,22 +1,22 @@
1
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
1
2
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
2
3
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
3
4
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
4
5
  import { betterFetch } from "@better-fetch/fetch";
5
6
  //#region src/social-providers/notion.ts
7
+ const NOTION_DEFAULT_SCOPES = [];
6
8
  const notion = (options) => {
7
9
  const tokenEndpoint = "https://api.notion.com/v1/oauth/token";
8
10
  return {
9
11
  id: "notion",
10
12
  name: "Notion",
13
+ callbackPath: "/callback/notion",
11
14
  createAuthorizationURL({ state, scopes, loginHint, redirectURI, additionalParams }) {
12
- const _scopes = options.disableDefaultScope ? [] : [];
13
- if (options.scope) _scopes.push(...options.scope);
14
- if (scopes) _scopes.push(...scopes);
15
15
  return createAuthorizationURL({
16
16
  id: "notion",
17
17
  options,
18
18
  authorizationEndpoint: "https://api.notion.com/v1/oauth/authorize",
19
- scopes: _scopes,
19
+ scopes: resolveRequestedScopes(options, NOTION_DEFAULT_SCOPES, scopes),
20
20
  state,
21
21
  redirectURI,
22
22
  loginHint,
@@ -21,6 +21,7 @@ interface PaybinOptions extends ProviderOptions<PaybinProfile> {
21
21
  declare const paybin: (options: PaybinOptions) => {
22
22
  id: "paybin";
23
23
  name: string;
24
+ callbackPath: string;
24
25
  createAuthorizationURL({
25
26
  state,
26
27
  scopes,
@@ -36,7 +37,10 @@ declare const paybin: (options: PaybinOptions) => {
36
37
  display?: string | undefined;
37
38
  loginHint?: string | undefined;
38
39
  additionalParams?: Record<string, string> | undefined;
39
- }): Promise<URL>;
40
+ }): Promise<{
41
+ url: URL;
42
+ requestedScopes: string[];
43
+ }>;
40
44
  validateAuthorizationCode: ({
41
45
  code,
42
46
  codeVerifier,