@imtbl/auth-next-server 2.12.7-alpha.7 → 2.12.7-alpha.9

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
@@ -7,6 +7,7 @@ Server-side utilities for Immutable authentication with Auth.js v5 (NextAuth) in
7
7
  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.
8
8
 
9
9
  **Key features:**
10
+
10
11
  - Auth.js v5 configuration for Immutable authentication
11
12
  - Route protection via middleware
12
13
  - Server utilities for authenticated data fetching
@@ -40,10 +41,12 @@ Create a file to configure Immutable authentication:
40
41
  import NextAuth from "next-auth";
41
42
  import { createAuthConfig } from "@imtbl/auth-next-server";
42
43
 
43
- export const { handlers, auth, signIn, signOut } = NextAuth(createAuthConfig({
44
- clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
45
- redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
46
- }));
44
+ export const { handlers, auth, signIn, signOut } = NextAuth(
45
+ createAuthConfig({
46
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
47
+ redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
48
+ }),
49
+ );
47
50
  ```
48
51
 
49
52
  ### 2. Set Up API Route
@@ -76,27 +79,29 @@ Creates an Auth.js v5 configuration object for Immutable authentication. You pas
76
79
  import NextAuth from "next-auth";
77
80
  import { createAuthConfig } from "@imtbl/auth-next-server";
78
81
 
79
- const { handlers, auth, signIn, signOut } = NextAuth(createAuthConfig({
80
- // Required
81
- clientId: "your-client-id",
82
- redirectUri: "https://your-app.com/callback",
83
-
84
- // Optional
85
- audience: "platform_api", // Default: "platform_api"
86
- scope: "openid profile email offline_access transact", // Default scope
87
- authenticationDomain: "https://auth.immutable.com", // Default domain
88
- }));
82
+ const { handlers, auth, signIn, signOut } = NextAuth(
83
+ createAuthConfig({
84
+ // Required
85
+ clientId: "your-client-id",
86
+ redirectUri: "https://your-app.com/callback",
87
+
88
+ // Optional
89
+ audience: "platform_api", // Default: "platform_api"
90
+ scope: "openid profile email offline_access transact", // Default scope
91
+ authenticationDomain: "https://auth.immutable.com", // Default domain
92
+ }),
93
+ );
89
94
  ```
90
95
 
91
96
  #### Configuration Options
92
97
 
93
- | Option | Type | Required | Description |
94
- |--------|------|----------|-------------|
95
- | `clientId` | `string` | Yes | Your Immutable application client ID |
96
- | `redirectUri` | `string` | Yes | OAuth redirect URI configured in Immutable Hub |
97
- | `audience` | `string` | No | OAuth audience (default: `"platform_api"`) |
98
- | `scope` | `string` | No | OAuth scopes (default: `"openid profile email offline_access transact"`) |
99
- | `authenticationDomain` | `string` | No | Auth domain (default: `"https://auth.immutable.com"`) |
98
+ | Option | Type | Required | Description |
99
+ | ---------------------- | -------- | -------- | ------------------------------------------------------------------------ |
100
+ | `clientId` | `string` | Yes | Your Immutable application client ID |
101
+ | `redirectUri` | `string` | Yes | OAuth redirect URI configured in Immutable Hub |
102
+ | `audience` | `string` | No | OAuth audience (default: `"platform_api"`) |
103
+ | `scope` | `string` | No | OAuth scopes (default: `"openid profile email offline_access transact"`) |
104
+ | `authenticationDomain` | `string` | No | Auth domain (default: `"https://auth.immutable.com"`) |
100
105
 
101
106
  #### Extending the Configuration
102
107
 
@@ -117,19 +122,20 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
117
122
  secret: process.env.AUTH_SECRET,
118
123
  trustHost: true,
119
124
  basePath: "/api/auth/custom",
120
-
125
+
121
126
  // Extend callbacks (be sure to call the base callbacks first)
122
127
  callbacks: {
123
128
  ...baseConfig.callbacks,
124
129
  async jwt(params) {
125
130
  // Call base jwt callback first
126
- const token = await baseConfig.callbacks?.jwt?.(params) ?? params.token;
131
+ const token = (await baseConfig.callbacks?.jwt?.(params)) ?? params.token;
127
132
  // Add your custom logic
128
133
  return token;
129
134
  },
130
135
  async session(params) {
131
136
  // Call base session callback first
132
- const session = await baseConfig.callbacks?.session?.(params) ?? params.session;
137
+ const session =
138
+ (await baseConfig.callbacks?.session?.(params)) ?? params.session;
133
139
  // Add your custom logic
134
140
  return session;
135
141
  },
@@ -141,18 +147,19 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
141
147
 
142
148
  This package provides several utilities for handling authentication in Server Components. Choose the right one based on your needs:
143
149
 
144
- | Utility | Use Case | Data Fetching | Error Handling |
145
- |---------|----------|---------------|----------------|
146
- | `getAuthProps` | Pass auth state to client, fetch data client-side | No | Manual |
147
- | `getAuthenticatedData` | SSR data fetching with client fallback | Yes | Manual |
148
- | `createProtectedFetchers` | Multiple pages with same error handling | Optional | Centralized |
149
- | `getValidSession` | Custom logic for each auth state | No | Manual (detailed) |
150
+ | Utility | Use Case | Data Fetching | Error Handling |
151
+ | ------------------------- | ------------------------------------------------- | ------------- | ----------------- |
152
+ | `getAuthProps` | Pass auth state to client, fetch data client-side | No | Manual |
153
+ | `getAuthenticatedData` | SSR data fetching with client fallback | Yes | Manual |
154
+ | `createProtectedFetchers` | Multiple pages with same error handling | Optional | Centralized |
155
+ | `getValidSession` | Custom logic for each auth state | No | Manual (detailed) |
150
156
 
151
157
  ### `getAuthProps(auth)`
152
158
 
153
159
  **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.
154
160
 
155
161
  **When to use:**
162
+
156
163
  - Pages where data is fetched client-side (e.g., infinite scroll, real-time updates)
157
164
  - Pages that show a loading skeleton while fetching
158
165
  - When you want full control over loading states in the client
@@ -167,11 +174,11 @@ import { DashboardClient } from "./DashboardClient";
167
174
 
168
175
  export default async function DashboardPage() {
169
176
  const authProps = await getAuthProps(auth);
170
-
177
+
171
178
  if (authProps.authError) {
172
179
  redirect("/login");
173
180
  }
174
-
181
+
175
182
  // DashboardClient will fetch its own data using useImmutableSession().getUser()
176
183
  return <DashboardClient {...authProps} />;
177
184
  }
@@ -182,11 +189,13 @@ export default async function DashboardPage() {
182
189
  **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.
183
190
 
184
191
  **When to use:**
192
+
185
193
  - Pages that benefit from SSR (SEO, faster first paint)
186
194
  - Profile pages, settings pages, or any page showing user-specific data
187
195
  - When you want the best of both worlds: SSR when possible, CSR as fallback
188
196
 
189
197
  **How it works:**
198
+
190
199
  1. If token is valid → fetches data server-side, returns `ssr: true`
191
200
  2. If token is expired → skips fetch, returns `ssr: false`, client refreshes and fetches
192
201
  3. Pair with `useHydratedData` hook on the client for seamless handling
@@ -208,11 +217,11 @@ async function fetchUserProfile(accessToken: string) {
208
217
 
209
218
  export default async function ProfilePage() {
210
219
  const result = await getAuthenticatedData(auth, fetchUserProfile);
211
-
220
+
212
221
  if (result.authError) {
213
222
  redirect("/login");
214
223
  }
215
-
224
+
216
225
  // ProfileClient uses useHydratedData() to handle both SSR data and client fallback
217
226
  return <ProfileClient {...result} />;
218
227
  }
@@ -223,11 +232,13 @@ export default async function ProfilePage() {
223
232
  **Use case:** You have multiple protected pages and want to define auth error handling once, rather than repeating `if (authError) redirect(...)` on every page.
224
233
 
225
234
  **When to use:**
235
+
226
236
  - Apps with many protected pages sharing the same error handling logic
227
237
  - When you want DRY (Don't Repeat Yourself) error handling
228
238
  - Teams that want consistent auth error behavior across the app
229
239
 
230
240
  **How it works:**
241
+
231
242
  - Define error handling once in a shared file
232
243
  - Use the returned `getAuthProps` and `getData` functions in your pages
233
244
  - Auth errors automatically trigger your handler (no manual checking needed)
@@ -245,7 +256,7 @@ export const { getAuthProps, getData } = createProtectedFetchers(
245
256
  (error) => {
246
257
  // This runs automatically when there's an auth error (e.g., RefreshTokenError)
247
258
  redirect(`/login?error=${error}`);
248
- }
259
+ },
249
260
  );
250
261
  ```
251
262
 
@@ -259,7 +270,7 @@ export default async function DashboardPage() {
259
270
  const result = await getData(async (token) => {
260
271
  return fetchDashboardData(token);
261
272
  });
262
-
273
+
263
274
  return <DashboardClient {...result} />;
264
275
  }
265
276
  ```
@@ -281,6 +292,7 @@ export default async function SettingsPage() {
281
292
  **Use case:** You need fine-grained control over different authentication states and want to handle each case with custom logic.
282
293
 
283
294
  **When to use:**
295
+
284
296
  - Complex pages that render completely different UI based on auth state
285
297
  - When you need to distinguish between "token expired" vs "not authenticated"
286
298
  - Analytics or logging that needs to track specific auth states
@@ -294,22 +306,22 @@ import { getValidSession } from "@imtbl/auth-next-server";
294
306
 
295
307
  export default async function AccountPage() {
296
308
  const result = await getValidSession(auth);
297
-
309
+
298
310
  switch (result.status) {
299
311
  case "authenticated":
300
312
  // Full access - render the complete account page with SSR data
301
313
  const userData = await fetchUserData(result.session.accessToken);
302
314
  return <FullAccountPage user={userData} />;
303
-
315
+
304
316
  case "token_expired":
305
317
  // Token expired but user has session - show skeleton, let client refresh
306
318
  // This avoids a flash of "please login" for users who are actually logged in
307
319
  return <AccountPageSkeleton session={result.session} />;
308
-
320
+
309
321
  case "unauthenticated":
310
322
  // No session at all - show login prompt or redirect
311
323
  return <LoginPrompt message="Sign in to view your account" />;
312
-
324
+
313
325
  case "error":
314
326
  // Auth system error (e.g., refresh token revoked) - needs re-login
315
327
  return <AuthErrorPage error={result.error} />;
@@ -324,11 +336,13 @@ export default async function AccountPage() {
324
336
  **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.
325
337
 
326
338
  **When to use:**
339
+
327
340
  - You have groups of pages that all require authentication (e.g., `/dashboard/*`, `/settings/*`)
328
341
  - You want to redirect unauthenticated users before any page code runs
329
342
  - You need consistent protection across many routes without adding checks to each page
330
343
 
331
344
  **When NOT to use:**
345
+
332
346
  - Pages that show different content for authenticated vs unauthenticated users (use page-level checks instead)
333
347
  - Public pages with optional authenticated features
334
348
 
@@ -352,17 +366,18 @@ export const config = {
352
366
 
353
367
  #### Middleware Options
354
368
 
355
- | Option | Type | Description |
356
- |--------|------|-------------|
357
- | `loginUrl` | `string` | URL to redirect unauthenticated users (default: `"/login"`) |
358
- | `protectedPaths` | `(string \| RegExp)[]` | Paths that require authentication |
359
- | `publicPaths` | `(string \| RegExp)[]` | Paths that skip authentication (takes precedence) |
369
+ | Option | Type | Description |
370
+ | ---------------- | ---------------------- | ----------------------------------------------------------- |
371
+ | `loginUrl` | `string` | URL to redirect unauthenticated users (default: `"/login"`) |
372
+ | `protectedPaths` | `(string \| RegExp)[]` | Paths that require authentication |
373
+ | `publicPaths` | `(string \| RegExp)[]` | Paths that skip authentication (takes precedence) |
360
374
 
361
375
  ### `withAuth(auth, handler)`
362
376
 
363
377
  **Use case:** Protect individual API Route Handlers or Server Actions. Ensures the handler only runs for authenticated users.
364
378
 
365
379
  **When to use:**
380
+
366
381
  - API routes that should only be accessible to authenticated users
367
382
  - Server Actions (form submissions, mutations) that require authentication
368
383
  - When you need the session/user info inside the handler
@@ -394,11 +409,11 @@ import { auth } from "@/lib/auth";
394
409
  import { withAuth } from "@imtbl/auth-next-server";
395
410
 
396
411
  export const transferAsset = withAuth(
397
- auth,
412
+ auth,
398
413
  async (session, formData: FormData) => {
399
414
  const assetId = formData.get("assetId") as string;
400
415
  const toAddress = formData.get("toAddress") as string;
401
-
416
+
402
417
  // Use session.user.sub to identify the sender
403
418
  // Use session.accessToken to call Immutable APIs
404
419
  const result = await executeTransfer({
@@ -407,9 +422,9 @@ export const transferAsset = withAuth(
407
422
  assetId,
408
423
  accessToken: session.accessToken,
409
424
  });
410
-
425
+
411
426
  return result;
412
- }
427
+ },
413
428
  );
414
429
  ```
415
430
 
@@ -420,22 +435,24 @@ The package augments the Auth.js `Session` type with Immutable-specific fields:
420
435
  ```typescript
421
436
  interface Session {
422
437
  user: {
423
- sub: string; // Immutable user ID
438
+ sub: string; // Immutable user ID
424
439
  email?: string;
425
440
  nickname?: string;
426
441
  };
427
442
  accessToken: string;
428
443
  refreshToken?: string;
429
- idToken?: string;
444
+ idToken?: string; // Only present transiently after sign-in or token refresh (not stored in cookie)
430
445
  accessTokenExpires: number;
431
446
  zkEvm?: {
432
447
  ethAddress: string;
433
448
  userAdminAddress: string;
434
449
  };
435
- error?: string; // "TokenExpired" or "RefreshTokenError"
450
+ error?: string; // "TokenExpired" or "RefreshTokenError"
436
451
  }
437
452
  ```
438
453
 
454
+ > **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.
455
+
439
456
  ## Token Refresh
440
457
 
441
458
  ### Automatic Refresh on Token Expiry
@@ -453,10 +470,10 @@ import { useImmutableSession } from "@imtbl/auth-next-client";
453
470
 
454
471
  function MyComponent() {
455
472
  const { getUser } = useImmutableSession();
456
-
473
+
457
474
  const handleRegistration = async () => {
458
475
  // After zkEVM registration completes...
459
-
476
+
460
477
  // Force refresh to get updated zkEvm claims from IDP
461
478
  const freshUser = await getUser(true);
462
479
  console.log("Updated zkEvm:", freshUser?.zkEvm);
@@ -465,6 +482,7 @@ function MyComponent() {
465
482
  ```
466
483
 
467
484
  When `forceRefresh` is triggered:
485
+
468
486
  1. Client calls `update({ forceRefresh: true })` via NextAuth
469
487
  2. The `jwt` callback detects `trigger === 'update'` with `forceRefresh: true`
470
488
  3. Server performs a token refresh using the refresh token
@@ -476,10 +494,10 @@ When `forceRefresh` is triggered:
476
494
  The package also exports utilities for manual token handling:
477
495
 
478
496
  ```typescript
479
- import {
480
- isTokenExpired, // Check if access token is expired
481
- refreshAccessToken, // Manually refresh tokens
482
- extractZkEvmFromIdToken // Extract zkEvm claims from ID token
497
+ import {
498
+ isTokenExpired, // Check if access token is expired
499
+ refreshAccessToken, // Manually refresh tokens
500
+ extractZkEvmFromIdToken, // Extract zkEvm claims from ID token
483
501
  } from "@imtbl/auth-next-server";
484
502
  ```
485
503
 
@@ -487,10 +505,10 @@ import {
487
505
 
488
506
  The session may contain an `error` field indicating authentication issues:
489
507
 
490
- | Error | Description | Recommended Action |
491
- |-------|-------------|-------------------|
492
- | `"TokenExpired"` | Access token expired, refresh token may be valid | Let client refresh via `@imtbl/auth-next-client` |
493
- | `"RefreshTokenError"` | Refresh token invalid/expired | Redirect to login |
508
+ | Error | Description | Recommended Action |
509
+ | --------------------- | ------------------------------------------------ | ------------------------------------------------ |
510
+ | `"TokenExpired"` | Access token expired, refresh token may be valid | Let client refresh via `@imtbl/auth-next-client` |
511
+ | `"RefreshTokenError"` | Refresh token invalid/expired | Redirect to login |
494
512
 
495
513
  ## TypeScript
496
514
 
@@ -60,6 +60,7 @@ var import_next_auth = __toESM(require("next-auth"), 1);
60
60
 
61
61
  // src/config.ts
62
62
  var import_credentials = __toESM(require("next-auth/providers/credentials"), 1);
63
+ var import_jwt = require("next-auth/jwt");
63
64
 
64
65
  // src/constants.ts
65
66
  var DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
@@ -148,6 +149,7 @@ async function refreshAccessToken(refreshToken, clientId, authDomain = DEFAULT_A
148
149
 
149
150
  // src/config.ts
150
151
  var Credentials = import_credentials.default.default || import_credentials.default;
152
+ var defaultJwtEncode = import_jwt.encode.default || import_jwt.encode;
151
153
  async function validateTokens(accessToken, authDomain) {
152
154
  try {
153
155
  const response = await fetch(`${authDomain}/userinfo`, {
@@ -169,6 +171,22 @@ async function validateTokens(accessToken, authDomain) {
169
171
  function createAuthConfig(config) {
170
172
  const authDomain = config.authenticationDomain || DEFAULT_AUTH_DOMAIN;
171
173
  return {
174
+ // Custom jwt.encode: strip idToken from the cookie to reduce size and avoid
175
+ // CloudFront 413 "Request Entity Too Large" errors. The idToken (~1-2 KB) is
176
+ // still available in session responses (after sign-in or token refresh) because
177
+ // the session callback runs BEFORE encode. All data extracted FROM idToken
178
+ // (email, nickname, zkEvm) remains in the cookie as separate fields.
179
+ // On the client, idToken is persisted in localStorage by @imtbl/auth-next-client.
180
+ jwt: {
181
+ async encode(params) {
182
+ const { token, ...rest } = params;
183
+ if (token) {
184
+ const { idToken, ...cookieToken } = token;
185
+ return defaultJwtEncode({ ...rest, token: cookieToken });
186
+ }
187
+ return defaultJwtEncode(params);
188
+ }
189
+ },
172
190
  providers: [
173
191
  Credentials({
174
192
  id: IMMUTABLE_PROVIDER_ID,
@@ -13,6 +13,7 @@ import { default as default2 } from "next-auth";
13
13
 
14
14
  // src/config.ts
15
15
  import CredentialsImport from "next-auth/providers/credentials";
16
+ import { encode as encodeImport } from "next-auth/jwt";
16
17
 
17
18
  // src/constants.ts
18
19
  var DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
@@ -101,6 +102,7 @@ async function refreshAccessToken(refreshToken, clientId, authDomain = DEFAULT_A
101
102
 
102
103
  // src/config.ts
103
104
  var Credentials = CredentialsImport.default || CredentialsImport;
105
+ var defaultJwtEncode = encodeImport.default || encodeImport;
104
106
  async function validateTokens(accessToken, authDomain) {
105
107
  try {
106
108
  const response = await fetch(`${authDomain}/userinfo`, {
@@ -122,6 +124,22 @@ async function validateTokens(accessToken, authDomain) {
122
124
  function createAuthConfig(config) {
123
125
  const authDomain = config.authenticationDomain || DEFAULT_AUTH_DOMAIN;
124
126
  return {
127
+ // Custom jwt.encode: strip idToken from the cookie to reduce size and avoid
128
+ // CloudFront 413 "Request Entity Too Large" errors. The idToken (~1-2 KB) is
129
+ // still available in session responses (after sign-in or token refresh) because
130
+ // the session callback runs BEFORE encode. All data extracted FROM idToken
131
+ // (email, nickname, zkEvm) remains in the cookie as separate fields.
132
+ // On the client, idToken is persisted in localStorage by @imtbl/auth-next-client.
133
+ jwt: {
134
+ async encode(params) {
135
+ const { token, ...rest } = params;
136
+ if (token) {
137
+ const { idToken, ...cookieToken } = token;
138
+ return defaultJwtEncode({ ...rest, token: cookieToken });
139
+ }
140
+ return defaultJwtEncode(params);
141
+ }
142
+ },
125
143
  providers: [
126
144
  Credentials({
127
145
  id: IMMUTABLE_PROVIDER_ID,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imtbl/auth-next-server",
3
- "version": "2.12.7-alpha.7",
3
+ "version": "2.12.7-alpha.9",
4
4
  "description": "Immutable Auth.js v5 integration for Next.js - Server-side utilities",
5
5
  "author": "Immutable",
6
6
  "license": "Apache-2.0",
package/src/config.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  // @ts-ignore - Type exists in next-auth v5 but TS resolver may use stale types
4
4
  import type { NextAuthConfig } from 'next-auth';
5
5
  import CredentialsImport from 'next-auth/providers/credentials';
6
+ import { encode as encodeImport } from 'next-auth/jwt';
6
7
  import type { ImmutableAuthConfig, ImmutableTokenData, UserInfoResponse } from './types';
7
8
  import { isTokenExpired, refreshAccessToken, extractZkEvmFromIdToken } from './refresh';
8
9
  import {
@@ -15,6 +16,8 @@ import {
15
16
  // may be nested under a 'default' property
16
17
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
18
  const Credentials = ((CredentialsImport as any).default || CredentialsImport) as typeof CredentialsImport;
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ const defaultJwtEncode = ((encodeImport as any).default || encodeImport) as typeof encodeImport;
18
21
 
19
22
  /**
20
23
  * Validate tokens by calling the userinfo endpoint.
@@ -72,6 +75,23 @@ export function createAuthConfig(config: ImmutableAuthConfig): NextAuthConfig {
72
75
  const authDomain = config.authenticationDomain || DEFAULT_AUTH_DOMAIN;
73
76
 
74
77
  return {
78
+ // Custom jwt.encode: strip idToken from the cookie to reduce size and avoid
79
+ // CloudFront 413 "Request Entity Too Large" errors. The idToken (~1-2 KB) is
80
+ // still available in session responses (after sign-in or token refresh) because
81
+ // the session callback runs BEFORE encode. All data extracted FROM idToken
82
+ // (email, nickname, zkEvm) remains in the cookie as separate fields.
83
+ // On the client, idToken is persisted in localStorage by @imtbl/auth-next-client.
84
+ jwt: {
85
+ async encode(params) {
86
+ const { token, ...rest } = params;
87
+ if (token) {
88
+ const { idToken, ...cookieToken } = token as Record<string, unknown>;
89
+ return defaultJwtEncode({ ...rest, token: cookieToken });
90
+ }
91
+ return defaultJwtEncode(params);
92
+ },
93
+ },
94
+
75
95
  providers: [
76
96
  Credentials({
77
97
  id: IMMUTABLE_PROVIDER_ID,