@imtbl/auth-next-client 2.12.7-alpha.7 → 2.12.7-alpha.8

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.
package/README.md CHANGED
@@ -582,7 +582,7 @@ The session type returned by `useImmutableSession`. Note that `accessToken` is i
582
582
  interface ImmutableSession {
583
583
  // accessToken is NOT exposed -- use getAccessToken() instead
584
584
  refreshToken?: string;
585
- idToken?: string;
585
+ idToken?: string; // Only present transiently after sign-in or token refresh (not stored in cookie)
586
586
  accessTokenExpires: number;
587
587
  zkEvm?: {
588
588
  ethAddress: string;
@@ -597,6 +597,8 @@ interface ImmutableSession {
597
597
  }
598
598
  ```
599
599
 
600
+ > **Note:** The `idToken` is **not** stored in the session cookie (to avoid CloudFront 413 errors from oversized headers). It is only present in the session response transiently after sign-in or token refresh. `@imtbl/auth-next-client` automatically persists it in `localStorage` so that `getUser()` always returns a valid `idToken` for wallet operations. All data extracted from the idToken (`email`, `nickname`, `zkEvm`) remains in the cookie as separate fields and is always available in the session.
601
+
600
602
  ### LoginConfig
601
603
 
602
604
  Configuration for the `useLogin` hook's login functions:
@@ -41,6 +41,34 @@ var DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
41
41
  var DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1e3;
42
42
  var TOKEN_EXPIRY_BUFFER_MS = 60 * 1e3;
43
43
 
44
+ // src/idTokenStorage.ts
45
+ var ID_TOKEN_STORAGE_KEY = "imtbl_id_token";
46
+ function storeIdToken(idToken) {
47
+ try {
48
+ if (typeof window !== "undefined" && window.localStorage) {
49
+ window.localStorage.setItem(ID_TOKEN_STORAGE_KEY, idToken);
50
+ }
51
+ } catch {
52
+ }
53
+ }
54
+ function getStoredIdToken() {
55
+ try {
56
+ if (typeof window !== "undefined" && window.localStorage) {
57
+ return window.localStorage.getItem(ID_TOKEN_STORAGE_KEY) ?? void 0;
58
+ }
59
+ } catch {
60
+ }
61
+ return void 0;
62
+ }
63
+ function clearStoredIdToken() {
64
+ try {
65
+ if (typeof window !== "undefined" && window.localStorage) {
66
+ window.localStorage.removeItem(ID_TOKEN_STORAGE_KEY);
67
+ }
68
+ } catch {
69
+ }
70
+ }
71
+
44
72
  // src/callback.tsx
45
73
  var import_jsx_runtime = require("react/jsx-runtime");
46
74
  function getSearchParams() {
@@ -90,6 +118,9 @@ function CallbackPage({
90
118
  window.close();
91
119
  } else {
92
120
  const tokenData = mapTokensToSignInData(tokens);
121
+ if (tokens.idToken) {
122
+ storeIdToken(tokens.idToken);
123
+ }
93
124
  const result = await (0, import_react2.signIn)(IMMUTABLE_PROVIDER_ID, {
94
125
  tokens: JSON.stringify(tokenData),
95
126
  redirect: false
@@ -214,6 +245,11 @@ function useImmutableSession() {
214
245
  deduplicatedUpdate(() => updateRef.current());
215
246
  }
216
247
  }, [session?.accessTokenExpires]);
248
+ (0, import_react3.useEffect)(() => {
249
+ if (session?.idToken) {
250
+ storeIdToken(session.idToken);
251
+ }
252
+ }, [session?.idToken]);
217
253
  const getUser = (0, import_react3.useCallback)(async (forceRefresh) => {
218
254
  let currentSession;
219
255
  if (forceRefresh) {
@@ -223,6 +259,9 @@ function useImmutableSession() {
223
259
  currentSession = updatedSession;
224
260
  if (currentSession) {
225
261
  sessionRef.current = currentSession;
262
+ if (currentSession.idToken) {
263
+ storeIdToken(currentSession.idToken);
264
+ }
226
265
  }
227
266
  } catch (error) {
228
267
  console.error("[auth-next-client] Force refresh failed:", error);
@@ -235,6 +274,9 @@ function useImmutableSession() {
235
274
  if (refreshed) {
236
275
  currentSession = refreshed;
237
276
  sessionRef.current = currentSession;
277
+ if (currentSession.idToken) {
278
+ storeIdToken(currentSession.idToken);
279
+ }
238
280
  } else {
239
281
  currentSession = sessionRef.current;
240
282
  }
@@ -251,7 +293,9 @@ function useImmutableSession() {
251
293
  return {
252
294
  accessToken: currentSession.accessToken,
253
295
  refreshToken: currentSession.refreshToken,
254
- idToken: currentSession.idToken,
296
+ // Prefer session idToken (fresh after sign-in or refresh, before useEffect
297
+ // stores it), fall back to localStorage for normal reads (cookie has no idToken).
298
+ idToken: currentSession.idToken || getStoredIdToken(),
255
299
  profile: {
256
300
  sub: currentSession.user?.sub ?? "",
257
301
  email: currentSession.user?.email ?? void 0,
@@ -291,6 +335,9 @@ function useLogin() {
291
335
  const [isLoggingIn, setIsLoggingIn] = (0, import_react3.useState)(false);
292
336
  const [error, setError] = (0, import_react3.useState)(null);
293
337
  const signInWithTokens = (0, import_react3.useCallback)(async (tokens) => {
338
+ if (tokens.idToken) {
339
+ storeIdToken(tokens.idToken);
340
+ }
294
341
  const result = await (0, import_react4.signIn)(IMMUTABLE_PROVIDER_ID, {
295
342
  tokens: JSON.stringify(tokens),
296
343
  redirect: false
@@ -357,6 +404,7 @@ function useLogout() {
357
404
  setIsLoggingOut(true);
358
405
  setError(null);
359
406
  try {
407
+ clearStoredIdToken();
360
408
  await (0, import_react4.signOut)({ redirect: false });
361
409
  (0, import_auth2.logoutWithRedirect)(config);
362
410
  } catch (err) {
@@ -12,6 +12,34 @@ var DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
12
12
  var DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1e3;
13
13
  var TOKEN_EXPIRY_BUFFER_MS = 60 * 1e3;
14
14
 
15
+ // src/idTokenStorage.ts
16
+ var ID_TOKEN_STORAGE_KEY = "imtbl_id_token";
17
+ function storeIdToken(idToken) {
18
+ try {
19
+ if (typeof window !== "undefined" && window.localStorage) {
20
+ window.localStorage.setItem(ID_TOKEN_STORAGE_KEY, idToken);
21
+ }
22
+ } catch {
23
+ }
24
+ }
25
+ function getStoredIdToken() {
26
+ try {
27
+ if (typeof window !== "undefined" && window.localStorage) {
28
+ return window.localStorage.getItem(ID_TOKEN_STORAGE_KEY) ?? void 0;
29
+ }
30
+ } catch {
31
+ }
32
+ return void 0;
33
+ }
34
+ function clearStoredIdToken() {
35
+ try {
36
+ if (typeof window !== "undefined" && window.localStorage) {
37
+ window.localStorage.removeItem(ID_TOKEN_STORAGE_KEY);
38
+ }
39
+ } catch {
40
+ }
41
+ }
42
+
15
43
  // src/callback.tsx
16
44
  import { jsx, jsxs } from "react/jsx-runtime";
17
45
  function getSearchParams() {
@@ -61,6 +89,9 @@ function CallbackPage({
61
89
  window.close();
62
90
  } else {
63
91
  const tokenData = mapTokensToSignInData(tokens);
92
+ if (tokens.idToken) {
93
+ storeIdToken(tokens.idToken);
94
+ }
64
95
  const result = await signIn(IMMUTABLE_PROVIDER_ID, {
65
96
  tokens: JSON.stringify(tokenData),
66
97
  redirect: false
@@ -195,6 +226,11 @@ function useImmutableSession() {
195
226
  deduplicatedUpdate(() => updateRef.current());
196
227
  }
197
228
  }, [session?.accessTokenExpires]);
229
+ useEffect2(() => {
230
+ if (session?.idToken) {
231
+ storeIdToken(session.idToken);
232
+ }
233
+ }, [session?.idToken]);
198
234
  const getUser = useCallback(async (forceRefresh) => {
199
235
  let currentSession;
200
236
  if (forceRefresh) {
@@ -204,6 +240,9 @@ function useImmutableSession() {
204
240
  currentSession = updatedSession;
205
241
  if (currentSession) {
206
242
  sessionRef.current = currentSession;
243
+ if (currentSession.idToken) {
244
+ storeIdToken(currentSession.idToken);
245
+ }
207
246
  }
208
247
  } catch (error) {
209
248
  console.error("[auth-next-client] Force refresh failed:", error);
@@ -216,6 +255,9 @@ function useImmutableSession() {
216
255
  if (refreshed) {
217
256
  currentSession = refreshed;
218
257
  sessionRef.current = currentSession;
258
+ if (currentSession.idToken) {
259
+ storeIdToken(currentSession.idToken);
260
+ }
219
261
  } else {
220
262
  currentSession = sessionRef.current;
221
263
  }
@@ -232,7 +274,9 @@ function useImmutableSession() {
232
274
  return {
233
275
  accessToken: currentSession.accessToken,
234
276
  refreshToken: currentSession.refreshToken,
235
- idToken: currentSession.idToken,
277
+ // Prefer session idToken (fresh after sign-in or refresh, before useEffect
278
+ // stores it), fall back to localStorage for normal reads (cookie has no idToken).
279
+ idToken: currentSession.idToken || getStoredIdToken(),
236
280
  profile: {
237
281
  sub: currentSession.user?.sub ?? "",
238
282
  email: currentSession.user?.email ?? void 0,
@@ -272,6 +316,9 @@ function useLogin() {
272
316
  const [isLoggingIn, setIsLoggingIn] = useState2(false);
273
317
  const [error, setError] = useState2(null);
274
318
  const signInWithTokens = useCallback(async (tokens) => {
319
+ if (tokens.idToken) {
320
+ storeIdToken(tokens.idToken);
321
+ }
275
322
  const result = await signIn2(IMMUTABLE_PROVIDER_ID, {
276
323
  tokens: JSON.stringify(tokens),
277
324
  redirect: false
@@ -338,6 +385,7 @@ function useLogout() {
338
385
  setIsLoggingOut(true);
339
386
  setError(null);
340
387
  try {
388
+ clearStoredIdToken();
341
389
  await signOut({ redirect: false });
342
390
  rawLogoutWithRedirect(config);
343
391
  } catch (err) {
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Utility for persisting idToken in localStorage.
3
+ *
4
+ * The idToken is stripped from the NextAuth session cookie (via a custom
5
+ * jwt.encode in @imtbl/auth-next-server) to keep cookie size under CDN header
6
+ * limits (CloudFront 20 KB). Instead, the client stores idToken in
7
+ * localStorage so that wallet operations (e.g., MagicTEESigner) can still
8
+ * access it via getUser().
9
+ *
10
+ * All functions are safe to call during SSR or in restricted environments
11
+ * (e.g., incognito mode with localStorage disabled) -- they silently no-op.
12
+ */
13
+ /**
14
+ * Store the idToken in localStorage.
15
+ * @param idToken - The raw ID token JWT string
16
+ */
17
+ export declare function storeIdToken(idToken: string): void;
18
+ /**
19
+ * Retrieve the idToken from localStorage.
20
+ * @returns The stored idToken, or undefined if not available.
21
+ */
22
+ export declare function getStoredIdToken(): string | undefined;
23
+ /**
24
+ * Remove the idToken from localStorage (e.g., on logout).
25
+ */
26
+ export declare function clearStoredIdToken(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imtbl/auth-next-client",
3
- "version": "2.12.7-alpha.7",
3
+ "version": "2.12.7-alpha.8",
4
4
  "description": "Immutable Auth.js v5 integration for Next.js - Client-side components",
5
5
  "author": "Immutable",
6
6
  "license": "Apache-2.0",
@@ -27,8 +27,8 @@
27
27
  }
28
28
  },
29
29
  "dependencies": {
30
- "@imtbl/auth": "2.12.7-alpha.7",
31
- "@imtbl/auth-next-server": "2.12.7-alpha.7"
30
+ "@imtbl/auth": "2.12.7-alpha.8",
31
+ "@imtbl/auth-next-server": "2.12.7-alpha.8"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "next": "^15.0.0",
package/src/callback.tsx CHANGED
@@ -6,6 +6,7 @@ import { signIn } from 'next-auth/react';
6
6
  import { handleLoginCallback as handleAuthCallback, type TokenResponse } from '@imtbl/auth';
7
7
  import type { ImmutableUserClient } from './types';
8
8
  import { IMMUTABLE_PROVIDER_ID } from './constants';
9
+ import { storeIdToken } from './idTokenStorage';
9
10
 
10
11
  /**
11
12
  * Config for CallbackPage - matches LoginConfig from @imtbl/auth
@@ -159,6 +160,12 @@ export function CallbackPage({
159
160
  // Not in a popup - sign in to NextAuth with the tokens
160
161
  const tokenData = mapTokensToSignInData(tokens);
161
162
 
163
+ // Persist idToken to localStorage before signIn so it's available
164
+ // immediately. The cookie won't contain idToken (stripped by jwt.encode).
165
+ if (tokens.idToken) {
166
+ storeIdToken(tokens.idToken);
167
+ }
168
+
162
169
  const result = await signIn(IMMUTABLE_PROVIDER_ID, {
163
170
  tokens: JSON.stringify(tokenData),
164
171
  redirect: false,
package/src/hooks.tsx CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  logoutWithRedirect as rawLogoutWithRedirect,
20
20
  } from '@imtbl/auth';
21
21
  import { IMMUTABLE_PROVIDER_ID, TOKEN_EXPIRY_BUFFER_MS } from './constants';
22
+ import { storeIdToken, getStoredIdToken, clearStoredIdToken } from './idTokenStorage';
22
23
 
23
24
  // ---------------------------------------------------------------------------
24
25
  // Module-level deduplication for session refresh
@@ -189,6 +190,20 @@ export function useImmutableSession(): UseImmutableSessionReturn {
189
190
  }
190
191
  }, [session?.accessTokenExpires]);
191
192
 
193
+ // ---------------------------------------------------------------------------
194
+ // Sync idToken to localStorage
195
+ // ---------------------------------------------------------------------------
196
+
197
+ // The idToken is stripped from the cookie by jwt.encode on the server to avoid
198
+ // CloudFront 413 errors. It is only present in the session response transiently
199
+ // after sign-in or token refresh. When present, persist it in localStorage so
200
+ // that getUser() can always return it (used by wallet's MagicTEESigner).
201
+ useEffect(() => {
202
+ if (session?.idToken) {
203
+ storeIdToken(session.idToken);
204
+ }
205
+ }, [session?.idToken]);
206
+
192
207
  /**
193
208
  * Get user function for wallet integration.
194
209
  * Returns a User object compatible with @imtbl/wallet's getUser option.
@@ -213,6 +228,10 @@ export function useImmutableSession(): UseImmutableSessionReturn {
213
228
  // Also update the ref so subsequent calls get the fresh data
214
229
  if (currentSession) {
215
230
  sessionRef.current = currentSession;
231
+ // Immediately persist fresh idToken to localStorage (avoids race with useEffect)
232
+ if (currentSession.idToken) {
233
+ storeIdToken(currentSession.idToken);
234
+ }
216
235
  }
217
236
  } catch (error) {
218
237
  // eslint-disable-next-line no-console
@@ -229,6 +248,10 @@ export function useImmutableSession(): UseImmutableSessionReturn {
229
248
  if (refreshed) {
230
249
  currentSession = refreshed as ImmutableSessionInternal;
231
250
  sessionRef.current = currentSession;
251
+ // Persist fresh idToken to localStorage immediately
252
+ if (currentSession.idToken) {
253
+ storeIdToken(currentSession.idToken);
254
+ }
232
255
  } else {
233
256
  currentSession = sessionRef.current;
234
257
  }
@@ -252,7 +275,9 @@ export function useImmutableSession(): UseImmutableSessionReturn {
252
275
  return {
253
276
  accessToken: currentSession.accessToken,
254
277
  refreshToken: currentSession.refreshToken,
255
- idToken: currentSession.idToken,
278
+ // Prefer session idToken (fresh after sign-in or refresh, before useEffect
279
+ // stores it), fall back to localStorage for normal reads (cookie has no idToken).
280
+ idToken: currentSession.idToken || getStoredIdToken(),
256
281
  profile: {
257
282
  sub: currentSession.user?.sub ?? '',
258
283
  email: currentSession.user?.email ?? undefined,
@@ -387,6 +412,12 @@ export function useLogin(): UseLoginReturn {
387
412
  profile: { sub: string; email?: string; nickname?: string };
388
413
  zkEvm?: ZkEvmInfo;
389
414
  }) => {
415
+ // Persist idToken to localStorage before signIn so it's available immediately.
416
+ // The cookie won't contain idToken (stripped by jwt.encode on the server).
417
+ if (tokens.idToken) {
418
+ storeIdToken(tokens.idToken);
419
+ }
420
+
390
421
  const result = await signIn(IMMUTABLE_PROVIDER_ID, {
391
422
  tokens: JSON.stringify(tokens),
392
423
  redirect: false,
@@ -550,6 +581,9 @@ export function useLogout(): UseLogoutReturn {
550
581
  setError(null);
551
582
 
552
583
  try {
584
+ // Clear idToken from localStorage before clearing session
585
+ clearStoredIdToken();
586
+
553
587
  // First, clear the NextAuth session (this clears the JWT cookie)
554
588
  // We use redirect: false to handle the redirect ourselves for federated logout
555
589
  await signOut({ redirect: false });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Utility for persisting idToken in localStorage.
3
+ *
4
+ * The idToken is stripped from the NextAuth session cookie (via a custom
5
+ * jwt.encode in @imtbl/auth-next-server) to keep cookie size under CDN header
6
+ * limits (CloudFront 20 KB). Instead, the client stores idToken in
7
+ * localStorage so that wallet operations (e.g., MagicTEESigner) can still
8
+ * access it via getUser().
9
+ *
10
+ * All functions are safe to call during SSR or in restricted environments
11
+ * (e.g., incognito mode with localStorage disabled) -- they silently no-op.
12
+ */
13
+
14
+ const ID_TOKEN_STORAGE_KEY = 'imtbl_id_token';
15
+
16
+ /**
17
+ * Store the idToken in localStorage.
18
+ * @param idToken - The raw ID token JWT string
19
+ */
20
+ export function storeIdToken(idToken: string): void {
21
+ try {
22
+ if (typeof window !== 'undefined' && window.localStorage) {
23
+ window.localStorage.setItem(ID_TOKEN_STORAGE_KEY, idToken);
24
+ }
25
+ } catch {
26
+ // Silently ignore -- localStorage may be unavailable (SSR, incognito, etc.)
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Retrieve the idToken from localStorage.
32
+ * @returns The stored idToken, or undefined if not available.
33
+ */
34
+ export function getStoredIdToken(): string | undefined {
35
+ try {
36
+ if (typeof window !== 'undefined' && window.localStorage) {
37
+ return window.localStorage.getItem(ID_TOKEN_STORAGE_KEY) ?? undefined;
38
+ }
39
+ } catch {
40
+ // Silently ignore
41
+ }
42
+ return undefined;
43
+ }
44
+
45
+ /**
46
+ * Remove the idToken from localStorage (e.g., on logout).
47
+ */
48
+ export function clearStoredIdToken(): void {
49
+ try {
50
+ if (typeof window !== 'undefined' && window.localStorage) {
51
+ window.localStorage.removeItem(ID_TOKEN_STORAGE_KEY);
52
+ }
53
+ } catch {
54
+ // Silently ignore
55
+ }
56
+ }