@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,39 +1,32 @@
1
- import { resolveAssertionParams } from "./client-assertion.mjs";
2
1
  import { getOAuth2Tokens } from "./utils.mjs";
2
+ import { applyTokenEndpointAuth } from "./token-endpoint-auth.mjs";
3
3
  import { createRemoteJWKSet, jwtVerify } from "jose";
4
- import { base64 } from "@better-auth/utils/base64";
5
4
  import { betterFetch } from "@better-fetch/fetch";
6
5
  //#region src/oauth2/validate-authorization-code.ts
7
- async function authorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, clientAssertion, tokenEndpoint, deviceId, headers, additionalParams = {}, resource }) {
6
+ async function authorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, tokenEndpointAuth, tokenEndpoint, deviceId, headers, additionalParams = {}, resource }) {
8
7
  options = typeof options === "function" ? await options() : options;
9
- if (authentication === "private_key_jwt") {
10
- if (!clientAssertion) throw new Error("private_key_jwt authentication requires a clientAssertion configuration");
11
- const assertionParams = await resolveAssertionParams({
12
- clientAssertion,
13
- clientId: Array.isArray(options.clientId) ? options.clientId[0] : options.clientId,
14
- tokenEndpoint
15
- });
16
- additionalParams = {
17
- ...additionalParams,
18
- ...assertionParams
19
- };
20
- }
21
- return createAuthorizationCodeRequest({
8
+ const request = buildAuthorizationCodeRequest({
22
9
  code,
23
10
  codeVerifier,
24
11
  redirectURI,
25
12
  options,
26
- authentication,
27
13
  deviceId,
28
14
  headers,
29
15
  additionalParams,
30
16
  resource
31
17
  });
18
+ await applyTokenEndpointAuth({
19
+ body: request.body,
20
+ headers: request.headers,
21
+ options,
22
+ tokenEndpoint: tokenEndpoint ?? "",
23
+ grantType: "authorization_code",
24
+ tokenEndpointAuth,
25
+ authentication
26
+ });
27
+ return request;
32
28
  }
33
- /**
34
- * @deprecated use async'd authorizationCodeRequest instead
35
- */
36
- function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, deviceId, headers, additionalParams = {}, resource }) {
29
+ function buildAuthorizationCodeRequest({ code, codeVerifier, redirectURI, options, deviceId, headers, additionalParams = {}, resource }) {
37
30
  const body = new URLSearchParams();
38
31
  const requestHeaders = {
39
32
  "content-type": "application/x-www-form-urlencoded",
@@ -48,26 +41,20 @@ function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, optio
48
41
  body.set("redirect_uri", options.redirectURI || redirectURI);
49
42
  if (resource) if (typeof resource === "string") body.append("resource", resource);
50
43
  else for (const _resource of resource) body.append("resource", _resource);
51
- const primaryClientId = Array.isArray(options.clientId) ? options.clientId[0] : options.clientId;
52
- if (authentication === "basic") requestHeaders["authorization"] = `Basic ${base64.encode(`${primaryClientId}:${options.clientSecret ?? ""}`)}`;
53
- else {
54
- body.set("client_id", primaryClientId);
55
- if (authentication !== "private_key_jwt" && options.clientSecret) body.set("client_secret", options.clientSecret);
56
- }
57
44
  for (const [key, value] of Object.entries(additionalParams)) if (!body.has(key)) body.append(key, value);
58
45
  return {
59
46
  body,
60
47
  headers: requestHeaders
61
48
  };
62
49
  }
63
- async function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication, clientAssertion, deviceId, headers, additionalParams = {}, resource }) {
50
+ async function validateAuthorizationCode({ code, codeVerifier, redirectURI, options, tokenEndpoint, authentication, tokenEndpointAuth, deviceId, headers, additionalParams = {}, resource }) {
64
51
  const { body, headers: requestHeaders } = await authorizationCodeRequest({
65
52
  code,
66
53
  codeVerifier,
67
54
  redirectURI,
68
55
  options,
69
56
  authentication,
70
- clientAssertion,
57
+ tokenEndpointAuth,
71
58
  tokenEndpoint,
72
59
  deviceId,
73
60
  headers,
@@ -89,4 +76,4 @@ async function validateToken(token, jwksEndpoint, options) {
89
76
  });
90
77
  }
91
78
  //#endregion
92
- export { authorizationCodeRequest, createAuthorizationCodeRequest, validateAuthorizationCode, validateToken };
79
+ export { authorizationCodeRequest, validateAuthorizationCode, validateToken };
@@ -0,0 +1,26 @@
1
+ import { UpstreamProvider } from "./oauth-provider.mjs";
2
+
3
+ //#region src/oauth2/verify-id-token.d.ts
4
+ /**
5
+ * Whether a provider can verify a client-submitted id_token.
6
+ *
7
+ * A provider supports id_token sign-in when it declares an {@link UpstreamProvider.idToken}
8
+ * verification config, or when the integrator supplies a `verifyIdToken` override on the
9
+ * provider options. A provider whose options set `disableIdTokenSignIn`, or that declares
10
+ * neither, rejects the client id_token sign-in path with `ID_TOKEN_NOT_SUPPORTED`.
11
+ */
12
+ declare function supportsIdTokenSignIn(provider: UpstreamProvider<any, any>): boolean;
13
+ /**
14
+ * Verify a client-submitted id_token against a provider's verification config.
15
+ *
16
+ * This is the single id_token verifier for every social provider. Providers no longer
17
+ * implement their own boolean `verifyIdToken`; they declare an {@link UpstreamProvider.idToken}
18
+ * config and this function performs the cryptographic check. The contract is fail-closed: a
19
+ * provider without a config (and without an integrator `verifyIdToken` override) returns
20
+ * `false`, so a forged token can never be accepted by omission.
21
+ *
22
+ * @returns `true` only when the token is authentic for the provider.
23
+ */
24
+ declare function verifyProviderIdToken(provider: UpstreamProvider<any, any>, token: string, nonce?: string): Promise<boolean>;
25
+ //#endregion
26
+ export { supportsIdTokenSignIn, verifyProviderIdToken };
@@ -0,0 +1,62 @@
1
+ import { decodeProtectedHeader, jwtVerify } from "jose";
2
+ //#region src/oauth2/verify-id-token.ts
3
+ async function sha256Hex(value) {
4
+ const data = new TextEncoder().encode(value);
5
+ const digest = await crypto.subtle.digest("SHA-256", data);
6
+ return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
7
+ }
8
+ async function nonceMatches(claimNonce, nonce, comparison = "exact") {
9
+ if (typeof claimNonce !== "string") return false;
10
+ if (claimNonce === nonce) return true;
11
+ if (comparison === "exact-or-sha256") return claimNonce === await sha256Hex(nonce);
12
+ return false;
13
+ }
14
+ /**
15
+ * Whether a provider can verify a client-submitted id_token.
16
+ *
17
+ * A provider supports id_token sign-in when it declares an {@link UpstreamProvider.idToken}
18
+ * verification config, or when the integrator supplies a `verifyIdToken` override on the
19
+ * provider options. A provider whose options set `disableIdTokenSignIn`, or that declares
20
+ * neither, rejects the client id_token sign-in path with `ID_TOKEN_NOT_SUPPORTED`.
21
+ */
22
+ function supportsIdTokenSignIn(provider) {
23
+ const options = provider.options ?? {};
24
+ if (options.disableIdTokenSignIn) return false;
25
+ return Boolean(provider.idToken || options.verifyIdToken);
26
+ }
27
+ /**
28
+ * Verify a client-submitted id_token against a provider's verification config.
29
+ *
30
+ * This is the single id_token verifier for every social provider. Providers no longer
31
+ * implement their own boolean `verifyIdToken`; they declare an {@link UpstreamProvider.idToken}
32
+ * config and this function performs the cryptographic check. The contract is fail-closed: a
33
+ * provider without a config (and without an integrator `verifyIdToken` override) returns
34
+ * `false`, so a forged token can never be accepted by omission.
35
+ *
36
+ * @returns `true` only when the token is authentic for the provider.
37
+ */
38
+ async function verifyProviderIdToken(provider, token, nonce) {
39
+ const options = provider.options ?? {};
40
+ if (options.disableIdTokenSignIn) return false;
41
+ try {
42
+ if (options.verifyIdToken) return await options.verifyIdToken(token, nonce);
43
+ const config = provider.idToken;
44
+ if (!config) return false;
45
+ if ("verify" in config) return await config.verify(token, nonce);
46
+ if (token.split(".").length !== 3) return config.allowOpaqueToken === true;
47
+ const { alg } = decodeProtectedHeader(token);
48
+ const { payload } = await jwtVerify(token, config.jwks, {
49
+ issuer: config.issuer,
50
+ audience: config.audience,
51
+ algorithms: config.algorithms ?? (alg ? [alg] : void 0),
52
+ maxTokenAge: config.maxTokenAge
53
+ });
54
+ if (nonce && !await nonceMatches(payload.nonce, nonce, config.nonceComparison)) return false;
55
+ if (config.verifyClaims && !config.verifyClaims(payload)) return false;
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ //#endregion
62
+ export { supportsIdTokenSignIn, verifyProviderIdToken };
@@ -14,6 +14,20 @@ interface VerifyAccessTokenRemote {
14
14
  * is also still active.
15
15
  */
16
16
  force?: boolean;
17
+ /**
18
+ * Accept introspection responses that omit the `aud` claim even when a
19
+ * required `audience` is configured in `verifyOptions`.
20
+ *
21
+ * By default verification fails closed: if you configure an `audience` and
22
+ * the introspection response has no `aud` (or a mismatching one), the token
23
+ * is rejected. Some authorization servers legitimately omit `aud` from
24
+ * introspection responses (it is OPTIONAL per RFC 7662 §2.2); only enable
25
+ * this if you trust the issuer to bind the token to this resource through
26
+ * another mechanism, as it skips the audience check in that case.
27
+ *
28
+ * @default false
29
+ */
30
+ allowMissingAudience?: boolean;
17
31
  }
18
32
  /**
19
33
  * Performs local verification of an access token for your APIs.
@@ -1,10 +1,23 @@
1
1
  import { logger } from "../env/logger.mjs";
2
2
  import { APIError } from "better-call";
3
- import { UnsecuredJWT, createLocalJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
3
+ import { UnsecuredJWT, createLocalJWKSet, decodeProtectedHeader, errors, jwtVerify } from "jose";
4
4
  import { betterFetch } from "@better-fetch/fetch";
5
5
  //#region src/oauth2/verify.ts
6
- /** Last fetched jwks used locally in getJwks @internal */
7
- let jwks;
6
+ const joseInfrastructureErrorCodes = new Set([
7
+ errors.JWKSTimeout.code,
8
+ errors.JWKSInvalid.code,
9
+ errors.JWKSMultipleMatchingKeys.code
10
+ ]);
11
+ function isJoseInfrastructureError(error) {
12
+ return joseInfrastructureErrorCodes.has(error.code);
13
+ }
14
+ const jwksCache = /* @__PURE__ */ new Map();
15
+ /**
16
+ * How long a cached JWKS is trusted before it is refetched
17
+ *
18
+ * @internal
19
+ */
20
+ const JWKS_CACHE_TTL_MS = 300 * 1e3;
8
21
  /**
9
22
  * Performs local verification of an access token for your APIs.
10
23
  *
@@ -28,15 +41,25 @@ async function getJwks(token, opts) {
28
41
  if (error instanceof Error) throw error;
29
42
  throw new Error(error);
30
43
  }
31
- if (!jwtHeaders.kid) throw new Error("Missing jwt kid");
32
- if (!jwks || !jwks.keys.find((jwk) => jwk.kid === jwtHeaders.kid)) {
33
- jwks = typeof opts.jwksFetch === "string" ? await betterFetch(opts.jwksFetch, { headers: { Accept: "application/json" } }).then(async (res) => {
44
+ if (!jwtHeaders.kid) throw new APIError("UNAUTHORIZED", { message: "invalid access token" });
45
+ const kid = jwtHeaders.kid;
46
+ const cacheKey = opts.jwksFetch;
47
+ const cached = jwksCache.get(cacheKey);
48
+ const isFresh = cached ? Date.now() - cached.fetchedAt < JWKS_CACHE_TTL_MS : false;
49
+ const hasKid = cached?.jwks.keys.some((jwk) => jwk.kid === kid) ?? false;
50
+ if (!cached || !isFresh || !hasKid) {
51
+ const jwks = typeof opts.jwksFetch === "string" ? await betterFetch(opts.jwksFetch, { headers: { Accept: "application/json" } }).then(async (res) => {
34
52
  if (res.error) throw new Error(`Jwks failed: ${res.error.message ?? res.error.statusText}`);
35
53
  return res.data;
36
54
  }) : await opts.jwksFetch();
37
55
  if (!jwks) throw new Error("No jwks found");
56
+ jwksCache.set(cacheKey, {
57
+ jwks,
58
+ fetchedAt: Date.now()
59
+ });
60
+ return jwks;
38
61
  }
39
- return jwks;
62
+ return cached.jwks;
40
63
  }
41
64
  /**
42
65
  * Performs local verification of an access token for your API.
@@ -51,9 +74,11 @@ async function verifyAccessToken(token, opts) {
51
74
  verifyOptions: opts.verifyOptions
52
75
  });
53
76
  } catch (error) {
54
- if (error instanceof Error) if (error.name === "TypeError" || error.name === "JWSInvalid") {} else if (error.name === "JWTExpired") throw new APIError("UNAUTHORIZED", { message: "token expired" });
55
- else if (error.name === "JWTInvalid") throw new APIError("UNAUTHORIZED", { message: "token invalid" });
56
- else throw error;
77
+ if (error instanceof Error) if (error.name === "TypeError" || error.name === "JWSInvalid") {} else if (error instanceof errors.JWTExpired) throw new APIError("UNAUTHORIZED", { message: "token expired" });
78
+ else if (error instanceof errors.JOSEError) {
79
+ if (isJoseInfrastructureError(error)) throw error;
80
+ throw new APIError("UNAUTHORIZED", { message: "invalid access token" });
81
+ } else throw error;
57
82
  else throw new Error(error);
58
83
  }
59
84
  if (opts?.remoteVerify) {
@@ -75,8 +100,9 @@ async function verifyAccessToken(token, opts) {
75
100
  if (!introspect.active) throw new APIError("UNAUTHORIZED", { message: "token inactive" });
76
101
  try {
77
102
  const unsecuredJwt = new UnsecuredJWT(introspect).encode();
78
- const { audience: _audience, ...verifyOptions } = opts.verifyOptions;
79
- payload = (introspect.aud ? UnsecuredJWT.decode(unsecuredJwt, opts.verifyOptions) : UnsecuredJWT.decode(unsecuredJwt, verifyOptions)).payload;
103
+ const { audience: _audience, ...verifyOptionsNoAudience } = opts.verifyOptions;
104
+ const skipAudience = !introspect.aud && opts.remoteVerify.allowMissingAudience === true;
105
+ payload = UnsecuredJWT.decode(unsecuredJwt, skipAudience ? verifyOptionsNoAudience : opts.verifyOptions).payload;
80
106
  } catch (error) {
81
107
  throw new Error(error);
82
108
  }
@@ -1,4 +1,6 @@
1
1
  import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
+ import * as jose from "jose";
3
+
2
4
  //#region src/social-providers/apple.d.ts
3
5
  interface AppleProfile {
4
6
  /**
@@ -67,10 +69,12 @@ interface AppleOptions extends ProviderOptions<AppleProfile> {
67
69
  declare const apple: (options: AppleOptions) => {
68
70
  id: "apple";
69
71
  name: string;
72
+ callbackPath: string;
70
73
  createAuthorizationURL({
71
74
  state,
72
75
  scopes,
73
- redirectURI
76
+ redirectURI,
77
+ additionalParams
74
78
  }: {
75
79
  state: string;
76
80
  codeVerifier: string;
@@ -78,7 +82,11 @@ declare const apple: (options: AppleOptions) => {
78
82
  redirectURI: string;
79
83
  display?: string | undefined;
80
84
  loginHint?: string | undefined;
81
- }): Promise<URL>;
85
+ additionalParams?: Record<string, string> | undefined;
86
+ }): Promise<{
87
+ url: URL;
88
+ requestedScopes: string[];
89
+ }>;
82
90
  validateAuthorizationCode: ({
83
91
  code,
84
92
  codeVerifier,
@@ -89,27 +97,17 @@ declare const apple: (options: AppleOptions) => {
89
97
  codeVerifier?: string | undefined;
90
98
  deviceId?: string | undefined;
91
99
  }) => Promise<OAuth2Tokens>;
92
- verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
100
+ idToken: {
101
+ jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
102
+ issuer: string;
103
+ audience: string | string[];
104
+ maxTokenAge: string;
105
+ nonceComparison: "exact-or-sha256";
106
+ };
93
107
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
94
108
  getUserInfo(token: OAuth2Tokens & {
95
109
  user?: {
96
- name /**
97
- * An Integer value that indicates whether the user appears to be a real
98
- * person. Use the value of this claim to mitigate fraud. The possible
99
- * values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal). For
100
- * more information, see ASUserDetectionStatus. This claim is present only
101
- * in iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14
102
- * and later. The claim isn’t present or supported for web-based apps.
103
- */?
104
- /**
105
- * An Integer value that indicates whether the user appears to be a real
106
- * person. Use the value of this claim to mitigate fraud. The possible
107
- * values are: 0 (or Unsupported), 1 (or Unknown), 2 (or LikelyReal). For
108
- * more information, see ASUserDetectionStatus. This claim is present only
109
- * in iOS 14 and later, macOS 11 and later, watchOS 7 and later, tvOS 14
110
- * and later. The claim isn’t present or supported for web-based apps.
111
- */
112
- : {
110
+ name?: {
113
111
  firstName?: string;
114
112
  lastName?: string;
115
113
  };
@@ -1,34 +1,35 @@
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
- import { decodeJwt, decodeProtectedHeader, importJWK, jwtVerify } from "jose";
8
+ import { decodeJwt, importJWK } from "jose";
8
9
  import { betterFetch } from "@better-fetch/fetch";
9
10
  //#region src/social-providers/apple.ts
11
+ const APPLE_DEFAULT_SCOPES = ["email", "name"];
10
12
  const apple = (options) => {
11
13
  const tokenEndpoint = "https://appleid.apple.com/auth/token";
12
14
  return {
13
15
  id: "apple",
14
16
  name: "Apple",
15
- async createAuthorizationURL({ state, scopes, redirectURI }) {
17
+ callbackPath: "/callback/apple",
18
+ async createAuthorizationURL({ state, scopes, redirectURI, additionalParams }) {
16
19
  if (!getPrimaryClientId(options.clientId) || !options.clientSecret) {
17
20
  logger.error("Client ID and client secret are required for Apple. Make sure to provide them in the options.");
18
21
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
19
22
  }
20
- const _scope = options.disableDefaultScope ? [] : ["email", "name"];
21
- if (options.scope) _scope.push(...options.scope);
22
- if (scopes) _scope.push(...scopes);
23
- return await createAuthorizationURL({
23
+ return createAuthorizationURL({
24
24
  id: "apple",
25
25
  options,
26
26
  authorizationEndpoint: "https://appleid.apple.com/auth/authorize",
27
- scopes: _scope,
27
+ scopes: resolveRequestedScopes(options, APPLE_DEFAULT_SCOPES, scopes),
28
28
  state,
29
29
  redirectURI,
30
30
  responseMode: "form_post",
31
- responseType: "code id_token"
31
+ responseType: "code id_token",
32
+ additionalParams
32
33
  });
33
34
  },
34
35
  validateAuthorizationCode: async ({ code, codeVerifier, redirectURI }) => {
@@ -40,26 +41,12 @@ const apple = (options) => {
40
41
  tokenEndpoint
41
42
  });
42
43
  },
43
- async verifyIdToken(token, nonce) {
44
- if (options.disableIdTokenSignIn) return false;
45
- if (options.verifyIdToken) return options.verifyIdToken(token, nonce);
46
- try {
47
- const { kid, alg: jwtAlg } = decodeProtectedHeader(token);
48
- if (!kid || !jwtAlg) return false;
49
- const { payload: jwtClaims } = await jwtVerify(token, await getApplePublicKey(kid), {
50
- algorithms: [jwtAlg],
51
- issuer: "https://appleid.apple.com",
52
- audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
53
- maxTokenAge: "1h"
54
- });
55
- ["email_verified", "is_private_email"].forEach((field) => {
56
- if (jwtClaims[field] !== void 0) jwtClaims[field] = Boolean(jwtClaims[field]);
57
- });
58
- if (nonce && jwtClaims.nonce !== nonce) return false;
59
- return !!jwtClaims;
60
- } catch {
61
- return false;
62
- }
44
+ idToken: {
45
+ jwks: (header) => getApplePublicKey(header.kid),
46
+ issuer: "https://appleid.apple.com",
47
+ audience: options.audience && options.audience.length ? options.audience : options.appBundleIdentifier ? options.appBundleIdentifier : options.clientId,
48
+ maxTokenAge: "1h",
49
+ nonceComparison: "exact-or-sha256"
63
50
  },
64
51
  refreshAccessToken: options.refreshAccessToken ? options.refreshAccessToken : async (refreshToken) => {
65
52
  return refreshAccessToken({
@@ -21,11 +21,13 @@ interface AtlassianOptions extends ProviderOptions<AtlassianProfile> {
21
21
  declare const atlassian: (options: AtlassianOptions) => {
22
22
  id: "atlassian";
23
23
  name: string;
24
+ callbackPath: string;
24
25
  createAuthorizationURL({
25
26
  state,
26
27
  scopes,
27
28
  codeVerifier,
28
- redirectURI
29
+ redirectURI,
30
+ additionalParams
29
31
  }: {
30
32
  state: string;
31
33
  codeVerifier: string;
@@ -33,7 +35,11 @@ declare const atlassian: (options: AtlassianOptions) => {
33
35
  redirectURI: string;
34
36
  display?: string | undefined;
35
37
  loginHint?: string | undefined;
36
- }): Promise<URL>;
38
+ additionalParams?: Record<string, string> | undefined;
39
+ }): Promise<{
40
+ url: URL;
41
+ requestedScopes: string[];
42
+ }>;
37
43
  validateAuthorizationCode: ({
38
44
  code,
39
45
  codeVerifier,
@@ -1,33 +1,36 @@
1
1
  import { BetterAuthError } from "../error/index.mjs";
2
2
  import { logger } from "../env/logger.mjs";
3
+ import { resolveRequestedScopes } from "../oauth2/scopes.mjs";
3
4
  import { createAuthorizationURL } from "../oauth2/create-authorization-url.mjs";
4
5
  import { refreshAccessToken } from "../oauth2/refresh-access-token.mjs";
5
6
  import { validateAuthorizationCode } from "../oauth2/validate-authorization-code.mjs";
6
7
  import { betterFetch } from "@better-fetch/fetch";
7
8
  //#region src/social-providers/atlassian.ts
9
+ const ATLASSIAN_DEFAULT_SCOPES = ["read:jira-user", "offline_access"];
8
10
  const atlassian = (options) => {
9
11
  const tokenEndpoint = "https://auth.atlassian.com/oauth/token";
10
12
  return {
11
13
  id: "atlassian",
12
14
  name: "Atlassian",
13
- async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI }) {
15
+ callbackPath: "/callback/atlassian",
16
+ async createAuthorizationURL({ state, scopes, codeVerifier, redirectURI, additionalParams }) {
14
17
  if (!options.clientId || !options.clientSecret) {
15
18
  logger.error("Client Id and Secret are required for Atlassian");
16
19
  throw new BetterAuthError("CLIENT_ID_AND_SECRET_REQUIRED");
17
20
  }
18
21
  if (!codeVerifier) throw new BetterAuthError("codeVerifier is required for Atlassian");
19
- const _scopes = options.disableDefaultScope ? [] : ["read:jira-user", "offline_access"];
20
- if (options.scope) _scopes.push(...options.scope);
21
- if (scopes) _scopes.push(...scopes);
22
22
  return createAuthorizationURL({
23
23
  id: "atlassian",
24
24
  options,
25
25
  authorizationEndpoint: "https://auth.atlassian.com/authorize",
26
- scopes: _scopes,
26
+ scopes: resolveRequestedScopes(options, ATLASSIAN_DEFAULT_SCOPES, scopes),
27
27
  state,
28
28
  codeVerifier,
29
29
  redirectURI,
30
- additionalParams: { audience: "api.atlassian.com" },
30
+ additionalParams: {
31
+ ...additionalParams ?? {},
32
+ audience: "api.atlassian.com"
33
+ },
31
34
  prompt: options.prompt
32
35
  });
33
36
  },
@@ -1,4 +1,6 @@
1
1
  import { OAuth2Tokens, ProviderOptions } from "../oauth2/oauth-provider.mjs";
2
+ import * as jose from "jose";
3
+
2
4
  //#region src/social-providers/cognito.d.ts
3
5
  interface CognitoProfile {
4
6
  sub: string;
@@ -30,15 +32,30 @@ interface CognitoOptions extends ProviderOptions<CognitoProfile> {
30
32
  region: string;
31
33
  userPoolId: string;
32
34
  requireClientSecret?: boolean | undefined;
35
+ /**
36
+ * Skip the Cognito hosted-UI identity-provider picker by preselecting an
37
+ * IdP (maps to the `identity_provider` query parameter on the authorize
38
+ * request). Accepts `"COGNITO"`, a SAML/OIDC provider name configured on
39
+ * the User Pool, or one of the social providers (`"Google"`, `"Facebook"`,
40
+ * `"LoginWithAmazon"`, `"SignInWithApple"`).
41
+ *
42
+ * Per-request overrides via `signIn.social({ additionalParams: { identity_provider } })`
43
+ * take precedence over this value.
44
+ *
45
+ * @see https://docs.aws.amazon.com/cognito/latest/developerguide/authorization-endpoint.html
46
+ */
47
+ identityProvider?: string | undefined;
33
48
  }
34
49
  declare const cognito: (options: CognitoOptions) => {
35
50
  id: "cognito";
36
51
  name: string;
52
+ callbackPath: string;
37
53
  createAuthorizationURL({
38
54
  state,
39
55
  scopes,
40
56
  codeVerifier,
41
- redirectURI
57
+ redirectURI,
58
+ additionalParams
42
59
  }: {
43
60
  state: string;
44
61
  codeVerifier: string;
@@ -46,7 +63,11 @@ declare const cognito: (options: CognitoOptions) => {
46
63
  redirectURI: string;
47
64
  display?: string | undefined;
48
65
  loginHint?: string | undefined;
49
- }): Promise<URL>;
66
+ additionalParams?: Record<string, string> | undefined;
67
+ }): Promise<{
68
+ url: URL;
69
+ requestedScopes: string[];
70
+ }>;
50
71
  validateAuthorizationCode: ({
51
72
  code,
52
73
  codeVerifier,
@@ -58,7 +79,12 @@ declare const cognito: (options: CognitoOptions) => {
58
79
  deviceId?: string | undefined;
59
80
  }) => Promise<OAuth2Tokens>;
60
81
  refreshAccessToken: (refreshToken: string) => Promise<OAuth2Tokens>;
61
- verifyIdToken(token: string, nonce: string | undefined): Promise<boolean>;
82
+ idToken: {
83
+ jwks: (header: jose.JWTHeaderParameters) => Promise<Uint8Array<ArrayBufferLike> | CryptoKey>;
84
+ issuer: string;
85
+ audience: string | string[];
86
+ maxTokenAge: string;
87
+ };
62
88
  getUserInfo(token: OAuth2Tokens & {
63
89
  user?: {
64
90
  name?: {