diff --git a/packages/auth-next-client/README.md b/packages/auth-next-client/README.md
index d0b58495e7..d0a36be569 100644
--- a/packages/auth-next-client/README.md
+++ b/packages/auth-next-client/README.md
@@ -582,7 +582,7 @@ The session type returned by `useImmutableSession`. Note that `accessToken` is i
interface ImmutableSession {
// accessToken is NOT exposed -- use getAccessToken() instead
refreshToken?: string;
- idToken?: string;
+ idToken?: string; // Only present transiently after sign-in or token refresh (not stored in cookie)
accessTokenExpires: number;
zkEvm?: {
ethAddress: string;
@@ -597,6 +597,8 @@ interface ImmutableSession {
}
```
+> **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.
+
### LoginConfig
Configuration for the `useLogin` hook's login functions:
diff --git a/packages/auth-next-client/src/callback.tsx b/packages/auth-next-client/src/callback.tsx
index 87cd74711e..96f015258a 100644
--- a/packages/auth-next-client/src/callback.tsx
+++ b/packages/auth-next-client/src/callback.tsx
@@ -6,6 +6,7 @@ import { signIn } from 'next-auth/react';
import { handleLoginCallback as handleAuthCallback, type TokenResponse } from '@imtbl/auth';
import type { ImmutableUserClient } from './types';
import { IMMUTABLE_PROVIDER_ID } from './constants';
+import { storeIdToken } from './idTokenStorage';
/**
* Config for CallbackPage - matches LoginConfig from @imtbl/auth
@@ -159,6 +160,12 @@ export function CallbackPage({
// Not in a popup - sign in to NextAuth with the tokens
const tokenData = mapTokensToSignInData(tokens);
+ // Persist idToken to localStorage before signIn so it's available
+ // immediately. The cookie won't contain idToken (stripped by jwt.encode).
+ if (tokens.idToken) {
+ storeIdToken(tokens.idToken);
+ }
+
const result = await signIn(IMMUTABLE_PROVIDER_ID, {
tokens: JSON.stringify(tokenData),
redirect: false,
diff --git a/packages/auth-next-client/src/hooks.tsx b/packages/auth-next-client/src/hooks.tsx
index 2dafa5533e..2aa038b79f 100644
--- a/packages/auth-next-client/src/hooks.tsx
+++ b/packages/auth-next-client/src/hooks.tsx
@@ -19,6 +19,7 @@ import {
logoutWithRedirect as rawLogoutWithRedirect,
} from '@imtbl/auth';
import { IMMUTABLE_PROVIDER_ID, TOKEN_EXPIRY_BUFFER_MS } from './constants';
+import { storeIdToken, getStoredIdToken, clearStoredIdToken } from './idTokenStorage';
// ---------------------------------------------------------------------------
// Module-level deduplication for session refresh
@@ -189,6 +190,20 @@ export function useImmutableSession(): UseImmutableSessionReturn {
}
}, [session?.accessTokenExpires]);
+ // ---------------------------------------------------------------------------
+ // Sync idToken to localStorage
+ // ---------------------------------------------------------------------------
+
+ // The idToken is stripped from the cookie by jwt.encode on the server to avoid
+ // CloudFront 413 errors. It is only present in the session response transiently
+ // after sign-in or token refresh. When present, persist it in localStorage so
+ // that getUser() can always return it (used by wallet's MagicTEESigner).
+ useEffect(() => {
+ if (session?.idToken) {
+ storeIdToken(session.idToken);
+ }
+ }, [session?.idToken]);
+
/**
* Get user function for wallet integration.
* Returns a User object compatible with @imtbl/wallet's getUser option.
@@ -213,6 +228,10 @@ export function useImmutableSession(): UseImmutableSessionReturn {
// Also update the ref so subsequent calls get the fresh data
if (currentSession) {
sessionRef.current = currentSession;
+ // Immediately persist fresh idToken to localStorage (avoids race with useEffect)
+ if (currentSession.idToken) {
+ storeIdToken(currentSession.idToken);
+ }
}
} catch (error) {
// eslint-disable-next-line no-console
@@ -229,6 +248,10 @@ export function useImmutableSession(): UseImmutableSessionReturn {
if (refreshed) {
currentSession = refreshed as ImmutableSessionInternal;
sessionRef.current = currentSession;
+ // Persist fresh idToken to localStorage immediately
+ if (currentSession.idToken) {
+ storeIdToken(currentSession.idToken);
+ }
} else {
currentSession = sessionRef.current;
}
@@ -252,7 +275,9 @@ export function useImmutableSession(): UseImmutableSessionReturn {
return {
accessToken: currentSession.accessToken,
refreshToken: currentSession.refreshToken,
- idToken: currentSession.idToken,
+ // Prefer session idToken (fresh after sign-in or refresh, before useEffect
+ // stores it), fall back to localStorage for normal reads (cookie has no idToken).
+ idToken: currentSession.idToken || getStoredIdToken(),
profile: {
sub: currentSession.user?.sub ?? '',
email: currentSession.user?.email ?? undefined,
@@ -387,6 +412,12 @@ export function useLogin(): UseLoginReturn {
profile: { sub: string; email?: string; nickname?: string };
zkEvm?: ZkEvmInfo;
}) => {
+ // Persist idToken to localStorage before signIn so it's available immediately.
+ // The cookie won't contain idToken (stripped by jwt.encode on the server).
+ if (tokens.idToken) {
+ storeIdToken(tokens.idToken);
+ }
+
const result = await signIn(IMMUTABLE_PROVIDER_ID, {
tokens: JSON.stringify(tokens),
redirect: false,
@@ -550,6 +581,9 @@ export function useLogout(): UseLogoutReturn {
setError(null);
try {
+ // Clear idToken from localStorage before clearing session
+ clearStoredIdToken();
+
// First, clear the NextAuth session (this clears the JWT cookie)
// We use redirect: false to handle the redirect ourselves for federated logout
await signOut({ redirect: false });
diff --git a/packages/auth-next-client/src/idTokenStorage.ts b/packages/auth-next-client/src/idTokenStorage.ts
new file mode 100644
index 0000000000..9d76a8e346
--- /dev/null
+++ b/packages/auth-next-client/src/idTokenStorage.ts
@@ -0,0 +1,56 @@
+/**
+ * Utility for persisting idToken in localStorage.
+ *
+ * The idToken is stripped from the NextAuth session cookie (via a custom
+ * jwt.encode in @imtbl/auth-next-server) to keep cookie size under CDN header
+ * limits (CloudFront 20 KB). Instead, the client stores idToken in
+ * localStorage so that wallet operations (e.g., MagicTEESigner) can still
+ * access it via getUser().
+ *
+ * All functions are safe to call during SSR or in restricted environments
+ * (e.g., incognito mode with localStorage disabled) -- they silently no-op.
+ */
+
+const ID_TOKEN_STORAGE_KEY = 'imtbl_id_token';
+
+/**
+ * Store the idToken in localStorage.
+ * @param idToken - The raw ID token JWT string
+ */
+export function storeIdToken(idToken: string): void {
+ try {
+ if (typeof window !== 'undefined' && window.localStorage) {
+ window.localStorage.setItem(ID_TOKEN_STORAGE_KEY, idToken);
+ }
+ } catch {
+ // Silently ignore -- localStorage may be unavailable (SSR, incognito, etc.)
+ }
+}
+
+/**
+ * Retrieve the idToken from localStorage.
+ * @returns The stored idToken, or undefined if not available.
+ */
+export function getStoredIdToken(): string | undefined {
+ try {
+ if (typeof window !== 'undefined' && window.localStorage) {
+ return window.localStorage.getItem(ID_TOKEN_STORAGE_KEY) ?? undefined;
+ }
+ } catch {
+ // Silently ignore
+ }
+ return undefined;
+}
+
+/**
+ * Remove the idToken from localStorage (e.g., on logout).
+ */
+export function clearStoredIdToken(): void {
+ try {
+ if (typeof window !== 'undefined' && window.localStorage) {
+ window.localStorage.removeItem(ID_TOKEN_STORAGE_KEY);
+ }
+ } catch {
+ // Silently ignore
+ }
+}
diff --git a/packages/auth-next-server/README.md b/packages/auth-next-server/README.md
index 014ee879b0..1a64b4a171 100644
--- a/packages/auth-next-server/README.md
+++ b/packages/auth-next-server/README.md
@@ -7,6 +7,7 @@ Server-side utilities for Immutable authentication with Auth.js v5 (NextAuth) in
This package provides server-side authentication utilities for Next.js applications using the App Router. It integrates with Auth.js v5 to handle OAuth authentication with Immutable's identity provider.
**Key features:**
+
- Auth.js v5 configuration for Immutable authentication
- Route protection via middleware
- Server utilities for authenticated data fetching
@@ -40,10 +41,12 @@ Create a file to configure Immutable authentication:
import NextAuth from "next-auth";
import { createAuthConfig } from "@imtbl/auth-next-server";
-export const { handlers, auth, signIn, signOut } = NextAuth(createAuthConfig({
- clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
- redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
-}));
+export const { handlers, auth, signIn, signOut } = NextAuth(
+ createAuthConfig({
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
+ redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
+ }),
+);
```
### 2. Set Up API Route
@@ -76,27 +79,29 @@ Creates an Auth.js v5 configuration object for Immutable authentication. You pas
import NextAuth from "next-auth";
import { createAuthConfig } from "@imtbl/auth-next-server";
-const { handlers, auth, signIn, signOut } = NextAuth(createAuthConfig({
- // Required
- clientId: "your-client-id",
- redirectUri: "https://your-app.com/callback",
-
- // Optional
- audience: "platform_api", // Default: "platform_api"
- scope: "openid profile email offline_access transact", // Default scope
- authenticationDomain: "https://auth.immutable.com", // Default domain
-}));
+const { handlers, auth, signIn, signOut } = NextAuth(
+ createAuthConfig({
+ // Required
+ clientId: "your-client-id",
+ redirectUri: "https://your-app.com/callback",
+
+ // Optional
+ audience: "platform_api", // Default: "platform_api"
+ scope: "openid profile email offline_access transact", // Default scope
+ authenticationDomain: "https://auth.immutable.com", // Default domain
+ }),
+);
```
#### Configuration Options
-| Option | Type | Required | Description |
-|--------|------|----------|-------------|
-| `clientId` | `string` | Yes | Your Immutable application client ID |
-| `redirectUri` | `string` | Yes | OAuth redirect URI configured in Immutable Hub |
-| `audience` | `string` | No | OAuth audience (default: `"platform_api"`) |
-| `scope` | `string` | No | OAuth scopes (default: `"openid profile email offline_access transact"`) |
-| `authenticationDomain` | `string` | No | Auth domain (default: `"https://auth.immutable.com"`) |
+| Option | Type | Required | Description |
+| ---------------------- | -------- | -------- | ------------------------------------------------------------------------ |
+| `clientId` | `string` | Yes | Your Immutable application client ID |
+| `redirectUri` | `string` | Yes | OAuth redirect URI configured in Immutable Hub |
+| `audience` | `string` | No | OAuth audience (default: `"platform_api"`) |
+| `scope` | `string` | No | OAuth scopes (default: `"openid profile email offline_access transact"`) |
+| `authenticationDomain` | `string` | No | Auth domain (default: `"https://auth.immutable.com"`) |
#### Extending the Configuration
@@ -117,19 +122,20 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
secret: process.env.AUTH_SECRET,
trustHost: true,
basePath: "/api/auth/custom",
-
+
// Extend callbacks (be sure to call the base callbacks first)
callbacks: {
...baseConfig.callbacks,
async jwt(params) {
// Call base jwt callback first
- const token = await baseConfig.callbacks?.jwt?.(params) ?? params.token;
+ const token = (await baseConfig.callbacks?.jwt?.(params)) ?? params.token;
// Add your custom logic
return token;
},
async session(params) {
// Call base session callback first
- const session = await baseConfig.callbacks?.session?.(params) ?? params.session;
+ const session =
+ (await baseConfig.callbacks?.session?.(params)) ?? params.session;
// Add your custom logic
return session;
},
@@ -141,18 +147,19 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
This package provides several utilities for handling authentication in Server Components. Choose the right one based on your needs:
-| Utility | Use Case | Data Fetching | Error Handling |
-|---------|----------|---------------|----------------|
-| `getAuthProps` | Pass auth state to client, fetch data client-side | No | Manual |
-| `getAuthenticatedData` | SSR data fetching with client fallback | Yes | Manual |
-| `createProtectedFetchers` | Multiple pages with same error handling | Optional | Centralized |
-| `getValidSession` | Custom logic for each auth state | No | Manual (detailed) |
+| Utility | Use Case | Data Fetching | Error Handling |
+| ------------------------- | ------------------------------------------------- | ------------- | ----------------- |
+| `getAuthProps` | Pass auth state to client, fetch data client-side | No | Manual |
+| `getAuthenticatedData` | SSR data fetching with client fallback | Yes | Manual |
+| `createProtectedFetchers` | Multiple pages with same error handling | Optional | Centralized |
+| `getValidSession` | Custom logic for each auth state | No | Manual (detailed) |
### `getAuthProps(auth)`
**Use case:** You want to pass authentication state to a Client Component but handle data fetching entirely on the client side. This is the simplest approach when your page doesn't need SSR data fetching.
**When to use:**
+
- Pages where data is fetched client-side (e.g., infinite scroll, real-time updates)
- Pages that show a loading skeleton while fetching
- When you want full control over loading states in the client
@@ -167,11 +174,11 @@ import { DashboardClient } from "./DashboardClient";
export default async function DashboardPage() {
const authProps = await getAuthProps(auth);
-
+
if (authProps.authError) {
redirect("/login");
}
-
+
// DashboardClient will fetch its own data using useImmutableSession().getUser()
return ;
}
@@ -182,11 +189,13 @@ export default async function DashboardPage() {
**Use case:** You want to fetch data server-side for faster initial page loads (SSR), but gracefully fall back to client-side fetching when the token is expired.
**When to use:**
+
- Pages that benefit from SSR (SEO, faster first paint)
- Profile pages, settings pages, or any page showing user-specific data
- When you want the best of both worlds: SSR when possible, CSR as fallback
**How it works:**
+
1. If token is valid → fetches data server-side, returns `ssr: true`
2. If token is expired → skips fetch, returns `ssr: false`, client refreshes and fetches
3. Pair with `useHydratedData` hook on the client for seamless handling
@@ -208,11 +217,11 @@ async function fetchUserProfile(accessToken: string) {
export default async function ProfilePage() {
const result = await getAuthenticatedData(auth, fetchUserProfile);
-
+
if (result.authError) {
redirect("/login");
}
-
+
// ProfileClient uses useHydratedData() to handle both SSR data and client fallback
return ;
}
@@ -223,11 +232,13 @@ export default async function ProfilePage() {
**Use case:** You have multiple protected pages and want to define auth error handling once, rather than repeating `if (authError) redirect(...)` on every page.
**When to use:**
+
- Apps with many protected pages sharing the same error handling logic
- When you want DRY (Don't Repeat Yourself) error handling
- Teams that want consistent auth error behavior across the app
**How it works:**
+
- Define error handling once in a shared file
- Use the returned `getAuthProps` and `getData` functions in your pages
- Auth errors automatically trigger your handler (no manual checking needed)
@@ -245,7 +256,7 @@ export const { getAuthProps, getData } = createProtectedFetchers(
(error) => {
// This runs automatically when there's an auth error (e.g., RefreshTokenError)
redirect(`/login?error=${error}`);
- }
+ },
);
```
@@ -259,7 +270,7 @@ export default async function DashboardPage() {
const result = await getData(async (token) => {
return fetchDashboardData(token);
});
-
+
return ;
}
```
@@ -281,6 +292,7 @@ export default async function SettingsPage() {
**Use case:** You need fine-grained control over different authentication states and want to handle each case with custom logic.
**When to use:**
+
- Complex pages that render completely different UI based on auth state
- When you need to distinguish between "token expired" vs "not authenticated"
- Analytics or logging that needs to track specific auth states
@@ -294,22 +306,22 @@ import { getValidSession } from "@imtbl/auth-next-server";
export default async function AccountPage() {
const result = await getValidSession(auth);
-
+
switch (result.status) {
case "authenticated":
// Full access - render the complete account page with SSR data
const userData = await fetchUserData(result.session.accessToken);
return ;
-
+
case "token_expired":
// Token expired but user has session - show skeleton, let client refresh
// This avoids a flash of "please login" for users who are actually logged in
return ;
-
+
case "unauthenticated":
// No session at all - show login prompt or redirect
return ;
-
+
case "error":
// Auth system error (e.g., refresh token revoked) - needs re-login
return ;
@@ -324,11 +336,13 @@ export default async function AccountPage() {
**Use case:** Protect entire sections of your app at the routing level, before pages even render. This is the most efficient way to block unauthenticated access.
**When to use:**
+
- You have groups of pages that all require authentication (e.g., `/dashboard/*`, `/settings/*`)
- You want to redirect unauthenticated users before any page code runs
- You need consistent protection across many routes without adding checks to each page
**When NOT to use:**
+
- Pages that show different content for authenticated vs unauthenticated users (use page-level checks instead)
- Public pages with optional authenticated features
@@ -352,17 +366,18 @@ export const config = {
#### Middleware Options
-| Option | Type | Description |
-|--------|------|-------------|
-| `loginUrl` | `string` | URL to redirect unauthenticated users (default: `"/login"`) |
-| `protectedPaths` | `(string \| RegExp)[]` | Paths that require authentication |
-| `publicPaths` | `(string \| RegExp)[]` | Paths that skip authentication (takes precedence) |
+| Option | Type | Description |
+| ---------------- | ---------------------- | ----------------------------------------------------------- |
+| `loginUrl` | `string` | URL to redirect unauthenticated users (default: `"/login"`) |
+| `protectedPaths` | `(string \| RegExp)[]` | Paths that require authentication |
+| `publicPaths` | `(string \| RegExp)[]` | Paths that skip authentication (takes precedence) |
### `withAuth(auth, handler)`
**Use case:** Protect individual API Route Handlers or Server Actions. Ensures the handler only runs for authenticated users.
**When to use:**
+
- API routes that should only be accessible to authenticated users
- Server Actions (form submissions, mutations) that require authentication
- When you need the session/user info inside the handler
@@ -394,11 +409,11 @@ import { auth } from "@/lib/auth";
import { withAuth } from "@imtbl/auth-next-server";
export const transferAsset = withAuth(
- auth,
+ auth,
async (session, formData: FormData) => {
const assetId = formData.get("assetId") as string;
const toAddress = formData.get("toAddress") as string;
-
+
// Use session.user.sub to identify the sender
// Use session.accessToken to call Immutable APIs
const result = await executeTransfer({
@@ -407,9 +422,9 @@ export const transferAsset = withAuth(
assetId,
accessToken: session.accessToken,
});
-
+
return result;
- }
+ },
);
```
@@ -420,22 +435,24 @@ The package augments the Auth.js `Session` type with Immutable-specific fields:
```typescript
interface Session {
user: {
- sub: string; // Immutable user ID
+ sub: string; // Immutable user ID
email?: string;
nickname?: string;
};
accessToken: string;
refreshToken?: string;
- idToken?: string;
+ idToken?: string; // Only present transiently after sign-in or token refresh (not stored in cookie)
accessTokenExpires: number;
zkEvm?: {
ethAddress: string;
userAdminAddress: string;
};
- error?: string; // "TokenExpired" or "RefreshTokenError"
+ error?: string; // "TokenExpired" or "RefreshTokenError"
}
```
+> **Note:** The `idToken` is **not** stored in the session cookie. It is stripped by a custom `jwt.encode` to keep cookie size under CDN header limits. The `idToken` is only present in the session response transiently after sign-in or token refresh. On the client, `@imtbl/auth-next-client` automatically persists it in `localStorage` so that wallet operations (via `getUser()`) can always access it. All data extracted from the idToken (`email`, `nickname`, `zkEvm`) remains in the cookie as separate fields.
+
## Token Refresh
### Automatic Refresh on Token Expiry
@@ -453,10 +470,10 @@ import { useImmutableSession } from "@imtbl/auth-next-client";
function MyComponent() {
const { getUser } = useImmutableSession();
-
+
const handleRegistration = async () => {
// After zkEVM registration completes...
-
+
// Force refresh to get updated zkEvm claims from IDP
const freshUser = await getUser(true);
console.log("Updated zkEvm:", freshUser?.zkEvm);
@@ -465,6 +482,7 @@ function MyComponent() {
```
When `forceRefresh` is triggered:
+
1. Client calls `update({ forceRefresh: true })` via NextAuth
2. The `jwt` callback detects `trigger === 'update'` with `forceRefresh: true`
3. Server performs a token refresh using the refresh token
@@ -476,10 +494,10 @@ When `forceRefresh` is triggered:
The package also exports utilities for manual token handling:
```typescript
-import {
- isTokenExpired, // Check if access token is expired
- refreshAccessToken, // Manually refresh tokens
- extractZkEvmFromIdToken // Extract zkEvm claims from ID token
+import {
+ isTokenExpired, // Check if access token is expired
+ refreshAccessToken, // Manually refresh tokens
+ extractZkEvmFromIdToken, // Extract zkEvm claims from ID token
} from "@imtbl/auth-next-server";
```
@@ -487,10 +505,10 @@ import {
The session may contain an `error` field indicating authentication issues:
-| Error | Description | Recommended Action |
-|-------|-------------|-------------------|
-| `"TokenExpired"` | Access token expired, refresh token may be valid | Let client refresh via `@imtbl/auth-next-client` |
-| `"RefreshTokenError"` | Refresh token invalid/expired | Redirect to login |
+| Error | Description | Recommended Action |
+| --------------------- | ------------------------------------------------ | ------------------------------------------------ |
+| `"TokenExpired"` | Access token expired, refresh token may be valid | Let client refresh via `@imtbl/auth-next-client` |
+| `"RefreshTokenError"` | Refresh token invalid/expired | Redirect to login |
## TypeScript
diff --git a/packages/auth-next-server/src/config.ts b/packages/auth-next-server/src/config.ts
index 34de64c25e..877bcda08f 100644
--- a/packages/auth-next-server/src/config.ts
+++ b/packages/auth-next-server/src/config.ts
@@ -3,6 +3,7 @@
// @ts-ignore - Type exists in next-auth v5 but TS resolver may use stale types
import type { NextAuthConfig } from 'next-auth';
import CredentialsImport from 'next-auth/providers/credentials';
+import { encode as encodeImport } from 'next-auth/jwt';
import type { ImmutableAuthConfig, ImmutableTokenData, UserInfoResponse } from './types';
import { isTokenExpired, refreshAccessToken, extractZkEvmFromIdToken } from './refresh';
import {
@@ -15,6 +16,8 @@ import {
// may be nested under a 'default' property
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Credentials = ((CredentialsImport as any).default || CredentialsImport) as typeof CredentialsImport;
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const defaultJwtEncode = ((encodeImport as any).default || encodeImport) as typeof encodeImport;
/**
* Validate tokens by calling the userinfo endpoint.
@@ -72,6 +75,23 @@ export function createAuthConfig(config: ImmutableAuthConfig): NextAuthConfig {
const authDomain = config.authenticationDomain || DEFAULT_AUTH_DOMAIN;
return {
+ // Custom jwt.encode: strip idToken from the cookie to reduce size and avoid
+ // CloudFront 413 "Request Entity Too Large" errors. The idToken (~1-2 KB) is
+ // still available in session responses (after sign-in or token refresh) because
+ // the session callback runs BEFORE encode. All data extracted FROM idToken
+ // (email, nickname, zkEvm) remains in the cookie as separate fields.
+ // On the client, idToken is persisted in localStorage by @imtbl/auth-next-client.
+ jwt: {
+ async encode(params) {
+ const { token, ...rest } = params;
+ if (token) {
+ const { idToken, ...cookieToken } = token as Record;
+ return defaultJwtEncode({ ...rest, token: cookieToken });
+ }
+ return defaultJwtEncode(params);
+ },
+ },
+
providers: [
Credentials({
id: IMMUTABLE_PROVIDER_ID,