@imtbl/auth-nextjs 0.0.1-alpha.0 → 2.12.4-alpha.11

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
@@ -1,38 +1,58 @@
1
1
  # @imtbl/auth-nextjs
2
2
 
3
- Next.js authentication integration for Immutable SDK using NextAuth.js.
3
+ Next.js App Router authentication integration for Immutable SDK using Auth.js v5.
4
4
 
5
- This package bridges `@imtbl/auth` popup-based authentication with NextAuth.js session management, providing:
5
+ This package bridges `@imtbl/auth` popup-based authentication with Auth.js session management, providing:
6
6
 
7
7
  - Server-side session storage in encrypted JWT cookies
8
8
  - Automatic token refresh on both server and client
9
- - Full SSR support with `getServerSession`
9
+ - Full SSR support with `auth()` function
10
10
  - React hooks for easy client-side authentication
11
+ - Middleware support for protecting routes
12
+
13
+ ## Requirements
14
+
15
+ - Next.js 14+ with App Router
16
+ - Auth.js v5 (next-auth@5.x)
17
+ - React 18+
11
18
 
12
19
  ## Installation
13
20
 
14
21
  ```bash
15
- pnpm add @imtbl/auth-nextjs next-auth
22
+ pnpm add @imtbl/auth-nextjs next-auth@beta
16
23
  ```
17
24
 
18
25
  ## Quick Start
19
26
 
20
- ### 1. Set Up Auth API Route
27
+ ### 1. Create Auth Configuration
21
28
 
22
29
  ```typescript
23
- // pages/api/auth/[...nextauth].ts
24
- import { ImmutableAuth } from "@imtbl/auth-nextjs";
30
+ // lib/auth.ts
31
+ import { createImmutableAuth } from "@imtbl/auth-nextjs";
25
32
 
26
- export default ImmutableAuth({
33
+ const config = {
27
34
  clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
28
35
  redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
29
- });
36
+ };
37
+
38
+ export const { handlers, auth, signIn, signOut } = createImmutableAuth(config);
39
+ ```
40
+
41
+ ### 2. Set Up Auth API Route
42
+
43
+ ```typescript
44
+ // app/api/auth/[...nextauth]/route.ts
45
+ import { handlers } from "@/lib/auth";
46
+
47
+ export const { GET, POST } = handlers;
30
48
  ```
31
49
 
32
- ### 2. Create Callback Page
50
+ ### 3. Create Callback Page
33
51
 
34
52
  ```typescript
35
- // pages/callback.tsx
53
+ // app/callback/page.tsx
54
+ "use client";
55
+
36
56
  import { CallbackPage } from "@imtbl/auth-nextjs/client";
37
57
 
38
58
  const config = {
@@ -41,14 +61,18 @@ const config = {
41
61
  };
42
62
 
43
63
  export default function Callback() {
44
- return <CallbackPage config={config} />;
64
+ return <CallbackPage config={config} redirectTo="/dashboard" />;
45
65
  }
46
66
  ```
47
67
 
48
- ### 3. Add Provider to App
68
+ See [CallbackPage Props](#callbackpage-props) for all available options.
69
+
70
+ ### 4. Add Provider to Layout
49
71
 
50
72
  ```typescript
51
- // pages/_app.tsx
73
+ // app/providers.tsx
74
+ "use client";
75
+
52
76
  import { ImmutableAuthProvider } from "@imtbl/auth-nextjs/client";
53
77
 
54
78
  const config = {
@@ -56,21 +80,39 @@ const config = {
56
80
  redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
57
81
  };
58
82
 
59
- export default function App({ Component, pageProps }: AppProps) {
83
+ export function Providers({ children }: { children: React.ReactNode }) {
60
84
  return (
61
- <ImmutableAuthProvider config={config} session={pageProps.session}>
62
- <Component {...pageProps} />
63
- </ImmutableAuthProvider>
85
+ <ImmutableAuthProvider config={config}>{children}</ImmutableAuthProvider>
86
+ );
87
+ }
88
+
89
+ // app/layout.tsx
90
+ import { Providers } from "./providers";
91
+
92
+ export default function RootLayout({
93
+ children,
94
+ }: {
95
+ children: React.ReactNode;
96
+ }) {
97
+ return (
98
+ <html>
99
+ <body>
100
+ <Providers>{children}</Providers>
101
+ </body>
102
+ </html>
64
103
  );
65
104
  }
66
105
  ```
67
106
 
68
- ### 4. Use in Components
107
+ ### 5. Use in Components
69
108
 
70
109
  ```typescript
110
+ // app/components/LoginButton.tsx
111
+ "use client";
112
+
71
113
  import { useImmutableAuth } from "@imtbl/auth-nextjs/client";
72
114
 
73
- function LoginButton() {
115
+ export function LoginButton() {
74
116
  const { user, isLoading, signIn, signOut } = useImmutableAuth();
75
117
 
76
118
  if (isLoading) return <div>Loading...</div>;
@@ -84,66 +126,65 @@ function LoginButton() {
84
126
  );
85
127
  }
86
128
 
87
- return <button onClick={signIn}>Login with Immutable</button>;
129
+ return <button onClick={() => signIn()}>Login with Immutable</button>;
88
130
  }
89
131
  ```
90
132
 
91
- ### 5. Access Session Server-Side (SSR)
133
+ ### 6. Access Session in Server Components
92
134
 
93
135
  ```typescript
94
- // pages/profile.tsx
95
- import { getImmutableSession } from "@imtbl/auth-nextjs/server";
96
- import type { GetServerSideProps } from "next";
136
+ // app/profile/page.tsx
137
+ import { auth } from "@/lib/auth";
138
+ import { redirect } from "next/navigation";
97
139
 
98
- const config = {
99
- clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
100
- redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
101
- };
140
+ export default async function ProfilePage() {
141
+ const session = await auth();
102
142
 
103
- export default function ProfilePage({ user }) {
104
- if (!user) return <p>Not logged in</p>;
105
- return <h1>Welcome, {user.email}</h1>;
106
- }
143
+ if (!session) {
144
+ redirect("/login");
145
+ }
107
146
 
108
- export const getServerSideProps: GetServerSideProps = async (ctx) => {
109
- const session = await getImmutableSession(ctx.req, ctx.res, config);
110
- return { props: { user: session?.user ?? null } };
111
- };
147
+ return <h1>Welcome, {session.user.email}</h1>;
148
+ }
112
149
  ```
113
150
 
114
- ### 6. Protect Pages (Optional)
151
+ ### 7. Protect Routes with Middleware (Optional)
115
152
 
116
153
  ```typescript
117
- // pages/dashboard.tsx
118
- import { withPageAuthRequired } from "@imtbl/auth-nextjs/server";
119
-
120
- const config = {
121
- clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
122
- redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
123
- };
124
-
125
- function DashboardPage() {
126
- return <h1>Dashboard (protected)</h1>;
127
- }
154
+ // middleware.ts
155
+ import { createAuthMiddleware } from "@imtbl/auth-nextjs/server";
156
+ import { auth } from "@/lib/auth";
128
157
 
129
- export default DashboardPage;
158
+ export default createAuthMiddleware(auth, {
159
+ loginUrl: "/login",
160
+ });
130
161
 
131
- // Redirects to /login if not authenticated
132
- export const getServerSideProps = withPageAuthRequired(config);
162
+ export const config = {
163
+ matcher: ["/dashboard/:path*", "/profile/:path*"],
164
+ };
133
165
  ```
134
166
 
135
167
  ## Configuration Options
136
168
 
137
169
  The `ImmutableAuthConfig` object accepts the following properties:
138
170
 
139
- | Property | Type | Required | Default | Description |
140
- | ---------------------- | -------- | -------- | ------------------------------------------------ | ------------------------------ |
141
- | `clientId` | `string` | Yes | - | Immutable OAuth client ID |
142
- | `redirectUri` | `string` | Yes | - | OAuth callback redirect URI |
143
- | `logoutRedirectUri` | `string` | No | - | Where to redirect after logout |
144
- | `audience` | `string` | No | `"platform_api"` | OAuth audience |
145
- | `scope` | `string` | No | `"openid profile email offline_access transact"` | OAuth scopes |
146
- | `authenticationDomain` | `string` | No | `"https://auth.immutable.com"` | Authentication domain |
171
+ | Property | Type | Required | Default | Description |
172
+ | ---------------------- | -------- | -------- | ------------------------------------------------ | -------------------------------------------------------------- |
173
+ | `clientId` | `string` | Yes | - | Immutable OAuth client ID |
174
+ | `redirectUri` | `string` | Yes | - | OAuth callback redirect URI (for redirect flow) |
175
+ | `popupRedirectUri` | `string` | No | `redirectUri` | OAuth callback redirect URI for popup flow |
176
+ | `logoutRedirectUri` | `string` | No | - | Where to redirect after logout |
177
+ | `audience` | `string` | No | `"platform_api"` | OAuth audience |
178
+ | `scope` | `string` | No | `"openid profile email offline_access transact"` | OAuth scopes |
179
+ | `authenticationDomain` | `string` | No | `"https://auth.immutable.com"` | Authentication domain |
180
+ | `passportDomain` | `string` | No | `"https://passport.immutable.com"` | Passport domain for transaction confirmations (see note below) |
181
+
182
+ > **Important:** The `passportDomain` must match your target environment for transaction signing to work correctly:
183
+ >
184
+ > - **Production:** `https://passport.immutable.com` (default)
185
+ > - **Sandbox:** `https://passport.sandbox.immutable.com`
186
+ >
187
+ > If you're using the sandbox environment, you must explicitly set `passportDomain` to the sandbox URL.
147
188
 
148
189
  ## Environment Variables
149
190
 
@@ -152,9 +193,8 @@ The `ImmutableAuthConfig` object accepts the following properties:
152
193
  NEXT_PUBLIC_IMMUTABLE_CLIENT_ID=your-client-id
153
194
  NEXT_PUBLIC_BASE_URL=http://localhost:3000
154
195
 
155
- # Required by NextAuth for cookie encryption
156
- NEXTAUTH_SECRET=generate-with-openssl-rand-base64-32
157
- NEXTAUTH_URL=http://localhost:3000
196
+ # Required by Auth.js for cookie encryption
197
+ AUTH_SECRET=generate-with-openssl-rand-base64-32
158
198
  ```
159
199
 
160
200
  Generate a secret:
@@ -163,103 +203,243 @@ Generate a secret:
163
203
  openssl rand -base64 32
164
204
  ```
165
205
 
206
+ ## Sandbox vs Production Configuration
207
+
208
+ When developing or testing, you'll typically use the **Sandbox** environment. Make sure to configure `passportDomain` correctly:
209
+
210
+ ```typescript
211
+ // lib/auth.ts
212
+
213
+ // For SANDBOX environment
214
+ const sandboxConfig = {
215
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
216
+ redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
217
+ passportDomain: "https://passport.sandbox.immutable.com", // Required for sandbox!
218
+ };
219
+
220
+ // For PRODUCTION environment
221
+ const productionConfig = {
222
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
223
+ redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
224
+ // passportDomain defaults to 'https://passport.immutable.com'
225
+ };
226
+
227
+ // Use environment variable to switch between configs
228
+ const config =
229
+ process.env.NEXT_PUBLIC_IMMUTABLE_ENV === "production"
230
+ ? productionConfig
231
+ : sandboxConfig;
232
+
233
+ export const { handlers, auth, signIn, signOut } = createImmutableAuth(config);
234
+ ```
235
+
236
+ > **Note:** The `passportDomain` is used for transaction confirmation popups. If not set correctly for your environment, transaction signing will not work as expected.
237
+
166
238
  ## API Reference
167
239
 
168
240
  ### Main Exports (`@imtbl/auth-nextjs`)
169
241
 
170
- | Export | Description |
171
- | ----------------------------------- | ------------------------------------------- |
172
- | `ImmutableAuth(config, overrides?)` | Creates NextAuth handler (use in API route) |
173
- | `refreshAccessToken(token)` | Utility to refresh an expired access token |
174
- | `isTokenExpired(token)` | Utility to check if a token is expired |
242
+ | Export | Description |
243
+ | --------------------------------------- | ------------------------------------------------------------------- |
244
+ | `createImmutableAuth(config, options?)` | Creates Auth.js instance with `{ handlers, auth, signIn, signOut }` |
245
+ | `createAuthConfig(config)` | Creates Auth.js config (for advanced use) |
246
+ | `refreshAccessToken(token, config)` | Utility to refresh an expired access token |
247
+ | `isTokenExpired(expires, buffer?)` | Utility to check if a token is expired |
175
248
 
176
249
  **Types:**
177
250
 
178
- | Type | Description |
179
- | ----------------------------- | ----------------------------------------- |
180
- | `ImmutableAuthConfig` | Configuration options |
181
- | `ImmutableAuthOverrides` | NextAuth options override type |
182
- | `ImmutableUser` | User profile type |
183
- | `ImmutableTokenData` | Token data passed to credentials provider |
184
- | `ZkEvmInfo` | zkEVM wallet information type |
185
- | `WithPageAuthRequiredOptions` | Options for page protection |
251
+ | Type | Description |
252
+ | ------------------------ | ----------------------------------------- |
253
+ | `ImmutableAuthConfig` | Configuration options |
254
+ | `ImmutableAuthOverrides` | Auth.js options override type |
255
+ | `ImmutableAuthResult` | Return type of createImmutableAuth |
256
+ | `ImmutableUser` | User profile type |
257
+ | `ImmutableTokenData` | Token data passed to credentials provider |
258
+ | `ZkEvmInfo` | zkEVM wallet information type |
186
259
 
187
260
  ### Client Exports (`@imtbl/auth-nextjs/client`)
188
261
 
189
- | Export | Description |
190
- | ----------------------- | ------------------------------------------------------- |
191
- | `ImmutableAuthProvider` | React context provider (wraps NextAuth SessionProvider) |
192
- | `useImmutableAuth()` | Hook for authentication state and methods (see below) |
193
- | `useAccessToken()` | Hook returning `getAccessToken` function |
194
- | `CallbackPage` | Pre-built callback page component for OAuth redirects |
262
+ | Export | Description |
263
+ | ----------------------- | ------------------------------------------------------ |
264
+ | `ImmutableAuthProvider` | React context provider (wraps Auth.js SessionProvider) |
265
+ | `useImmutableAuth()` | Hook for authentication state and methods (see below) |
266
+ | `useAccessToken()` | Hook returning `getAccessToken` function |
267
+ | `CallbackPage` | Pre-built callback page component for OAuth redirects |
268
+
269
+ #### CallbackPage Props
270
+
271
+ | Prop | Type | Default | Description |
272
+ | ------------------ | ----------------------------------------------------- | ------- | ------------------------------------------------------------------ |
273
+ | `config` | `ImmutableAuthConfig` | - | Required. Immutable auth configuration |
274
+ | `redirectTo` | `string \| ((user: ImmutableUser) => string \| void)` | `"/"` | Where to redirect after successful auth (supports dynamic routing) |
275
+ | `loadingComponent` | `React.ReactElement \| null` | `null` | Custom loading UI while processing authentication |
276
+ | `errorComponent` | `(error: string) => React.ReactElement \| null` | - | Custom error UI component |
277
+ | `onSuccess` | `(user: ImmutableUser) => void \| Promise<void>` | - | Callback fired after successful authentication |
278
+ | `onError` | `(error: string) => void` | - | Callback fired when authentication fails |
279
+
280
+ **Example with all props:**
281
+
282
+ ```tsx
283
+ // app/callback/page.tsx
284
+ "use client";
285
+
286
+ import { CallbackPage } from "@imtbl/auth-nextjs/client";
287
+ import { Spinner } from "@/components/ui/spinner";
288
+
289
+ const config = {
290
+ clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
291
+ redirectUri: `${process.env.NEXT_PUBLIC_BASE_URL}/callback`,
292
+ };
293
+
294
+ export default function Callback() {
295
+ return (
296
+ <CallbackPage
297
+ config={config}
298
+ // Dynamic redirect based on user
299
+ redirectTo={(user) => {
300
+ if (user.email?.endsWith("@admin.com")) return "/admin";
301
+ return "/dashboard";
302
+ }}
303
+ // Custom loading UI
304
+ loadingComponent={
305
+ <div className="flex items-center justify-center min-h-screen">
306
+ <Spinner />
307
+ <span>Completing authentication...</span>
308
+ </div>
309
+ }
310
+ // Custom error UI
311
+ errorComponent={(error) => (
312
+ <div className="text-center p-8">
313
+ <h2 className="text-red-500">Authentication Error</h2>
314
+ <p>{error}</p>
315
+ <a href="/">Return Home</a>
316
+ </div>
317
+ )}
318
+ // Success callback for analytics
319
+ onSuccess={async (user) => {
320
+ await analytics.track("login_success", { userId: user.sub });
321
+ }}
322
+ // Error callback for logging
323
+ onError={(error) => {
324
+ console.error("Auth failed:", error);
325
+ Sentry.captureMessage(error);
326
+ }}
327
+ />
328
+ );
329
+ }
330
+ ```
195
331
 
196
332
  **`useImmutableAuth()` Return Value:**
197
333
 
198
334
  | Property | Type | Description |
199
335
  | ----------------- | ----------------------- | ------------------------------------------------ |
200
336
  | `user` | `ImmutableUser \| null` | Current user profile (null if not authenticated) |
201
- | `session` | `Session \| null` | Full NextAuth session with tokens |
337
+ | `session` | `Session \| null` | Full Auth.js session with tokens |
202
338
  | `isLoading` | `boolean` | Whether authentication state is loading |
203
339
  | `isAuthenticated` | `boolean` | Whether user is authenticated |
204
- | `signIn` | `() => Promise<void>` | Sign in with Immutable (opens popup) |
205
- | `signOut` | `() => Promise<void>` | Sign out from both NextAuth and Immutable |
340
+ | `signIn` | `(options?) => Promise` | Sign in with Immutable (opens popup) |
341
+ | `signOut` | `() => Promise<void>` | Sign out from both Auth.js and Immutable |
206
342
  | `getAccessToken` | `() => Promise<string>` | Get a valid access token (refreshes if needed) |
207
343
  | `auth` | `Auth \| null` | The underlying Auth instance (for advanced use) |
208
344
 
209
- **Types:**
345
+ ### Server Exports (`@imtbl/auth-nextjs/server`)
210
346
 
211
- | Type | Description |
212
- | ---------------------------- | -------------------------------- |
213
- | `ImmutableAuthProviderProps` | Props for the provider component |
214
- | `UseImmutableAuthReturn` | Return type of useImmutableAuth |
215
- | `CallbackPageProps` | Props for CallbackPage component |
216
- | `ImmutableAuthConfig` | Re-exported configuration type |
217
- | `ImmutableUser` | Re-exported user type |
347
+ | Export | Description |
348
+ | ----------------------------------- | ------------------------------------------------ |
349
+ | `createImmutableAuth` | Re-exported for convenience |
350
+ | `createAuthMiddleware(auth, opts?)` | Create middleware for protecting routes |
351
+ | `withAuth(auth, handler)` | HOC for protecting Server Actions/Route Handlers |
218
352
 
219
- ### Server Exports (`@imtbl/auth-nextjs/server`)
353
+ **`createAuthMiddleware` Options:**
220
354
 
221
- | Export | Description |
222
- | ---------------------------------------- | ---------------------------------------- |
223
- | `getImmutableSession(req, res, config)` | Get session server-side |
224
- | `withPageAuthRequired(config, options?)` | HOC for protecting pages with auth check |
355
+ | Option | Type | Default | Description |
356
+ | ---------------- | ---------------------- | ---------- | -------------------------------------- |
357
+ | `loginUrl` | `string` | `"/login"` | URL to redirect when not authenticated |
358
+ | `protectedPaths` | `(string \| RegExp)[]` | - | Paths that require authentication |
359
+ | `publicPaths` | `(string \| RegExp)[]` | - | Paths to exclude from protection |
225
360
 
226
- **`withPageAuthRequired` Options:**
361
+ ## How It Works
227
362
 
228
- | Option | Type | Default | Description |
229
- | -------------------- | ----------------------- | ------------ | ---------------------------------------------------- |
230
- | `loginUrl` | `string` | `"/login"` | URL to redirect to when not authenticated |
231
- | `returnTo` | `string \| false` | current page | URL to redirect to after login (`false` to disable) |
232
- | `getServerSideProps` | `(ctx, session) => ...` | - | Custom getServerSideProps that runs after auth check |
363
+ 1. **Login**: User clicks login → `@imtbl/auth` opens popup → tokens returned
364
+ 2. **Session Creation**: Tokens passed to Auth.js credentials provider → stored in encrypted JWT cookie
365
+ 3. **Token Refresh**: Auth.js JWT callback automatically refreshes expired tokens using refresh_token
366
+ 4. **SSR**: `auth()` reads and decrypts cookie, providing full session with tokens
367
+ 5. **Auto-sync**: Tokens are automatically synced between server (NextAuth session) and client (Auth instance) to handle refresh token rotation
233
368
 
234
- **Example with custom getServerSideProps:**
369
+ ## Handling Token Refresh Errors
370
+
371
+ When a refresh token expires or becomes invalid (e.g., after 365 days of inactivity, or revoked from another session), the session will contain an `error` property. You should handle this gracefully:
372
+
373
+ ### Server Components
235
374
 
236
375
  ```typescript
237
- export const getServerSideProps = withPageAuthRequired(config, {
238
- loginUrl: "/auth/signin",
239
- async getServerSideProps(ctx, session) {
240
- // session is guaranteed to exist here
241
- const data = await fetchData(session.accessToken);
242
- return { props: { data } };
243
- },
244
- });
376
+ // app/profile/page.tsx
377
+ import { auth } from "@/lib/auth";
378
+ import { redirect } from "next/navigation";
379
+
380
+ export default async function ProfilePage() {
381
+ const session = await auth();
382
+
383
+ // Handle refresh token failure - prompt user to re-login
384
+ if (session?.error === "RefreshTokenError") {
385
+ redirect("/login?error=session_expired");
386
+ }
387
+
388
+ if (!session) {
389
+ redirect("/login");
390
+ }
391
+
392
+ return <h1>Welcome, {session.user.email}</h1>;
393
+ }
245
394
  ```
246
395
 
247
- **Types:**
396
+ ### Client Components
248
397
 
249
- | Type | Description |
250
- | --------------------------------- | ----------------------------------------- |
251
- | `WithPageAuthRequiredOptions` | Basic options for page protection |
252
- | `WithPageAuthRequiredFullOptions` | Full options including getServerSideProps |
253
- | `WithPageAuthRequiredProps` | Props added to protected pages (session) |
398
+ ```typescript
399
+ "use client";
254
400
 
255
- ## How It Works
401
+ import { useImmutableAuth } from "@imtbl/auth-nextjs/client";
256
402
 
257
- 1. **Login**: User clicks login → `@imtbl/auth` opens popup → tokens returned
258
- 2. **Session Creation**: Tokens passed to NextAuth's credentials provider → stored in encrypted JWT cookie
259
- 3. **Token Refresh**: NextAuth JWT callback automatically refreshes expired tokens using refresh_token
260
- 4. **SSR**: `getServerSession()` reads and decrypts cookie, providing full session with tokens
261
- 5. **Auto-hydration**: If localStorage is cleared but session cookie exists, the Auth instance is automatically hydrated from session tokens
403
+ export function ProtectedContent() {
404
+ const { session, user, signIn, isLoading } = useImmutableAuth();
262
405
 
263
- ## License
406
+ if (isLoading) return <div>Loading...</div>;
264
407
 
265
- Apache-2.0
408
+ // Handle refresh token failure
409
+ if (session?.error) {
410
+ return (
411
+ <div>
412
+ <p>Your session has expired. Please log in again.</p>
413
+ <button onClick={() => signIn()}>Log In</button>
414
+ </div>
415
+ );
416
+ }
417
+
418
+ if (!user) {
419
+ return <button onClick={() => signIn()}>Log In</button>;
420
+ }
421
+
422
+ return <div>Welcome, {user.email}</div>;
423
+ }
424
+ ```
425
+
426
+ ### Using getAccessToken
427
+
428
+ The `getAccessToken()` function will throw an error if the token cannot be refreshed:
429
+
430
+ ```typescript
431
+ const { getAccessToken } = useImmutableAuth();
432
+
433
+ async function fetchData() {
434
+ try {
435
+ const token = await getAccessToken();
436
+ const response = await fetch("/api/data", {
437
+ headers: { Authorization: `Bearer ${token}` },
438
+ });
439
+ return response.json();
440
+ } catch (error) {
441
+ // Token refresh failed - redirect to login or show error
442
+ console.error("Failed to get access token:", error);
443
+ }
444
+ }
445
+ ```