Skip to content

Commit bbcb69f

Browse files
shineli1984cursoragentJCSanPedro
authored
feat(passport): reduce cookie size (#2793)
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: John Carlo San Pedro <j.carlosanpedro@gmail.com>
1 parent bd01b99 commit bbcb69f

File tree

6 files changed

+201
-64
lines changed

6 files changed

+201
-64
lines changed

packages/auth-next-client/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ The session type returned by `useImmutableSession`. Note that `accessToken` is i
582582
interface ImmutableSession {
583583
// accessToken is NOT exposed -- use getAccessToken() instead
584584
refreshToken?: string;
585-
idToken?: string;
585+
idToken?: string; // Only present transiently after sign-in or token refresh (not stored in cookie)
586586
accessTokenExpires: number;
587587
zkEvm?: {
588588
ethAddress: string;
@@ -597,6 +597,8 @@ interface ImmutableSession {
597597
}
598598
```
599599

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+
600602
### LoginConfig
601603

602604
Configuration for the `useLogin` hook's login functions:

packages/auth-next-client/src/callback.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { signIn } from 'next-auth/react';
66
import { handleLoginCallback as handleAuthCallback, type TokenResponse } from '@imtbl/auth';
77
import type { ImmutableUserClient } from './types';
88
import { IMMUTABLE_PROVIDER_ID } from './constants';
9+
import { storeIdToken } from './idTokenStorage';
910

1011
/**
1112
* Config for CallbackPage - matches LoginConfig from @imtbl/auth
@@ -159,6 +160,12 @@ export function CallbackPage({
159160
// Not in a popup - sign in to NextAuth with the tokens
160161
const tokenData = mapTokensToSignInData(tokens);
161162

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+
162169
const result = await signIn(IMMUTABLE_PROVIDER_ID, {
163170
tokens: JSON.stringify(tokenData),
164171
redirect: false,

packages/auth-next-client/src/hooks.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
logoutWithRedirect as rawLogoutWithRedirect,
2020
} from '@imtbl/auth';
2121
import { IMMUTABLE_PROVIDER_ID, TOKEN_EXPIRY_BUFFER_MS } from './constants';
22+
import { storeIdToken, getStoredIdToken, clearStoredIdToken } from './idTokenStorage';
2223

2324
// ---------------------------------------------------------------------------
2425
// Module-level deduplication for session refresh
@@ -189,6 +190,20 @@ export function useImmutableSession(): UseImmutableSessionReturn {
189190
}
190191
}, [session?.accessTokenExpires]);
191192

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+
192207
/**
193208
* Get user function for wallet integration.
194209
* Returns a User object compatible with @imtbl/wallet's getUser option.
@@ -213,6 +228,10 @@ export function useImmutableSession(): UseImmutableSessionReturn {
213228
// Also update the ref so subsequent calls get the fresh data
214229
if (currentSession) {
215230
sessionRef.current = currentSession;
231+
// Immediately persist fresh idToken to localStorage (avoids race with useEffect)
232+
if (currentSession.idToken) {
233+
storeIdToken(currentSession.idToken);
234+
}
216235
}
217236
} catch (error) {
218237
// eslint-disable-next-line no-console
@@ -229,6 +248,10 @@ export function useImmutableSession(): UseImmutableSessionReturn {
229248
if (refreshed) {
230249
currentSession = refreshed as ImmutableSessionInternal;
231250
sessionRef.current = currentSession;
251+
// Persist fresh idToken to localStorage immediately
252+
if (currentSession.idToken) {
253+
storeIdToken(currentSession.idToken);
254+
}
232255
} else {
233256
currentSession = sessionRef.current;
234257
}
@@ -252,7 +275,9 @@ export function useImmutableSession(): UseImmutableSessionReturn {
252275
return {
253276
accessToken: currentSession.accessToken,
254277
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(),
256281
profile: {
257282
sub: currentSession.user?.sub ?? '',
258283
email: currentSession.user?.email ?? undefined,
@@ -387,6 +412,12 @@ export function useLogin(): UseLoginReturn {
387412
profile: { sub: string; email?: string; nickname?: string };
388413
zkEvm?: ZkEvmInfo;
389414
}) => {
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+
390421
const result = await signIn(IMMUTABLE_PROVIDER_ID, {
391422
tokens: JSON.stringify(tokens),
392423
redirect: false,
@@ -550,6 +581,9 @@ export function useLogout(): UseLogoutReturn {
550581
setError(null);
551582

552583
try {
584+
// Clear idToken from localStorage before clearing session
585+
clearStoredIdToken();
586+
553587
// First, clear the NextAuth session (this clears the JWT cookie)
554588
// We use redirect: false to handle the redirect ourselves for federated logout
555589
await signOut({ redirect: false });
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)