@imtbl/auth-next-client 2.12.5-alpha.16 → 2.12.5-alpha.21

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
@@ -4,14 +4,12 @@ Client-side React components and hooks for Immutable authentication with Auth.js
4
4
 
5
5
  ## Overview
6
6
 
7
- This package provides React components and hooks for client-side authentication in Next.js applications using the App Router. It works in conjunction with `@imtbl/auth-next-server` to provide a complete authentication solution.
7
+ This package provides minimal client-side utilities for Next.js applications using Immutable authentication. It's designed to work with Next.js's native `SessionProvider` and the standalone login functions from `@imtbl/auth`.
8
8
 
9
9
  **Key features:**
10
- - `ImmutableAuthProvider` - Authentication context provider
11
- - `useImmutableAuth` - Hook for auth state and methods
12
10
  - `CallbackPage` - OAuth callback handler component
13
- - `useHydratedData` - SSR data hydration with client-side fallback
14
- - Automatic token refresh and session synchronization
11
+ - `useImmutableSession` - Hook that provides a `getUser` function for wallet integration
12
+ - Re-exports standalone login functions from `@imtbl/auth`
15
13
 
16
14
  For server-side utilities, use [`@imtbl/auth-next-server`](../auth-next-server).
17
15
 
@@ -19,10 +17,6 @@ For server-side utilities, use [`@imtbl/auth-next-server`](../auth-next-server).
19
17
 
20
18
  ```bash
21
19
  npm install @imtbl/auth-next-client @imtbl/auth-next-server next-auth@5
22
- # or
23
- pnpm add @imtbl/auth-next-client @imtbl/auth-next-server next-auth@5
24
- # or
25
- yarn add @imtbl/auth-next-client @imtbl/auth-next-server next-auth@5
26
20
  ```
27
21
 
28
22
  ### Peer Dependencies
@@ -37,25 +31,29 @@ yarn add @imtbl/auth-next-client @imtbl/auth-next-server next-auth@5
37
31
 
38
32
  First, set up the server-side authentication following the [`@imtbl/auth-next-server` documentation](../auth-next-server).
39
33
 
34
+ ```typescript
35
+ // lib/auth.ts
36
+ import NextAuth from "next-auth";
37
+ import { createAuthConfig } from "@imtbl/auth-next-server";
38
+
39
+ export const { handlers, auth, signIn, signOut } = NextAuth(createAuthConfig({
40
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
41
+ redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
42
+ }));
43
+ ```
44
+
40
45
  ### 2. Create Providers Component
41
46
 
47
+ Use `SessionProvider` from `next-auth/react` directly:
48
+
42
49
  ```tsx
43
50
  // app/providers.tsx
44
51
  "use client";
45
52
 
46
- import { ImmutableAuthProvider } from "@imtbl/auth-next-client";
47
-
48
- const config = {
49
- clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
50
- redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
51
- };
53
+ import { SessionProvider } from "next-auth/react";
52
54
 
53
55
  export function Providers({ children }: { children: React.ReactNode }) {
54
- return (
55
- <ImmutableAuthProvider config={config}>
56
- {children}
57
- </ImmutableAuthProvider>
58
- );
56
+ return <SessionProvider>{children}</SessionProvider>;
59
57
  }
60
58
  ```
61
59
 
@@ -98,180 +96,77 @@ export default function Callback() {
98
96
  }
99
97
  ```
100
98
 
101
- ### 5. Use Authentication in Components
102
-
103
- ```tsx
104
- // components/AuthButton.tsx
105
- "use client";
106
-
107
- import { useImmutableAuth } from "@imtbl/auth-next-client";
108
-
109
- export function AuthButton() {
110
- const { user, isLoading, isLoggingIn, isAuthenticated, signIn, signOut } = useImmutableAuth();
111
-
112
- if (isLoading) {
113
- return <div>Loading...</div>;
114
- }
115
-
116
- if (isAuthenticated) {
117
- return (
118
- <div>
119
- <span>Welcome, {user?.email || user?.sub}</span>
120
- <button onClick={() => signOut()}>Sign Out</button>
121
- </div>
122
- );
123
- }
124
-
125
- return (
126
- <button onClick={() => signIn()} disabled={isLoggingIn}>
127
- {isLoggingIn ? "Signing in..." : "Sign In"}
128
- </button>
129
- );
130
- }
131
- ```
132
-
133
- ## Components
134
-
135
- ### `ImmutableAuthProvider`
136
-
137
- **Use case:** Wraps your application to provide authentication context. Required for all `useImmutableAuth` and related hooks to work.
99
+ ### 5. Add Login Button
138
100
 
139
- **When to use:**
140
- - Required: Must wrap your app at the root level (typically in `app/layout.tsx` or a providers file)
141
- - Provides auth state to all child components via React Context
101
+ Use the standalone login functions from `@imtbl/auth`:
142
102
 
143
103
  ```tsx
144
- // app/providers.tsx
145
- // Use case: Basic provider setup
104
+ // components/LoginButton.tsx
146
105
  "use client";
147
106
 
148
- import { ImmutableAuthProvider } from "@imtbl/auth-next-client";
107
+ import { loginWithPopup } from "@imtbl/auth-next-client";
108
+ import { signIn } from "next-auth/react";
149
109
 
150
110
  const config = {
151
111
  clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
152
- redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
112
+ redirectUri: `${window.location.origin}/callback`,
153
113
  };
154
114
 
155
- export function Providers({ children }: { children: React.ReactNode }) {
156
- return (
157
- <ImmutableAuthProvider config={config}>
158
- {children}
159
- </ImmutableAuthProvider>
160
- );
115
+ export function LoginButton() {
116
+ const handleLogin = async () => {
117
+ // Open popup login
118
+ const tokens = await loginWithPopup(config);
119
+
120
+ // Sign in to NextAuth with the tokens
121
+ await signIn("immutable", {
122
+ tokens: JSON.stringify(tokens),
123
+ redirect: false,
124
+ });
125
+ };
126
+
127
+ return <button onClick={handleLogin}>Sign In with Immutable</button>;
161
128
  }
162
129
  ```
163
130
 
164
- #### With SSR Session Hydration
131
+ ### 6. Connect Wallet with getUser
165
132
 
166
- Pass the server-side session to avoid a flash of unauthenticated state on page load:
133
+ Use `useImmutableSession` for wallet integration:
167
134
 
168
135
  ```tsx
169
- // app/providers.tsx
170
- // Use case: SSR hydration to prevent auth state flash
136
+ // components/WalletConnect.tsx
171
137
  "use client";
172
138
 
173
- import { ImmutableAuthProvider } from "@imtbl/auth-next-client";
174
- import type { Session } from "next-auth";
175
-
176
- export function Providers({
177
- children,
178
- session // Passed from Server Component
179
- }: {
180
- children: React.ReactNode;
181
- session: Session | null;
182
- }) {
183
- return (
184
- <ImmutableAuthProvider config={config} session={session}>
185
- {children}
186
- </ImmutableAuthProvider>
187
- );
188
- }
189
- ```
190
-
191
- ```tsx
192
- // app/layout.tsx
193
- // Use case: Get session server-side and pass to providers
194
- import { auth } from "@/lib/auth";
195
- import { Providers } from "./providers";
139
+ import { useImmutableSession } from "@imtbl/auth-next-client";
140
+ import { connectWallet } from "@imtbl/wallet";
196
141
 
197
- export default async function RootLayout({ children }) {
198
- const session = await auth();
199
- return (
200
- <html>
201
- <body>
202
- <Providers session={session}>{children}</Providers>
203
- </body>
204
- </html>
205
- );
206
- }
207
- ```
142
+ export function WalletConnect() {
143
+ const { isAuthenticated, getUser } = useImmutableSession();
208
144
 
209
- #### With Custom Base Path
145
+ const handleConnect = async () => {
146
+ // Pass getUser directly to wallet - it returns fresh tokens from session
147
+ const provider = await connectWallet({ getUser });
148
+
149
+ // Use the provider for blockchain interactions
150
+ const accounts = await provider.request({ method: "eth_requestAccounts" });
151
+ console.log("Connected:", accounts);
152
+ };
210
153
 
211
- Use when you have a non-standard Auth.js API route path:
154
+ if (!isAuthenticated) {
155
+ return <p>Please log in first</p>;
156
+ }
212
157
 
213
- ```tsx
214
- // Use case: Custom API route path (e.g., per-environment routes)
215
- <ImmutableAuthProvider
216
- config={config}
217
- basePath="/api/auth/sandbox" // Instead of default "/api/auth"
218
- >
219
- {children}
220
- </ImmutableAuthProvider>
158
+ return <button onClick={handleConnect}>Connect Wallet</button>;
159
+ }
221
160
  ```
222
161
 
223
- #### Props
224
-
225
- | Prop | Type | Required | Description |
226
- |------|------|----------|-------------|
227
- | `config` | `object` | Yes | Authentication configuration |
228
- | `config.clientId` | `string` | Yes | Immutable application client ID |
229
- | `config.redirectUri` | `string` | Yes | OAuth redirect URI (must match Immutable Hub config) |
230
- | `config.popupRedirectUri` | `string` | No | Separate redirect URI for popup login flows |
231
- | `config.logoutRedirectUri` | `string` | No | Where to redirect after logout |
232
- | `config.audience` | `string` | No | OAuth audience (default: `"platform_api"`) |
233
- | `config.scope` | `string` | No | OAuth scopes (default includes `transact` for blockchain) |
234
- | `config.authenticationDomain` | `string` | No | Immutable auth domain URL |
235
- | `config.passportDomain` | `string` | No | Immutable Passport domain URL |
236
- | `session` | `Session` | No | Server-side session for SSR hydration (prevents auth flash) |
237
- | `basePath` | `string` | No | Auth.js API base path (default: `"/api/auth"`) |
162
+ ## Components
238
163
 
239
164
  ### `CallbackPage`
240
165
 
241
- **Use case:** Handles the OAuth callback after Immutable authentication. This component processes the authorization code from the URL and establishes the session.
242
-
243
- **When to use:**
244
- - Required for redirect-based login flows (when user is redirected to Immutable login page)
245
- - Create a page at your `redirectUri` path (e.g., `/callback`)
246
-
247
- **How it works:**
248
- 1. User clicks "Sign In" → redirected to Immutable login
249
- 2. After login, Immutable redirects to your callback URL with auth code
250
- 3. `CallbackPage` exchanges the code for tokens and creates the session
251
- 4. User is redirected to your app (e.g., `/dashboard`)
252
-
253
- ```tsx
254
- // app/callback/page.tsx
255
- // Use case: Basic callback page that redirects to dashboard after login
256
- "use client";
257
-
258
- import { CallbackPage } from "@imtbl/auth-next-client";
259
-
260
- const config = {
261
- clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
262
- redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
263
- };
264
-
265
- export default function Callback() {
266
- return <CallbackPage config={config} redirectTo="/dashboard" />;
267
- }
268
- ```
269
-
270
- #### Dynamic Redirect Based on User
166
+ Handles the OAuth callback after Immutable authentication. This component processes the authorization code from the URL and establishes the session.
271
167
 
272
168
  ```tsx
273
169
  // app/callback/page.tsx
274
- // Use case: Redirect new users to onboarding, existing users to dashboard
275
170
  "use client";
276
171
 
277
172
  import { CallbackPage } from "@imtbl/auth-next-client";
@@ -279,17 +174,15 @@ import { CallbackPage } from "@imtbl/auth-next-client";
279
174
  export default function Callback() {
280
175
  return (
281
176
  <CallbackPage
282
- config={config}
283
- redirectTo={(user) => {
284
- // Redirect based on user properties
285
- return user.email ? "/dashboard" : "/onboarding";
177
+ config={{
178
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
179
+ redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
286
180
  }}
287
- onSuccess={async (user) => {
288
- // Track successful login
289
- await analytics.track("user_logged_in", { userId: user.sub });
181
+ redirectTo="/dashboard"
182
+ onSuccess={(user) => {
183
+ console.log("User logged in:", user.sub);
290
184
  }}
291
185
  onError={(error) => {
292
- // Log authentication failures
293
186
  console.error("Login failed:", error);
294
187
  }}
295
188
  />
@@ -297,143 +190,43 @@ export default function Callback() {
297
190
  }
298
191
  ```
299
192
 
300
- #### Custom Loading and Error UI
301
-
302
- ```tsx
303
- // app/callback/page.tsx
304
- // Use case: Branded callback page with custom loading and error states
305
- "use client";
306
-
307
- import { CallbackPage } from "@imtbl/auth-next-client";
308
- import { Spinner, ErrorCard } from "@/components/ui";
309
-
310
- export default function Callback() {
311
- return (
312
- <CallbackPage
313
- config={config}
314
- redirectTo="/dashboard"
315
- loadingComponent={
316
- <div className="flex flex-col items-center justify-center min-h-screen">
317
- <Spinner size="lg" />
318
- <p className="mt-4 text-gray-600">Completing sign in...</p>
319
- </div>
320
- }
321
- errorComponent={(error) => (
322
- <div className="flex items-center justify-center min-h-screen">
323
- <ErrorCard
324
- title="Authentication Failed"
325
- message={error}
326
- action={{ label: "Try Again", href: "/login" }}
327
- />
328
- </div>
329
- )}
330
- />
331
- );
332
- }
333
- ```
334
-
335
193
  #### Props
336
194
 
337
195
  | Prop | Type | Required | Description |
338
196
  |------|------|----------|-------------|
339
- | `config` | `object` | Yes | Authentication configuration (same as provider) |
197
+ | `config` | `CallbackConfig` | Yes | Authentication configuration |
198
+ | `config.clientId` | `string` | Yes | Immutable application client ID |
199
+ | `config.redirectUri` | `string` | Yes | OAuth redirect URI |
340
200
  | `redirectTo` | `string \| ((user) => string)` | No | Redirect destination after login (default: `"/"`) |
341
201
  | `loadingComponent` | `ReactElement` | No | Custom loading component |
342
202
  | `errorComponent` | `(error: string) => ReactElement` | No | Custom error component |
343
203
  | `onSuccess` | `(user) => void \| Promise<void>` | No | Success callback (runs before redirect) |
344
- | `onError` | `(error: string) => void` | No | Error callback (runs before error UI shows) |
204
+ | `onError` | `(error: string) => void` | No | Error callback |
345
205
 
346
206
  ## Hooks
347
207
 
348
- This package provides hooks for different authentication needs:
208
+ ### `useImmutableSession()`
349
209
 
350
- | Hook | Use Case |
351
- |------|----------|
352
- | `useImmutableAuth` | Full auth state and methods (sign in, sign out, get tokens) |
353
- | `useAccessToken` | Just need to make authenticated API calls |
354
- | `useHydratedData` | Display SSR-fetched data with client-side fallback |
355
-
356
- ### `useImmutableAuth()`
357
-
358
- **Use case:** The main hook for authentication. Use this when you need to check auth state, trigger sign in/out, or make authenticated API calls.
359
-
360
- **When to use:**
361
- - Login/logout buttons
362
- - Displaying user information
363
- - Conditionally rendering content based on auth state
364
- - Making authenticated API calls from client components
210
+ A convenience hook that wraps `next-auth/react`'s `useSession` with a `getUser` function for wallet integration.
365
211
 
366
212
  ```tsx
367
- // components/Header.tsx
368
- // Use case: Navigation header with login/logout and user info
369
213
  "use client";
370
214
 
371
- import { useImmutableAuth } from "@imtbl/auth-next-client";
215
+ import { useImmutableSession } from "@imtbl/auth-next-client";
372
216
 
373
- export function Header() {
217
+ function MyComponent() {
374
218
  const {
375
- user, // User profile (sub, email, nickname)
376
- isLoading, // True during initial session fetch
377
- isLoggingIn, // True while popup is open
378
- isAuthenticated,// True when user is logged in
379
- signIn, // Opens login popup
380
- signOut, // Signs out from both Auth.js and Immutable
381
- getAccessToken, // Returns valid token (refreshes if needed)
382
- } = useImmutableAuth();
383
-
384
- if (isLoading) {
385
- return <HeaderSkeleton />;
386
- }
387
-
388
- if (isAuthenticated) {
389
- return (
390
- <header>
391
- <span>Welcome, {user?.email || user?.sub}</span>
392
- <button onClick={() => signOut()}>Sign Out</button>
393
- </header>
394
- );
395
- }
396
-
397
- return (
398
- <header>
399
- <button onClick={() => signIn()} disabled={isLoggingIn}>
400
- {isLoggingIn ? "Signing in..." : "Sign In with Immutable"}
401
- </button>
402
- </header>
403
- );
404
- }
405
- ```
219
+ session, // Session with tokens
220
+ status, // 'loading' | 'authenticated' | 'unauthenticated'
221
+ isLoading, // True during initial load
222
+ isAuthenticated, // True when logged in
223
+ getUser, // Function for wallet integration
224
+ } = useImmutableSession();
406
225
 
407
- #### Making Authenticated API Calls
226
+ if (isLoading) return <div>Loading...</div>;
227
+ if (!isAuthenticated) return <div>Please log in</div>;
408
228
 
409
- ```tsx
410
- // components/InventoryButton.tsx
411
- // Use case: Fetch user data from your API using the access token
412
- "use client";
413
-
414
- import { useImmutableAuth } from "@imtbl/auth-next-client";
415
- import { useState } from "react";
416
-
417
- export function InventoryButton() {
418
- const { getAccessToken, isAuthenticated } = useImmutableAuth();
419
- const [inventory, setInventory] = useState(null);
420
-
421
- const fetchInventory = async () => {
422
- // getAccessToken() automatically refreshes expired tokens
423
- const token = await getAccessToken();
424
- const response = await fetch("/api/user/inventory", {
425
- headers: { Authorization: `Bearer ${token}` },
426
- });
427
- setInventory(await response.json());
428
- };
429
-
430
- if (!isAuthenticated) return null;
431
-
432
- return (
433
- <button onClick={fetchInventory}>
434
- Load My Inventory
435
- </button>
436
- );
229
+ return <div>Welcome, {session?.user?.email}</div>;
437
230
  }
438
231
  ```
439
232
 
@@ -441,344 +234,165 @@ export function InventoryButton() {
441
234
 
442
235
  | Property | Type | Description |
443
236
  |----------|------|-------------|
444
- | `user` | `ImmutableUserClient \| null` | Current user profile |
445
- | `session` | `Session \| null` | Full Auth.js session with tokens |
237
+ | `session` | `ImmutableSession \| null` | Session with access/refresh tokens |
238
+ | `status` | `string` | Auth status: `'loading'`, `'authenticated'`, `'unauthenticated'` |
446
239
  | `isLoading` | `boolean` | Whether initial auth state is loading |
447
- | `isLoggingIn` | `boolean` | Whether a login flow is in progress |
448
240
  | `isAuthenticated` | `boolean` | Whether user is authenticated |
449
- | `signIn` | `(options?) => Promise<void>` | Start sign-in flow (opens popup) |
450
- | `signOut` | `() => Promise<void>` | Sign out from both Auth.js and Immutable |
451
- | `getAccessToken` | `() => Promise<string>` | Get valid access token (refreshes if needed) |
452
- | `auth` | `Auth \| null` | Underlying `@imtbl/auth` instance for advanced use |
453
-
454
- #### Sign-In Options
455
-
456
- ```tsx
457
- signIn({
458
- useCachedSession: true, // Try to use cached session first
459
- // Additional options from @imtbl/auth LoginOptions
460
- });
461
- ```
241
+ | `getUser` | `(forceRefresh?: boolean) => Promise<User \| null>` | Get user function for wallet integration |
462
242
 
463
- ### `useAccessToken()`
243
+ #### The `getUser` Function
464
244
 
465
- **Use case:** A simpler hook when you only need to make authenticated API calls and don't need the full auth state.
466
-
467
- **When to use:**
468
- - Components that only make API calls (no login UI)
469
- - When you want to keep the component focused on its domain logic
470
- - Utility hooks or functions that need to fetch authenticated data
245
+ The `getUser` function returns fresh tokens from the session. It accepts an optional `forceRefresh` parameter:
471
246
 
472
247
  ```tsx
473
- // hooks/useUserAssets.ts
474
- // Use case: Custom hook that fetches user's NFT assets
475
- "use client";
248
+ // Normal usage - returns current session data
249
+ const user = await getUser();
476
250
 
477
- import { useAccessToken } from "@imtbl/auth-next-client";
478
- import { useState, useEffect } from "react";
479
-
480
- export function useUserAssets() {
481
- const getAccessToken = useAccessToken();
482
- const [assets, setAssets] = useState([]);
483
- const [loading, setLoading] = useState(true);
484
-
485
- useEffect(() => {
486
- async function fetchAssets() {
487
- try {
488
- const token = await getAccessToken();
489
- const response = await fetch("/api/assets", {
490
- headers: { Authorization: `Bearer ${token}` },
491
- });
492
- setAssets(await response.json());
493
- } finally {
494
- setLoading(false);
495
- }
496
- }
497
- fetchAssets();
498
- }, [getAccessToken]);
499
-
500
- return { assets, loading };
501
- }
251
+ // Force refresh - triggers server-side token refresh to get updated claims
252
+ // Use this after operations that update user data (e.g., zkEVM registration)
253
+ const freshUser = await getUser(true);
502
254
  ```
503
255
 
504
- ### `useHydratedData(props, fetcher)`
256
+ When `forceRefresh` is `true`:
257
+ 1. Triggers the NextAuth `jwt` callback with `trigger='update'`
258
+ 2. Server performs a token refresh with the identity provider
259
+ 3. Updated claims (like `zkEvm` data after registration) are extracted from the new ID token
260
+ 4. Returns the refreshed user data
505
261
 
506
- **Use case:** Display data that was fetched server-side (SSR), with automatic client-side fallback when SSR was skipped (e.g., token was expired).
262
+ ## Login Functions
507
263
 
508
- **When to use:**
509
- - Client Components that receive data from `getAuthenticatedData` (server-side)
510
- - Pages that benefit from SSR but need client fallback for token refresh
511
- - When you want seamless SSR → CSR transitions without flash of loading states
264
+ This package re-exports the standalone login functions from `@imtbl/auth`:
512
265
 
513
- **When NOT to use:**
514
- - Components that only fetch client-side (use `useImmutableAuth().getAccessToken()` instead)
515
- - Components that don't receive server-fetched props
266
+ ### `loginWithPopup(config)`
516
267
 
517
- **How it works:**
518
- 1. Server uses `getAuthenticatedData` to fetch data (if token valid) or skip (if expired)
519
- 2. Server passes result (`{ data, ssr, session }`) to Client Component
520
- 3. Client uses `useHydratedData` to either use SSR data immediately OR fetch client-side
268
+ Opens a popup window for authentication and returns tokens.
521
269
 
522
270
  ```tsx
523
- // app/profile/page.tsx (Server Component)
524
- // Use case: Profile page with SSR data fetching
525
- import { auth } from "@/lib/auth";
526
- import { getAuthenticatedData } from "@imtbl/auth-next-server";
527
- import { ProfileClient } from "./ProfileClient";
528
-
529
- export default async function ProfilePage() {
530
- // Server fetches data if token is valid, skips if expired
531
- const result = await getAuthenticatedData(auth, async (token) => {
532
- return fetchProfile(token);
271
+ import { loginWithPopup } from "@imtbl/auth-next-client";
272
+ import { signIn } from "next-auth/react";
273
+
274
+ async function handlePopupLogin() {
275
+ const tokens = await loginWithPopup({
276
+ clientId: "your-client-id",
277
+ redirectUri: `${window.location.origin}/callback`,
533
278
  });
534
279
 
535
- // Pass the result to the Client Component
536
- return <ProfileClient {...result} />;
280
+ await signIn("immutable", {
281
+ tokens: JSON.stringify(tokens),
282
+ redirect: false,
283
+ });
537
284
  }
538
285
  ```
539
286
 
540
- ```tsx
541
- // app/profile/ProfileClient.tsx (Client Component)
542
- // Use case: Display profile with SSR data or client-side fallback
543
- "use client";
544
-
545
- import { useHydratedData, type AuthPropsWithData } from "@imtbl/auth-next-client";
287
+ ### `loginWithRedirect(config)`
546
288
 
547
- interface Profile {
548
- name: string;
549
- email: string;
550
- avatarUrl: string;
551
- }
552
-
553
- export function ProfileClient(props: AuthPropsWithData<Profile>) {
554
- // useHydratedData handles both cases:
555
- // - If ssr=true: uses data immediately (no loading state)
556
- // - If ssr=false: refreshes token and fetches client-side
557
- const { data, isLoading, error, refetch } = useHydratedData(
558
- props,
559
- async (token) => fetchProfile(token) // Same fetcher as server
560
- );
289
+ Redirects the page to the authentication provider. Use `CallbackPage` on the callback page.
561
290
 
562
- // Only shows loading state when client-side fetch is happening
563
- if (isLoading) return <ProfileSkeleton />;
564
- if (error) return <div>Error: {error.message}</div>;
565
- if (!data) return <div>No profile found</div>;
291
+ ```tsx
292
+ import { loginWithRedirect } from "@imtbl/auth-next-client";
566
293
 
567
- return (
568
- <div>
569
- <img src={data.avatarUrl} alt={data.name} />
570
- <h1>{data.name}</h1>
571
- <p>{data.email}</p>
572
- <button onClick={refetch}>Refresh</button>
573
- </div>
574
- );
294
+ function handleRedirectLogin() {
295
+ loginWithRedirect({
296
+ clientId: "your-client-id",
297
+ redirectUri: `${window.location.origin}/callback`,
298
+ });
575
299
  }
576
300
  ```
577
301
 
578
- #### The SSR/CSR Flow Explained
579
-
580
- | Scenario | Server | Client | User Experience |
581
- |----------|--------|--------|-----------------|
582
- | Token valid | Fetches data, `ssr: true` | Uses data immediately | Instant content (SSR) |
583
- | Token expired | Skips fetch, `ssr: false` | Refreshes token, fetches | Brief loading, then content |
584
- | Server fetch fails | Returns `fetchError` | Retries automatically | Brief loading, then content |
585
-
586
- #### Return Value
587
-
588
- | Property | Type | Description |
589
- |----------|------|-------------|
590
- | `data` | `T \| null` | The fetched data |
591
- | `isLoading` | `boolean` | Whether data is being fetched |
592
- | `error` | `Error \| null` | Fetch error if any |
593
- | `refetch` | `() => Promise<void>` | Function to refetch data |
594
-
595
- ## Choosing the Right Data Fetching Pattern
596
-
597
- | Pattern | Server Fetches | When to Use |
598
- |---------|---------------|-------------|
599
- | `getAuthProps` + `getAccessToken()` | No | Client-only fetching (infinite scroll, real-time, full control) |
600
- | `getAuthenticatedData` + `useHydratedData` | Yes | SSR with client fallback (best performance + reliability) |
601
- | Client-only with `getAccessToken()` | No | Simple components, non-critical data |
602
-
603
- ### Decision Guide
302
+ ### `handleLoginCallback(config)`
604
303
 
605
- **Use SSR pattern (`getAuthenticatedData` + `useHydratedData`) when:**
606
- - Page benefits from fast initial load (user profile, settings, inventory)
607
- - SEO matters (public profile pages with auth-dependent content)
608
- - You want the best user experience (no loading flash for authenticated users)
304
+ Handles the OAuth callback (used internally by `CallbackPage`).
609
305
 
610
- **Use client-only pattern (`getAccessToken()`) when:**
611
- - Data changes frequently (real-time updates, notifications)
612
- - Infinite scroll or pagination
613
- - Non-critical secondary data (recommendations, suggestions)
614
- - Simple components where SSR complexity isn't worth it
615
-
616
- ## Types
617
-
618
- ### User Types
619
-
620
- ```typescript
621
- interface ImmutableUserClient {
622
- sub: string; // Immutable user ID
623
- email?: string;
624
- nickname?: string;
625
- }
626
-
627
- interface ZkEvmInfo {
628
- ethAddress: string;
629
- userAdminAddress: string;
630
- }
631
- ```
632
-
633
- ### Props Types
634
-
635
- ```typescript
636
- // From server for passing to client components
637
- interface AuthProps {
638
- session: Session | null;
639
- ssr: boolean;
640
- authError?: string;
641
- }
306
+ ```tsx
307
+ import { handleLoginCallback } from "@imtbl/auth-next-client";
642
308
 
643
- interface AuthPropsWithData<T> extends AuthProps {
644
- data: T | null;
645
- fetchError?: string;
646
- }
309
+ const tokens = await handleLoginCallback({
310
+ clientId: "your-client-id",
311
+ redirectUri: `${window.location.origin}/callback`,
312
+ });
647
313
  ```
648
314
 
649
- ### Re-exported Types
315
+ ## Types
650
316
 
651
- For convenience, common types are re-exported from `@imtbl/auth-next-server`:
317
+ ### Session Type
652
318
 
653
319
  ```typescript
654
- import type {
655
- ImmutableAuthConfig,
656
- ImmutableTokenData,
657
- ImmutableUser,
658
- AuthProps,
659
- AuthPropsWithData,
660
- ProtectedAuthProps,
661
- ProtectedAuthPropsWithData,
662
- } from "@imtbl/auth-next-client";
663
- ```
664
-
665
- ## Advanced Usage
666
-
667
- ### Multiple Environments
668
-
669
- Support multiple Immutable environments (dev, sandbox, production):
670
-
671
- ```tsx
672
- // lib/auth-config.ts
673
- export function getAuthConfig(env: "dev" | "sandbox" | "production") {
674
- const configs = {
675
- dev: {
676
- clientId: "dev-client-id",
677
- authenticationDomain: "https://auth.dev.immutable.com",
678
- },
679
- sandbox: {
680
- clientId: "sandbox-client-id",
681
- authenticationDomain: "https://auth.immutable.com",
682
- },
683
- production: {
684
- clientId: "prod-client-id",
685
- authenticationDomain: "https://auth.immutable.com",
686
- },
320
+ interface ImmutableSession {
321
+ accessToken: string;
322
+ refreshToken?: string;
323
+ idToken?: string;
324
+ accessTokenExpires: number;
325
+ zkEvm?: {
326
+ ethAddress: string;
327
+ userAdminAddress: string;
687
328
  };
688
-
689
- return {
690
- ...configs[env],
691
- redirectUri: `${window.location.origin}/callback`,
329
+ error?: string;
330
+ user: {
331
+ sub: string;
332
+ email?: string;
333
+ nickname?: string;
692
334
  };
693
335
  }
694
336
  ```
695
337
 
696
- ```tsx
697
- // app/providers.tsx
698
- "use client";
699
-
700
- import { ImmutableAuthProvider } from "@imtbl/auth-next-client";
701
- import { getAuthConfig } from "@/lib/auth-config";
702
-
703
- export function Providers({ children, environment }: {
704
- children: React.ReactNode;
705
- environment: "dev" | "sandbox" | "production";
706
- }) {
707
- const config = getAuthConfig(environment);
708
- const basePath = `/api/auth/${environment}`;
709
-
710
- return (
711
- <ImmutableAuthProvider config={config} basePath={basePath}>
712
- {children}
713
- </ImmutableAuthProvider>
714
- );
715
- }
716
- ```
717
-
718
- ### Accessing the Auth Instance
719
-
720
- For advanced use cases, you can access the underlying `@imtbl/auth` instance:
721
-
722
- ```tsx
723
- import { useImmutableAuth } from "@imtbl/auth-next-client";
724
-
725
- function AdvancedComponent() {
726
- const { auth } = useImmutableAuth();
338
+ ### LoginConfig
727
339
 
728
- const handleAdvanced = async () => {
729
- if (auth) {
730
- // Direct access to @imtbl/auth methods
731
- const user = await auth.getUser();
732
- const idToken = await auth.getIdToken();
733
- }
734
- };
340
+ ```typescript
341
+ interface LoginConfig {
342
+ clientId: string;
343
+ redirectUri: string;
344
+ popupRedirectUri?: string;
345
+ audience?: string;
346
+ scope?: string;
347
+ authenticationDomain?: string;
735
348
  }
736
349
  ```
737
350
 
738
- ### Token Refresh Events
351
+ ### TokenResponse
739
352
 
740
- The provider automatically handles token refresh events and syncs them to the Auth.js session. You can observe these by watching the session:
741
-
742
- ```tsx
743
- import { useImmutableAuth } from "@imtbl/auth-next-client";
744
-
745
- function TokenMonitor() {
746
- const { session } = useImmutableAuth();
747
-
748
- useEffect(() => {
749
- if (session?.accessToken) {
750
- console.log("Token updated:", session.accessTokenExpires);
751
- }
752
- }, [session?.accessToken]);
353
+ ```typescript
354
+ interface TokenResponse {
355
+ accessToken: string;
356
+ refreshToken?: string;
357
+ idToken?: string;
358
+ accessTokenExpires: number;
359
+ profile: { sub: string; email?: string; nickname?: string };
360
+ zkEvm?: { ethAddress: string; userAdminAddress: string };
753
361
  }
754
362
  ```
755
363
 
756
364
  ## Error Handling
757
365
 
758
- The `session.error` field indicates authentication issues:
366
+ The session may contain an `error` field indicating authentication issues:
759
367
 
760
368
  | Error | Description | Handling |
761
369
  |-------|-------------|----------|
762
- | `"TokenExpired"` | Access token expired | `getAccessToken()` will auto-refresh |
370
+ | `"TokenExpired"` | Access token expired | Server-side refresh will happen automatically |
763
371
  | `"RefreshTokenError"` | Refresh token invalid | Prompt user to sign in again |
764
372
 
765
373
  ```tsx
766
- import { useImmutableAuth } from "@imtbl/auth-next-client";
374
+ import { useImmutableSession } from "@imtbl/auth-next-client";
375
+ import { signIn, signOut } from "next-auth/react";
767
376
 
768
377
  function ProtectedContent() {
769
- const { session, signIn, isAuthenticated } = useImmutableAuth();
378
+ const { session, isAuthenticated } = useImmutableSession();
770
379
 
771
380
  if (session?.error === "RefreshTokenError") {
772
381
  return (
773
382
  <div>
774
383
  <p>Your session has expired. Please sign in again.</p>
775
- <button onClick={() => signIn()}>Sign In</button>
384
+ <button onClick={() => signOut()}>Sign Out</button>
776
385
  </div>
777
386
  );
778
387
  }
779
388
 
780
389
  if (!isAuthenticated) {
781
- return <div>Please sign in to continue.</div>;
390
+ return (
391
+ <div>
392
+ <p>Please sign in to continue.</p>
393
+ <button onClick={() => signIn()}>Sign In</button>
394
+ </div>
395
+ );
782
396
  }
783
397
 
784
398
  return <div>Protected content here</div>;
@@ -788,7 +402,8 @@ function ProtectedContent() {
788
402
  ## Related Packages
789
403
 
790
404
  - [`@imtbl/auth-next-server`](../auth-next-server) - Server-side authentication utilities
791
- - [`@imtbl/auth`](../auth) - Core authentication library
405
+ - [`@imtbl/auth`](../auth) - Core authentication library with standalone login functions
406
+ - [`@imtbl/wallet`](../wallet) - Wallet connection with `getUser` support
792
407
 
793
408
  ## License
794
409