@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 +176 -561
- package/dist/node/index.cjs +103 -354
- package/dist/node/index.js +103 -366
- package/dist/types/callback.d.ts +28 -7
- package/dist/types/hooks.d.ts +70 -0
- package/dist/types/index.d.ts +19 -5
- package/dist/types/types.d.ts +1 -73
- package/package.json +4 -4
- package/src/callback.tsx +77 -76
- package/src/hooks.tsx +162 -0
- package/src/index.ts +30 -14
- package/src/types.ts +1 -75
- package/dist/types/provider.d.ts +0 -66
- package/src/provider.tsx +0 -547
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
|
|
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
|
-
- `
|
|
14
|
-
-
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
145
|
-
// Use case: Basic provider setup
|
|
104
|
+
// components/LoginButton.tsx
|
|
146
105
|
"use client";
|
|
147
106
|
|
|
148
|
-
import {
|
|
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: `${
|
|
112
|
+
redirectUri: `${window.location.origin}/callback`,
|
|
153
113
|
};
|
|
154
114
|
|
|
155
|
-
export function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
131
|
+
### 6. Connect Wallet with getUser
|
|
165
132
|
|
|
166
|
-
|
|
133
|
+
Use `useImmutableSession` for wallet integration:
|
|
167
134
|
|
|
168
135
|
```tsx
|
|
169
|
-
//
|
|
170
|
-
// Use case: SSR hydration to prevent auth state flash
|
|
136
|
+
// components/WalletConnect.tsx
|
|
171
137
|
"use client";
|
|
172
138
|
|
|
173
|
-
import {
|
|
174
|
-
import
|
|
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
|
|
198
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
154
|
+
if (!isAuthenticated) {
|
|
155
|
+
return <p>Please log in first</p>;
|
|
156
|
+
}
|
|
212
157
|
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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={
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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` | `
|
|
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
|
|
204
|
+
| `onError` | `(error: string) => void` | No | Error callback |
|
|
345
205
|
|
|
346
206
|
## Hooks
|
|
347
207
|
|
|
348
|
-
|
|
208
|
+
### `useImmutableSession()`
|
|
349
209
|
|
|
350
|
-
|
|
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 {
|
|
215
|
+
import { useImmutableSession } from "@imtbl/auth-next-client";
|
|
372
216
|
|
|
373
|
-
|
|
217
|
+
function MyComponent() {
|
|
374
218
|
const {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
isAuthenticated
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
226
|
+
if (isLoading) return <div>Loading...</div>;
|
|
227
|
+
if (!isAuthenticated) return <div>Please log in</div>;
|
|
408
228
|
|
|
409
|
-
|
|
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
|
-
| `
|
|
445
|
-
| `
|
|
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
|
-
| `
|
|
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
|
-
|
|
243
|
+
#### The `getUser` Function
|
|
464
244
|
|
|
465
|
-
|
|
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
|
-
//
|
|
474
|
-
|
|
475
|
-
"use client";
|
|
248
|
+
// Normal usage - returns current session data
|
|
249
|
+
const user = await getUser();
|
|
476
250
|
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262
|
+
## Login Functions
|
|
507
263
|
|
|
508
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
536
|
-
|
|
280
|
+
await signIn("immutable", {
|
|
281
|
+
tokens: JSON.stringify(tokens),
|
|
282
|
+
redirect: false,
|
|
283
|
+
});
|
|
537
284
|
}
|
|
538
285
|
```
|
|
539
286
|
|
|
540
|
-
|
|
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
|
-
|
|
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
|
-
|
|
563
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
611
|
-
|
|
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
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
309
|
+
const tokens = await handleLoginCallback({
|
|
310
|
+
clientId: "your-client-id",
|
|
311
|
+
redirectUri: `${window.location.origin}/callback`,
|
|
312
|
+
});
|
|
647
313
|
```
|
|
648
314
|
|
|
649
|
-
|
|
315
|
+
## Types
|
|
650
316
|
|
|
651
|
-
|
|
317
|
+
### Session Type
|
|
652
318
|
|
|
653
319
|
```typescript
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
690
|
-
|
|
691
|
-
|
|
329
|
+
error?: string;
|
|
330
|
+
user: {
|
|
331
|
+
sub: string;
|
|
332
|
+
email?: string;
|
|
333
|
+
nickname?: string;
|
|
692
334
|
};
|
|
693
335
|
}
|
|
694
336
|
```
|
|
695
337
|
|
|
696
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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
|
-
###
|
|
351
|
+
### TokenResponse
|
|
739
352
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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 `
|
|
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 |
|
|
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 {
|
|
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,
|
|
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={() =>
|
|
384
|
+
<button onClick={() => signOut()}>Sign Out</button>
|
|
776
385
|
</div>
|
|
777
386
|
);
|
|
778
387
|
}
|
|
779
388
|
|
|
780
389
|
if (!isAuthenticated) {
|
|
781
|
-
return
|
|
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
|
|