@imtbl/auth-next-client 2.12.7-alpha.9 → 2.12.7
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 +40 -0
- package/dist/node/index.cjs +67 -7
- package/dist/node/index.js +59 -7
- package/dist/types/constants.d.ts +8 -30
- package/dist/types/defaultConfig.d.ts +8 -0
- package/dist/types/hooks.d.ts +85 -21
- package/dist/types/index.d.ts +2 -0
- package/package.json +5 -5
- package/src/constants.ts +8 -37
- package/src/defaultConfig.ts +19 -0
- package/src/hooks.test.tsx +4 -0
- package/src/hooks.tsx +144 -30
- package/src/index.ts +12 -0
package/README.md
CHANGED
|
@@ -27,6 +27,10 @@ npm install @imtbl/auth-next-client @imtbl/auth-next-server next-auth@5
|
|
|
27
27
|
- `next` >= 14.0.0
|
|
28
28
|
- `next-auth` >= 5.0.0-beta.25
|
|
29
29
|
|
|
30
|
+
### Next.js 14 Compatibility
|
|
31
|
+
|
|
32
|
+
This package is compatible with both Next.js 14 and 15. It uses only standard APIs available in both versions (`next/navigation` for `useRouter`, `next-auth/react`). No Next.js 15-only APIs are used.
|
|
33
|
+
|
|
30
34
|
## Quick Start
|
|
31
35
|
|
|
32
36
|
### 1. Set Up Server-Side Auth
|
|
@@ -100,6 +104,42 @@ export default function Callback() {
|
|
|
100
104
|
}
|
|
101
105
|
```
|
|
102
106
|
|
|
107
|
+
### Default Auth (Zero Config)
|
|
108
|
+
|
|
109
|
+
When using `createAuthConfig()` with no args on the server, you can call login/logout with no config—sandbox clientId and redirectUri are used:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// With default auth - no config needed
|
|
113
|
+
function LoginButton() {
|
|
114
|
+
const { isAuthenticated } = useImmutableSession();
|
|
115
|
+
const { loginWithPopup, isLoggingIn, error } = useLogin();
|
|
116
|
+
|
|
117
|
+
if (isAuthenticated) return <p>You are logged in!</p>;
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<button onClick={() => loginWithPopup()} disabled={isLoggingIn}>
|
|
121
|
+
{isLoggingIn ? "Signing in..." : "Sign In"}
|
|
122
|
+
</button>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Or with custom config (pass full LoginConfig/LogoutConfig when overriding):
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
// With custom config - pass complete config
|
|
131
|
+
loginWithPopup({
|
|
132
|
+
clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
|
|
133
|
+
redirectUri: `${window.location.origin}/callback`,
|
|
134
|
+
});
|
|
135
|
+
logout({
|
|
136
|
+
clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID!,
|
|
137
|
+
logoutRedirectUri: process.env.NEXT_PUBLIC_BASE_URL!,
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
See the [wallets-connect-with-nextjs](../../examples/passport/wallets-connect-with-nextjs) example for a full integration with `@imtbl/wallet`.
|
|
142
|
+
|
|
103
143
|
### 5. Add Login Button
|
|
104
144
|
|
|
105
145
|
Use the `useLogin` hook for login flows with built-in state management:
|
package/dist/node/index.cjs
CHANGED
|
@@ -22,7 +22,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
var src_exports = {};
|
|
23
23
|
__export(src_exports, {
|
|
24
24
|
CallbackPage: () => CallbackPage,
|
|
25
|
+
DEFAULT_AUDIENCE: () => DEFAULT_AUDIENCE,
|
|
26
|
+
DEFAULT_AUTH_DOMAIN: () => DEFAULT_AUTH_DOMAIN,
|
|
27
|
+
DEFAULT_LOGOUT_REDIRECT_URI_PATH: () => DEFAULT_LOGOUT_REDIRECT_URI_PATH,
|
|
28
|
+
DEFAULT_REDIRECT_URI_PATH: () => DEFAULT_REDIRECT_URI_PATH,
|
|
29
|
+
DEFAULT_SANDBOX_CLIENT_ID: () => DEFAULT_SANDBOX_CLIENT_ID,
|
|
30
|
+
DEFAULT_SCOPE: () => DEFAULT_SCOPE,
|
|
31
|
+
IMMUTABLE_PROVIDER_ID: () => IMMUTABLE_PROVIDER_ID,
|
|
25
32
|
MarketingConsentStatus: () => import_auth3.MarketingConsentStatus,
|
|
33
|
+
deriveDefaultRedirectUri: () => deriveDefaultRedirectUri,
|
|
26
34
|
useImmutableSession: () => useImmutableSession,
|
|
27
35
|
useLogin: () => useLogin,
|
|
28
36
|
useLogout: () => useLogout
|
|
@@ -36,10 +44,14 @@ var import_react2 = require("next-auth/react");
|
|
|
36
44
|
var import_auth = require("@imtbl/auth");
|
|
37
45
|
|
|
38
46
|
// src/constants.ts
|
|
47
|
+
var DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
|
|
48
|
+
var DEFAULT_AUDIENCE = "platform_api";
|
|
49
|
+
var DEFAULT_SCOPE = "openid profile email offline_access transact";
|
|
39
50
|
var IMMUTABLE_PROVIDER_ID = "immutable";
|
|
40
|
-
var
|
|
41
|
-
var
|
|
42
|
-
var
|
|
51
|
+
var DEFAULT_SANDBOX_CLIENT_ID = "mjtCL8mt06BtbxSkp2vbrYStKWnXVZfo";
|
|
52
|
+
var DEFAULT_REDIRECT_URI_PATH = "/callback";
|
|
53
|
+
var DEFAULT_LOGOUT_REDIRECT_URI_PATH = "/";
|
|
54
|
+
var TOKEN_EXPIRY_BUFFER_MS = 6e4;
|
|
43
55
|
|
|
44
56
|
// src/idTokenStorage.ts
|
|
45
57
|
var ID_TOKEN_STORAGE_KEY = "imtbl_id_token";
|
|
@@ -213,6 +225,18 @@ function CallbackPage({
|
|
|
213
225
|
var import_react3 = require("react");
|
|
214
226
|
var import_react4 = require("next-auth/react");
|
|
215
227
|
var import_auth2 = require("@imtbl/auth");
|
|
228
|
+
|
|
229
|
+
// src/defaultConfig.ts
|
|
230
|
+
function deriveDefaultRedirectUri() {
|
|
231
|
+
if (typeof window === "undefined") {
|
|
232
|
+
throw new Error(
|
|
233
|
+
"[auth-next-client] deriveDefaultRedirectUri requires window. Login hooks run in the browser when the user triggers login."
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return `${window.location.origin}${DEFAULT_REDIRECT_URI_PATH}`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/hooks.tsx
|
|
216
240
|
var pendingRefresh = null;
|
|
217
241
|
function deduplicatedUpdate(update) {
|
|
218
242
|
if (!pendingRefresh) {
|
|
@@ -222,6 +246,30 @@ function deduplicatedUpdate(update) {
|
|
|
222
246
|
}
|
|
223
247
|
return pendingRefresh;
|
|
224
248
|
}
|
|
249
|
+
function getSandboxLoginConfig() {
|
|
250
|
+
const redirectUri = deriveDefaultRedirectUri();
|
|
251
|
+
return {
|
|
252
|
+
clientId: DEFAULT_SANDBOX_CLIENT_ID,
|
|
253
|
+
redirectUri,
|
|
254
|
+
popupRedirectUri: redirectUri,
|
|
255
|
+
scope: DEFAULT_SCOPE,
|
|
256
|
+
audience: DEFAULT_AUDIENCE,
|
|
257
|
+
authenticationDomain: DEFAULT_AUTH_DOMAIN
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function getSandboxLogoutConfig() {
|
|
261
|
+
if (typeof window === "undefined") {
|
|
262
|
+
throw new Error(
|
|
263
|
+
"[auth-next-client] getSandboxLogoutConfig requires window. Logout runs in the browser when the user triggers it."
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
const logoutRedirectUri = window.location.origin + DEFAULT_LOGOUT_REDIRECT_URI_PATH;
|
|
267
|
+
return {
|
|
268
|
+
clientId: DEFAULT_SANDBOX_CLIENT_ID,
|
|
269
|
+
logoutRedirectUri,
|
|
270
|
+
authenticationDomain: DEFAULT_AUTH_DOMAIN
|
|
271
|
+
};
|
|
272
|
+
}
|
|
225
273
|
function useImmutableSession() {
|
|
226
274
|
const { data: sessionData, status, update } = (0, import_react4.useSession)();
|
|
227
275
|
const [isRefreshing, setIsRefreshing] = (0, import_react3.useState)(false);
|
|
@@ -353,7 +401,8 @@ function useLogin() {
|
|
|
353
401
|
setIsLoggingIn(true);
|
|
354
402
|
setError(null);
|
|
355
403
|
try {
|
|
356
|
-
const
|
|
404
|
+
const fullConfig = config ?? getSandboxLoginConfig();
|
|
405
|
+
const tokens = await (0, import_auth2.loginWithPopup)(fullConfig, options);
|
|
357
406
|
await signInWithTokens(tokens);
|
|
358
407
|
} catch (err) {
|
|
359
408
|
const errorMessage = err instanceof Error ? err.message : "Login failed";
|
|
@@ -367,7 +416,8 @@ function useLogin() {
|
|
|
367
416
|
setIsLoggingIn(true);
|
|
368
417
|
setError(null);
|
|
369
418
|
try {
|
|
370
|
-
const
|
|
419
|
+
const fullConfig = config ?? getSandboxLoginConfig();
|
|
420
|
+
const tokens = await (0, import_auth2.loginWithEmbedded)(fullConfig);
|
|
371
421
|
await signInWithTokens(tokens);
|
|
372
422
|
} catch (err) {
|
|
373
423
|
const errorMessage = err instanceof Error ? err.message : "Login failed";
|
|
@@ -381,7 +431,8 @@ function useLogin() {
|
|
|
381
431
|
setIsLoggingIn(true);
|
|
382
432
|
setError(null);
|
|
383
433
|
try {
|
|
384
|
-
|
|
434
|
+
const fullConfig = config ?? getSandboxLoginConfig();
|
|
435
|
+
await (0, import_auth2.loginWithRedirect)(fullConfig, options);
|
|
385
436
|
} catch (err) {
|
|
386
437
|
const errorMessage = err instanceof Error ? err.message : "Login failed";
|
|
387
438
|
setError(errorMessage);
|
|
@@ -406,7 +457,8 @@ function useLogout() {
|
|
|
406
457
|
try {
|
|
407
458
|
clearStoredIdToken();
|
|
408
459
|
await (0, import_react4.signOut)({ redirect: false });
|
|
409
|
-
|
|
460
|
+
const fullConfig = config ?? getSandboxLogoutConfig();
|
|
461
|
+
(0, import_auth2.logoutWithRedirect)(fullConfig);
|
|
410
462
|
} catch (err) {
|
|
411
463
|
const errorMessage = err instanceof Error ? err.message : "Logout failed";
|
|
412
464
|
setError(errorMessage);
|
|
@@ -426,7 +478,15 @@ var import_auth3 = require("@imtbl/auth");
|
|
|
426
478
|
// Annotate the CommonJS export names for ESM import in node:
|
|
427
479
|
0 && (module.exports = {
|
|
428
480
|
CallbackPage,
|
|
481
|
+
DEFAULT_AUDIENCE,
|
|
482
|
+
DEFAULT_AUTH_DOMAIN,
|
|
483
|
+
DEFAULT_LOGOUT_REDIRECT_URI_PATH,
|
|
484
|
+
DEFAULT_REDIRECT_URI_PATH,
|
|
485
|
+
DEFAULT_SANDBOX_CLIENT_ID,
|
|
486
|
+
DEFAULT_SCOPE,
|
|
487
|
+
IMMUTABLE_PROVIDER_ID,
|
|
429
488
|
MarketingConsentStatus,
|
|
489
|
+
deriveDefaultRedirectUri,
|
|
430
490
|
useImmutableSession,
|
|
431
491
|
useLogin,
|
|
432
492
|
useLogout
|
package/dist/node/index.js
CHANGED
|
@@ -7,10 +7,14 @@ import { signIn } from "next-auth/react";
|
|
|
7
7
|
import { handleLoginCallback as handleAuthCallback } from "@imtbl/auth";
|
|
8
8
|
|
|
9
9
|
// src/constants.ts
|
|
10
|
+
var DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
|
|
11
|
+
var DEFAULT_AUDIENCE = "platform_api";
|
|
12
|
+
var DEFAULT_SCOPE = "openid profile email offline_access transact";
|
|
10
13
|
var IMMUTABLE_PROVIDER_ID = "immutable";
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
+
var DEFAULT_SANDBOX_CLIENT_ID = "mjtCL8mt06BtbxSkp2vbrYStKWnXVZfo";
|
|
15
|
+
var DEFAULT_REDIRECT_URI_PATH = "/callback";
|
|
16
|
+
var DEFAULT_LOGOUT_REDIRECT_URI_PATH = "/";
|
|
17
|
+
var TOKEN_EXPIRY_BUFFER_MS = 6e4;
|
|
14
18
|
|
|
15
19
|
// src/idTokenStorage.ts
|
|
16
20
|
var ID_TOKEN_STORAGE_KEY = "imtbl_id_token";
|
|
@@ -194,6 +198,18 @@ import {
|
|
|
194
198
|
loginWithRedirect as rawLoginWithRedirect,
|
|
195
199
|
logoutWithRedirect as rawLogoutWithRedirect
|
|
196
200
|
} from "@imtbl/auth";
|
|
201
|
+
|
|
202
|
+
// src/defaultConfig.ts
|
|
203
|
+
function deriveDefaultRedirectUri() {
|
|
204
|
+
if (typeof window === "undefined") {
|
|
205
|
+
throw new Error(
|
|
206
|
+
"[auth-next-client] deriveDefaultRedirectUri requires window. Login hooks run in the browser when the user triggers login."
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return `${window.location.origin}${DEFAULT_REDIRECT_URI_PATH}`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/hooks.tsx
|
|
197
213
|
var pendingRefresh = null;
|
|
198
214
|
function deduplicatedUpdate(update) {
|
|
199
215
|
if (!pendingRefresh) {
|
|
@@ -203,6 +219,30 @@ function deduplicatedUpdate(update) {
|
|
|
203
219
|
}
|
|
204
220
|
return pendingRefresh;
|
|
205
221
|
}
|
|
222
|
+
function getSandboxLoginConfig() {
|
|
223
|
+
const redirectUri = deriveDefaultRedirectUri();
|
|
224
|
+
return {
|
|
225
|
+
clientId: DEFAULT_SANDBOX_CLIENT_ID,
|
|
226
|
+
redirectUri,
|
|
227
|
+
popupRedirectUri: redirectUri,
|
|
228
|
+
scope: DEFAULT_SCOPE,
|
|
229
|
+
audience: DEFAULT_AUDIENCE,
|
|
230
|
+
authenticationDomain: DEFAULT_AUTH_DOMAIN
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function getSandboxLogoutConfig() {
|
|
234
|
+
if (typeof window === "undefined") {
|
|
235
|
+
throw new Error(
|
|
236
|
+
"[auth-next-client] getSandboxLogoutConfig requires window. Logout runs in the browser when the user triggers it."
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const logoutRedirectUri = window.location.origin + DEFAULT_LOGOUT_REDIRECT_URI_PATH;
|
|
240
|
+
return {
|
|
241
|
+
clientId: DEFAULT_SANDBOX_CLIENT_ID,
|
|
242
|
+
logoutRedirectUri,
|
|
243
|
+
authenticationDomain: DEFAULT_AUTH_DOMAIN
|
|
244
|
+
};
|
|
245
|
+
}
|
|
206
246
|
function useImmutableSession() {
|
|
207
247
|
const { data: sessionData, status, update } = useSession();
|
|
208
248
|
const [isRefreshing, setIsRefreshing] = useState2(false);
|
|
@@ -334,7 +374,8 @@ function useLogin() {
|
|
|
334
374
|
setIsLoggingIn(true);
|
|
335
375
|
setError(null);
|
|
336
376
|
try {
|
|
337
|
-
const
|
|
377
|
+
const fullConfig = config ?? getSandboxLoginConfig();
|
|
378
|
+
const tokens = await rawLoginWithPopup(fullConfig, options);
|
|
338
379
|
await signInWithTokens(tokens);
|
|
339
380
|
} catch (err) {
|
|
340
381
|
const errorMessage = err instanceof Error ? err.message : "Login failed";
|
|
@@ -348,7 +389,8 @@ function useLogin() {
|
|
|
348
389
|
setIsLoggingIn(true);
|
|
349
390
|
setError(null);
|
|
350
391
|
try {
|
|
351
|
-
const
|
|
392
|
+
const fullConfig = config ?? getSandboxLoginConfig();
|
|
393
|
+
const tokens = await rawLoginWithEmbedded(fullConfig);
|
|
352
394
|
await signInWithTokens(tokens);
|
|
353
395
|
} catch (err) {
|
|
354
396
|
const errorMessage = err instanceof Error ? err.message : "Login failed";
|
|
@@ -362,7 +404,8 @@ function useLogin() {
|
|
|
362
404
|
setIsLoggingIn(true);
|
|
363
405
|
setError(null);
|
|
364
406
|
try {
|
|
365
|
-
|
|
407
|
+
const fullConfig = config ?? getSandboxLoginConfig();
|
|
408
|
+
await rawLoginWithRedirect(fullConfig, options);
|
|
366
409
|
} catch (err) {
|
|
367
410
|
const errorMessage = err instanceof Error ? err.message : "Login failed";
|
|
368
411
|
setError(errorMessage);
|
|
@@ -387,7 +430,8 @@ function useLogout() {
|
|
|
387
430
|
try {
|
|
388
431
|
clearStoredIdToken();
|
|
389
432
|
await signOut({ redirect: false });
|
|
390
|
-
|
|
433
|
+
const fullConfig = config ?? getSandboxLogoutConfig();
|
|
434
|
+
rawLogoutWithRedirect(fullConfig);
|
|
391
435
|
} catch (err) {
|
|
392
436
|
const errorMessage = err instanceof Error ? err.message : "Logout failed";
|
|
393
437
|
setError(errorMessage);
|
|
@@ -406,7 +450,15 @@ function useLogout() {
|
|
|
406
450
|
import { MarketingConsentStatus } from "@imtbl/auth";
|
|
407
451
|
export {
|
|
408
452
|
CallbackPage,
|
|
453
|
+
DEFAULT_AUDIENCE,
|
|
454
|
+
DEFAULT_AUTH_DOMAIN,
|
|
455
|
+
DEFAULT_LOGOUT_REDIRECT_URI_PATH,
|
|
456
|
+
DEFAULT_REDIRECT_URI_PATH,
|
|
457
|
+
DEFAULT_SANDBOX_CLIENT_ID,
|
|
458
|
+
DEFAULT_SCOPE,
|
|
459
|
+
IMMUTABLE_PROVIDER_ID,
|
|
409
460
|
MarketingConsentStatus,
|
|
461
|
+
deriveDefaultRedirectUri,
|
|
410
462
|
useImmutableSession,
|
|
411
463
|
useLogin,
|
|
412
464
|
useLogout
|
|
@@ -1,37 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Default Immutable authentication domain
|
|
2
|
+
* Client-side constants for @imtbl/auth-next-client.
|
|
3
|
+
* Defined locally to avoid importing from auth-next-server (which uses next/server).
|
|
4
|
+
* Values must stay in sync with auth-next-server constants.
|
|
6
5
|
*/
|
|
7
6
|
export declare const DEFAULT_AUTH_DOMAIN = "https://auth.immutable.com";
|
|
8
|
-
/**
|
|
9
|
-
* Default OAuth audience
|
|
10
|
-
*/
|
|
11
7
|
export declare const DEFAULT_AUDIENCE = "platform_api";
|
|
12
|
-
/**
|
|
13
|
-
* Default OAuth scopes
|
|
14
|
-
*/
|
|
15
8
|
export declare const DEFAULT_SCOPE = "openid profile email offline_access transact";
|
|
16
|
-
/**
|
|
17
|
-
* NextAuth credentials provider ID for Immutable
|
|
18
|
-
*/
|
|
19
9
|
export declare const IMMUTABLE_PROVIDER_ID = "immutable";
|
|
20
|
-
/**
|
|
21
|
-
* Default NextAuth API base path
|
|
22
|
-
*/
|
|
23
10
|
export declare const DEFAULT_NEXTAUTH_BASE_PATH = "/api/auth";
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export declare const
|
|
29
|
-
/**
|
|
30
|
-
* Default token expiry in milliseconds
|
|
31
|
-
*/
|
|
32
|
-
export declare const DEFAULT_TOKEN_EXPIRY_MS: number;
|
|
33
|
-
/**
|
|
34
|
-
* Buffer time in milliseconds before token expiry to trigger refresh.
|
|
35
|
-
* Matches TOKEN_EXPIRY_BUFFER_SECONDS (60s) in @imtbl/auth-next-server.
|
|
36
|
-
*/
|
|
37
|
-
export declare const TOKEN_EXPIRY_BUFFER_MS: number;
|
|
11
|
+
export declare const DEFAULT_SANDBOX_CLIENT_ID = "mjtCL8mt06BtbxSkp2vbrYStKWnXVZfo";
|
|
12
|
+
export declare const DEFAULT_REDIRECT_URI_PATH = "/callback";
|
|
13
|
+
export declare const DEFAULT_LOGOUT_REDIRECT_URI_PATH = "/";
|
|
14
|
+
export declare const DEFAULT_TOKEN_EXPIRY_MS = 900000;
|
|
15
|
+
export declare const TOKEN_EXPIRY_BUFFER_MS = 60000;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox default redirect URI for zero-config mode.
|
|
3
|
+
* Defined locally to avoid importing from auth-next-server (which uses next/server).
|
|
4
|
+
* OAuth requires an absolute URL; this runs in the browser when login is invoked.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare function deriveDefaultRedirectUri(): string;
|
package/dist/types/hooks.d.ts
CHANGED
|
@@ -86,53 +86,87 @@ export interface UseImmutableSessionReturn {
|
|
|
86
86
|
export declare function useImmutableSession(): UseImmutableSessionReturn;
|
|
87
87
|
/**
|
|
88
88
|
* Return type for useLogin hook
|
|
89
|
+
*
|
|
90
|
+
* Config is optional - when omitted, defaults are auto-derived (clientId, redirectUri, etc.).
|
|
91
|
+
* When provided, must be a complete LoginConfig.
|
|
89
92
|
*/
|
|
90
93
|
export interface UseLoginReturn {
|
|
91
94
|
/** Start login with popup flow */
|
|
92
|
-
loginWithPopup: (config
|
|
95
|
+
loginWithPopup: (config?: LoginConfig, options?: StandaloneLoginOptions) => Promise<void>;
|
|
93
96
|
/** Start login with embedded modal flow */
|
|
94
|
-
loginWithEmbedded: (config
|
|
97
|
+
loginWithEmbedded: (config?: LoginConfig) => Promise<void>;
|
|
95
98
|
/** Start login with redirect flow (navigates away from page) */
|
|
96
|
-
loginWithRedirect: (config
|
|
99
|
+
loginWithRedirect: (config?: LoginConfig, options?: StandaloneLoginOptions) => Promise<void>;
|
|
97
100
|
/** Whether login is currently in progress */
|
|
98
101
|
isLoggingIn: boolean;
|
|
99
102
|
/** Error message from the last login attempt, or null if none */
|
|
100
103
|
error: string | null;
|
|
101
104
|
}
|
|
102
105
|
/**
|
|
103
|
-
* Hook to handle Immutable authentication login flows.
|
|
106
|
+
* Hook to handle Immutable authentication login flows with automatic defaults.
|
|
104
107
|
*
|
|
105
108
|
* Provides login functions that:
|
|
106
109
|
* 1. Handle OAuth authentication via popup, embedded modal, or redirect
|
|
107
110
|
* 2. Automatically sign in to NextAuth after successful authentication
|
|
108
111
|
* 3. Track loading and error states
|
|
112
|
+
* 4. Auto-detect clientId and redirectUri if not provided (uses defaults)
|
|
109
113
|
*
|
|
110
|
-
* Config
|
|
111
|
-
*
|
|
114
|
+
* Config can be passed at call time or omitted to use sensible defaults:
|
|
115
|
+
* - `clientId`: Auto-detected based on environment (sandbox vs production)
|
|
116
|
+
* - `redirectUri`: Auto-derived from `window.location.origin + '/callback'`
|
|
117
|
+
* - `popupRedirectUri`: Auto-derived from `window.location.origin + '/callback'` (same as redirectUri)
|
|
118
|
+
* - `logoutRedirectUri`: Auto-derived from `window.location.origin`
|
|
119
|
+
* - `scope`: `'openid profile email offline_access transact'`
|
|
120
|
+
* - `audience`: `'platform_api'`
|
|
121
|
+
* - `authenticationDomain`: `'https://auth.immutable.com'`
|
|
112
122
|
*
|
|
113
123
|
* Must be used within a SessionProvider from next-auth/react.
|
|
114
124
|
*
|
|
115
|
-
* @example
|
|
125
|
+
* @example Minimal usage (uses all defaults)
|
|
116
126
|
* ```tsx
|
|
117
127
|
* import { useLogin, useImmutableSession } from '@imtbl/auth-next-client';
|
|
118
128
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
129
|
+
* function LoginButton() {
|
|
130
|
+
* const { isAuthenticated } = useImmutableSession();
|
|
131
|
+
* const { loginWithPopup, isLoggingIn, error } = useLogin();
|
|
132
|
+
*
|
|
133
|
+
* if (isAuthenticated) {
|
|
134
|
+
* return <p>You are logged in!</p>;
|
|
135
|
+
* }
|
|
136
|
+
*
|
|
137
|
+
* return (
|
|
138
|
+
* <>
|
|
139
|
+
* <button onClick={() => loginWithPopup()} disabled={isLoggingIn}>
|
|
140
|
+
* {isLoggingIn ? 'Signing in...' : 'Sign In'}
|
|
141
|
+
* </button>
|
|
142
|
+
* {error && <p style={{ color: 'red' }}>{error}</p>}
|
|
143
|
+
* </>
|
|
144
|
+
* );
|
|
145
|
+
* }
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* @example With custom configuration
|
|
149
|
+
* ```tsx
|
|
150
|
+
* import { useLogin, useImmutableSession } from '@imtbl/auth-next-client';
|
|
124
151
|
*
|
|
125
152
|
* function LoginButton() {
|
|
126
153
|
* const { isAuthenticated } = useImmutableSession();
|
|
127
154
|
* const { loginWithPopup, isLoggingIn, error } = useLogin();
|
|
128
155
|
*
|
|
156
|
+
* const handleLogin = () => {
|
|
157
|
+
* loginWithPopup({
|
|
158
|
+
* clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID,
|
|
159
|
+
* redirectUri: `${window.location.origin}/callback`,
|
|
160
|
+
* });
|
|
161
|
+
* };
|
|
162
|
+
*
|
|
129
163
|
* if (isAuthenticated) {
|
|
130
164
|
* return <p>You are logged in!</p>;
|
|
131
165
|
* }
|
|
132
166
|
*
|
|
133
167
|
* return (
|
|
134
168
|
* <>
|
|
135
|
-
* <button onClick={
|
|
169
|
+
* <button onClick={handleLogin} disabled={isLoggingIn}>
|
|
136
170
|
* {isLoggingIn ? 'Signing in...' : 'Sign In'}
|
|
137
171
|
* </button>
|
|
138
172
|
* {error && <p style={{ color: 'red' }}>{error}</p>}
|
|
@@ -152,9 +186,11 @@ export interface UseLogoutReturn {
|
|
|
152
186
|
* This ensures that when the user logs in again, they will be prompted to select
|
|
153
187
|
* an account instead of being automatically logged in with the previous account.
|
|
154
188
|
*
|
|
155
|
-
*
|
|
189
|
+
* Config is optional - defaults will be auto-derived if not provided.
|
|
190
|
+
*
|
|
191
|
+
* @param config - Optional logout configuration with clientId and optional redirectUri
|
|
156
192
|
*/
|
|
157
|
-
logout: (config
|
|
193
|
+
logout: (config?: LogoutConfig) => Promise<void>;
|
|
158
194
|
/** Whether logout is currently in progress */
|
|
159
195
|
isLoggingOut: boolean;
|
|
160
196
|
/** Error message from the last logout attempt, or null if none */
|
|
@@ -171,16 +207,38 @@ export interface UseLogoutReturn {
|
|
|
171
207
|
* an account (for social logins like Google) instead of being automatically logged
|
|
172
208
|
* in with the previous account.
|
|
173
209
|
*
|
|
210
|
+
* Config is optional - defaults will be auto-derived if not provided:
|
|
211
|
+
* - `clientId`: Auto-detected based on environment (sandbox vs production)
|
|
212
|
+
* - `logoutRedirectUri`: Auto-derived from `window.location.origin`
|
|
213
|
+
*
|
|
174
214
|
* Must be used within a SessionProvider from next-auth/react.
|
|
175
215
|
*
|
|
176
|
-
* @example
|
|
216
|
+
* @example Minimal usage (uses all defaults)
|
|
177
217
|
* ```tsx
|
|
178
218
|
* import { useLogout, useImmutableSession } from '@imtbl/auth-next-client';
|
|
179
219
|
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
220
|
+
* function LogoutButton() {
|
|
221
|
+
* const { isAuthenticated } = useImmutableSession();
|
|
222
|
+
* const { logout, isLoggingOut, error } = useLogout();
|
|
223
|
+
*
|
|
224
|
+
* if (!isAuthenticated) {
|
|
225
|
+
* return null;
|
|
226
|
+
* }
|
|
227
|
+
*
|
|
228
|
+
* return (
|
|
229
|
+
* <>
|
|
230
|
+
* <button onClick={() => logout()} disabled={isLoggingOut}>
|
|
231
|
+
* {isLoggingOut ? 'Signing out...' : 'Sign Out'}
|
|
232
|
+
* </button>
|
|
233
|
+
* {error && <p style={{ color: 'red' }}>{error}</p>}
|
|
234
|
+
* </>
|
|
235
|
+
* );
|
|
236
|
+
* }
|
|
237
|
+
* ```
|
|
238
|
+
*
|
|
239
|
+
* @example With custom configuration
|
|
240
|
+
* ```tsx
|
|
241
|
+
* import { useLogout, useImmutableSession } from '@imtbl/auth-next-client';
|
|
184
242
|
*
|
|
185
243
|
* function LogoutButton() {
|
|
186
244
|
* const { isAuthenticated } = useImmutableSession();
|
|
@@ -192,7 +250,13 @@ export interface UseLogoutReturn {
|
|
|
192
250
|
*
|
|
193
251
|
* return (
|
|
194
252
|
* <>
|
|
195
|
-
* <button
|
|
253
|
+
* <button
|
|
254
|
+
* onClick={() => logout({
|
|
255
|
+
* clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID,
|
|
256
|
+
* logoutRedirectUri: `${window.location.origin}/custom-logout`,
|
|
257
|
+
* })}
|
|
258
|
+
* disabled={isLoggingOut}
|
|
259
|
+
* >
|
|
196
260
|
* {isLoggingOut ? 'Signing out...' : 'Sign Out'}
|
|
197
261
|
* </button>
|
|
198
262
|
* {error && <p style={{ color: 'red' }}>{error}</p>}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -28,3 +28,5 @@ export type { ImmutableUserClient, ImmutableTokenDataClient, ZkEvmInfo, } from '
|
|
|
28
28
|
export type { ImmutableAuthConfig, ImmutableTokenData, ImmutableUser, AuthProps, AuthPropsWithData, ProtectedAuthProps, ProtectedAuthPropsWithData, } from '@imtbl/auth-next-server';
|
|
29
29
|
export type { LoginConfig, StandaloneLoginOptions, DirectLoginOptions, LogoutConfig, } from '@imtbl/auth';
|
|
30
30
|
export { MarketingConsentStatus } from '@imtbl/auth';
|
|
31
|
+
export { DEFAULT_AUTH_DOMAIN, DEFAULT_AUDIENCE, DEFAULT_SCOPE, IMMUTABLE_PROVIDER_ID, DEFAULT_SANDBOX_CLIENT_ID, DEFAULT_REDIRECT_URI_PATH, DEFAULT_LOGOUT_REDIRECT_URI_PATH, } from './constants';
|
|
32
|
+
export { deriveDefaultRedirectUri } from './defaultConfig';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imtbl/auth-next-client",
|
|
3
|
-
"version": "2.12.7
|
|
3
|
+
"version": "2.12.7",
|
|
4
4
|
"description": "Immutable Auth.js v5 integration for Next.js - Client-side components",
|
|
5
5
|
"author": "Immutable",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@imtbl/auth": "2.12.7
|
|
31
|
-
"@imtbl/auth-next-server": "2.12.7
|
|
30
|
+
"@imtbl/auth": "2.12.7",
|
|
31
|
+
"@imtbl/auth-next-server": "2.12.7"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"next": "^15.0.0",
|
|
34
|
+
"next": "^14.0.0 || ^15.0.0",
|
|
35
35
|
"next-auth": "^5.0.0-beta.25",
|
|
36
36
|
"react": "^18.2.0 || ^19.0.0"
|
|
37
37
|
},
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"@types/react": "^18.3.5",
|
|
57
57
|
"eslint": "^8.56.0",
|
|
58
58
|
"jest": "^29.7.0",
|
|
59
|
-
"next": "^15.
|
|
59
|
+
"next": "^15.2.6",
|
|
60
60
|
"next-auth": "^5.0.0-beta.30",
|
|
61
61
|
"react": "^18.2.0",
|
|
62
62
|
"tsup": "^8.3.0",
|
package/src/constants.ts
CHANGED
|
@@ -1,45 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Client-side constants for @imtbl/auth-next-client.
|
|
3
|
+
* Defined locally to avoid importing from auth-next-server (which uses next/server).
|
|
4
|
+
* Values must stay in sync with auth-next-server constants.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
/**
|
|
6
|
-
* Default Immutable authentication domain
|
|
7
|
-
*/
|
|
8
7
|
export const DEFAULT_AUTH_DOMAIN = 'https://auth.immutable.com';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Default OAuth audience
|
|
12
|
-
*/
|
|
13
8
|
export const DEFAULT_AUDIENCE = 'platform_api';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Default OAuth scopes
|
|
17
|
-
*/
|
|
18
9
|
export const DEFAULT_SCOPE = 'openid profile email offline_access transact';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* NextAuth credentials provider ID for Immutable
|
|
22
|
-
*/
|
|
23
10
|
export const IMMUTABLE_PROVIDER_ID = 'immutable';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Default NextAuth API base path
|
|
27
|
-
*/
|
|
28
11
|
export const DEFAULT_NEXTAUTH_BASE_PATH = '/api/auth';
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
export const DEFAULT_TOKEN_EXPIRY_SECONDS = 900;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Default token expiry in milliseconds
|
|
38
|
-
*/
|
|
39
|
-
export const DEFAULT_TOKEN_EXPIRY_MS = DEFAULT_TOKEN_EXPIRY_SECONDS * 1000;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Buffer time in milliseconds before token expiry to trigger refresh.
|
|
43
|
-
* Matches TOKEN_EXPIRY_BUFFER_SECONDS (60s) in @imtbl/auth-next-server.
|
|
44
|
-
*/
|
|
45
|
-
export const TOKEN_EXPIRY_BUFFER_MS = 60 * 1000;
|
|
12
|
+
export const DEFAULT_SANDBOX_CLIENT_ID = 'mjtCL8mt06BtbxSkp2vbrYStKWnXVZfo';
|
|
13
|
+
export const DEFAULT_REDIRECT_URI_PATH = '/callback';
|
|
14
|
+
export const DEFAULT_LOGOUT_REDIRECT_URI_PATH = '/';
|
|
15
|
+
export const DEFAULT_TOKEN_EXPIRY_MS = 900_000;
|
|
16
|
+
export const TOKEN_EXPIRY_BUFFER_MS = 60_000;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox default redirect URI for zero-config mode.
|
|
3
|
+
* Defined locally to avoid importing from auth-next-server (which uses next/server).
|
|
4
|
+
* OAuth requires an absolute URL; this runs in the browser when login is invoked.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { DEFAULT_REDIRECT_URI_PATH } from './constants';
|
|
10
|
+
|
|
11
|
+
export function deriveDefaultRedirectUri(): string {
|
|
12
|
+
if (typeof window === 'undefined') {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'[auth-next-client] deriveDefaultRedirectUri requires window. '
|
|
15
|
+
+ 'Login hooks run in the browser when the user triggers login.',
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return `${window.location.origin}${DEFAULT_REDIRECT_URI_PATH}`;
|
|
19
|
+
}
|
package/src/hooks.test.tsx
CHANGED
|
@@ -23,6 +23,10 @@ jest.mock('@imtbl/auth', () => ({
|
|
|
23
23
|
logoutWithRedirect: jest.fn(),
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
+
jest.mock('./defaultConfig', () => ({
|
|
27
|
+
deriveDefaultRedirectUri: jest.fn(() => 'http://localhost:3000/callback'),
|
|
28
|
+
}));
|
|
29
|
+
|
|
26
30
|
import { useImmutableSession } from './hooks';
|
|
27
31
|
|
|
28
32
|
// ---------------------------------------------------------------------------
|
package/src/hooks.tsx
CHANGED
|
@@ -18,7 +18,16 @@ import {
|
|
|
18
18
|
loginWithRedirect as rawLoginWithRedirect,
|
|
19
19
|
logoutWithRedirect as rawLogoutWithRedirect,
|
|
20
20
|
} from '@imtbl/auth';
|
|
21
|
-
import {
|
|
21
|
+
import { deriveDefaultRedirectUri } from './defaultConfig';
|
|
22
|
+
import {
|
|
23
|
+
IMMUTABLE_PROVIDER_ID,
|
|
24
|
+
TOKEN_EXPIRY_BUFFER_MS,
|
|
25
|
+
DEFAULT_SANDBOX_CLIENT_ID,
|
|
26
|
+
DEFAULT_LOGOUT_REDIRECT_URI_PATH,
|
|
27
|
+
DEFAULT_AUTH_DOMAIN,
|
|
28
|
+
DEFAULT_SCOPE,
|
|
29
|
+
DEFAULT_AUDIENCE,
|
|
30
|
+
} from './constants';
|
|
22
31
|
import { storeIdToken, getStoredIdToken, clearStoredIdToken } from './idTokenStorage';
|
|
23
32
|
|
|
24
33
|
// ---------------------------------------------------------------------------
|
|
@@ -42,6 +51,37 @@ function deduplicatedUpdate(
|
|
|
42
51
|
return pendingRefresh;
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Sandbox defaults for zero-config (no config or full config - no merge)
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
function getSandboxLoginConfig(): LoginConfig {
|
|
59
|
+
const redirectUri = deriveDefaultRedirectUri();
|
|
60
|
+
return {
|
|
61
|
+
clientId: DEFAULT_SANDBOX_CLIENT_ID,
|
|
62
|
+
redirectUri,
|
|
63
|
+
popupRedirectUri: redirectUri,
|
|
64
|
+
scope: DEFAULT_SCOPE,
|
|
65
|
+
audience: DEFAULT_AUDIENCE,
|
|
66
|
+
authenticationDomain: DEFAULT_AUTH_DOMAIN,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getSandboxLogoutConfig(): LogoutConfig {
|
|
71
|
+
if (typeof window === 'undefined') {
|
|
72
|
+
throw new Error(
|
|
73
|
+
'[auth-next-client] getSandboxLogoutConfig requires window. '
|
|
74
|
+
+ 'Logout runs in the browser when the user triggers it.',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const logoutRedirectUri = window.location.origin + DEFAULT_LOGOUT_REDIRECT_URI_PATH;
|
|
78
|
+
return {
|
|
79
|
+
clientId: DEFAULT_SANDBOX_CLIENT_ID,
|
|
80
|
+
logoutRedirectUri,
|
|
81
|
+
authenticationDomain: DEFAULT_AUTH_DOMAIN,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
45
85
|
/**
|
|
46
86
|
* Internal session type with full token data (not exported).
|
|
47
87
|
* Used internally by the hook for token validation, refresh logic, and getUser/getAccessToken.
|
|
@@ -341,14 +381,17 @@ export function useImmutableSession(): UseImmutableSessionReturn {
|
|
|
341
381
|
|
|
342
382
|
/**
|
|
343
383
|
* Return type for useLogin hook
|
|
384
|
+
*
|
|
385
|
+
* Config is optional - when omitted, defaults are auto-derived (clientId, redirectUri, etc.).
|
|
386
|
+
* When provided, must be a complete LoginConfig.
|
|
344
387
|
*/
|
|
345
388
|
export interface UseLoginReturn {
|
|
346
389
|
/** Start login with popup flow */
|
|
347
|
-
loginWithPopup: (config
|
|
390
|
+
loginWithPopup: (config?: LoginConfig, options?: StandaloneLoginOptions) => Promise<void>;
|
|
348
391
|
/** Start login with embedded modal flow */
|
|
349
|
-
loginWithEmbedded: (config
|
|
392
|
+
loginWithEmbedded: (config?: LoginConfig) => Promise<void>;
|
|
350
393
|
/** Start login with redirect flow (navigates away from page) */
|
|
351
|
-
loginWithRedirect: (config
|
|
394
|
+
loginWithRedirect: (config?: LoginConfig, options?: StandaloneLoginOptions) => Promise<void>;
|
|
352
395
|
/** Whether login is currently in progress */
|
|
353
396
|
isLoggingIn: boolean;
|
|
354
397
|
/** Error message from the last login attempt, or null if none */
|
|
@@ -356,39 +399,70 @@ export interface UseLoginReturn {
|
|
|
356
399
|
}
|
|
357
400
|
|
|
358
401
|
/**
|
|
359
|
-
* Hook to handle Immutable authentication login flows.
|
|
402
|
+
* Hook to handle Immutable authentication login flows with automatic defaults.
|
|
360
403
|
*
|
|
361
404
|
* Provides login functions that:
|
|
362
405
|
* 1. Handle OAuth authentication via popup, embedded modal, or redirect
|
|
363
406
|
* 2. Automatically sign in to NextAuth after successful authentication
|
|
364
407
|
* 3. Track loading and error states
|
|
408
|
+
* 4. Auto-detect clientId and redirectUri if not provided (uses defaults)
|
|
365
409
|
*
|
|
366
|
-
* Config
|
|
367
|
-
*
|
|
410
|
+
* Config can be passed at call time or omitted to use sensible defaults:
|
|
411
|
+
* - `clientId`: Auto-detected based on environment (sandbox vs production)
|
|
412
|
+
* - `redirectUri`: Auto-derived from `window.location.origin + '/callback'`
|
|
413
|
+
* - `popupRedirectUri`: Auto-derived from `window.location.origin + '/callback'` (same as redirectUri)
|
|
414
|
+
* - `logoutRedirectUri`: Auto-derived from `window.location.origin`
|
|
415
|
+
* - `scope`: `'openid profile email offline_access transact'`
|
|
416
|
+
* - `audience`: `'platform_api'`
|
|
417
|
+
* - `authenticationDomain`: `'https://auth.immutable.com'`
|
|
368
418
|
*
|
|
369
419
|
* Must be used within a SessionProvider from next-auth/react.
|
|
370
420
|
*
|
|
371
|
-
* @example
|
|
421
|
+
* @example Minimal usage (uses all defaults)
|
|
372
422
|
* ```tsx
|
|
373
423
|
* import { useLogin, useImmutableSession } from '@imtbl/auth-next-client';
|
|
374
424
|
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
425
|
+
* function LoginButton() {
|
|
426
|
+
* const { isAuthenticated } = useImmutableSession();
|
|
427
|
+
* const { loginWithPopup, isLoggingIn, error } = useLogin();
|
|
428
|
+
*
|
|
429
|
+
* if (isAuthenticated) {
|
|
430
|
+
* return <p>You are logged in!</p>;
|
|
431
|
+
* }
|
|
432
|
+
*
|
|
433
|
+
* return (
|
|
434
|
+
* <>
|
|
435
|
+
* <button onClick={() => loginWithPopup()} disabled={isLoggingIn}>
|
|
436
|
+
* {isLoggingIn ? 'Signing in...' : 'Sign In'}
|
|
437
|
+
* </button>
|
|
438
|
+
* {error && <p style={{ color: 'red' }}>{error}</p>}
|
|
439
|
+
* </>
|
|
440
|
+
* );
|
|
441
|
+
* }
|
|
442
|
+
* ```
|
|
443
|
+
*
|
|
444
|
+
* @example With custom configuration
|
|
445
|
+
* ```tsx
|
|
446
|
+
* import { useLogin, useImmutableSession } from '@imtbl/auth-next-client';
|
|
380
447
|
*
|
|
381
448
|
* function LoginButton() {
|
|
382
449
|
* const { isAuthenticated } = useImmutableSession();
|
|
383
450
|
* const { loginWithPopup, isLoggingIn, error } = useLogin();
|
|
384
451
|
*
|
|
452
|
+
* const handleLogin = () => {
|
|
453
|
+
* loginWithPopup({
|
|
454
|
+
* clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID,
|
|
455
|
+
* redirectUri: `${window.location.origin}/callback`,
|
|
456
|
+
* });
|
|
457
|
+
* };
|
|
458
|
+
*
|
|
385
459
|
* if (isAuthenticated) {
|
|
386
460
|
* return <p>You are logged in!</p>;
|
|
387
461
|
* }
|
|
388
462
|
*
|
|
389
463
|
* return (
|
|
390
464
|
* <>
|
|
391
|
-
* <button onClick={
|
|
465
|
+
* <button onClick={handleLogin} disabled={isLoggingIn}>
|
|
392
466
|
* {isLoggingIn ? 'Signing in...' : 'Sign In'}
|
|
393
467
|
* </button>
|
|
394
468
|
* {error && <p style={{ color: 'red' }}>{error}</p>}
|
|
@@ -434,16 +508,18 @@ export function useLogin(): UseLoginReturn {
|
|
|
434
508
|
/**
|
|
435
509
|
* Login with a popup window.
|
|
436
510
|
* Opens a popup for OAuth authentication, then signs in to NextAuth.
|
|
511
|
+
* Config is optional - defaults will be auto-derived if not provided.
|
|
437
512
|
*/
|
|
438
513
|
const loginWithPopup = useCallback(async (
|
|
439
|
-
config
|
|
514
|
+
config?: LoginConfig,
|
|
440
515
|
options?: StandaloneLoginOptions,
|
|
441
516
|
): Promise<void> => {
|
|
442
517
|
setIsLoggingIn(true);
|
|
443
518
|
setError(null);
|
|
444
519
|
|
|
445
520
|
try {
|
|
446
|
-
const
|
|
521
|
+
const fullConfig = config ?? getSandboxLoginConfig();
|
|
522
|
+
const tokens = await rawLoginWithPopup(fullConfig, options);
|
|
447
523
|
await signInWithTokens(tokens);
|
|
448
524
|
} catch (err) {
|
|
449
525
|
const errorMessage = err instanceof Error ? err.message : 'Login failed';
|
|
@@ -457,13 +533,15 @@ export function useLogin(): UseLoginReturn {
|
|
|
457
533
|
/**
|
|
458
534
|
* Login with an embedded modal.
|
|
459
535
|
* Shows a modal for login method selection, then opens a popup for OAuth.
|
|
536
|
+
* Config is optional - defaults will be auto-derived if not provided.
|
|
460
537
|
*/
|
|
461
|
-
const loginWithEmbedded = useCallback(async (config
|
|
538
|
+
const loginWithEmbedded = useCallback(async (config?: LoginConfig): Promise<void> => {
|
|
462
539
|
setIsLoggingIn(true);
|
|
463
540
|
setError(null);
|
|
464
541
|
|
|
465
542
|
try {
|
|
466
|
-
const
|
|
543
|
+
const fullConfig = config ?? getSandboxLoginConfig();
|
|
544
|
+
const tokens = await rawLoginWithEmbedded(fullConfig);
|
|
467
545
|
await signInWithTokens(tokens);
|
|
468
546
|
} catch (err) {
|
|
469
547
|
const errorMessage = err instanceof Error ? err.message : 'Login failed';
|
|
@@ -479,16 +557,18 @@ export function useLogin(): UseLoginReturn {
|
|
|
479
557
|
* Redirects the page to OAuth authentication.
|
|
480
558
|
* After authentication, the user will be redirected to your callback page.
|
|
481
559
|
* Use the CallbackPage component to complete the flow.
|
|
560
|
+
* Config is optional - defaults will be auto-derived if not provided.
|
|
482
561
|
*/
|
|
483
562
|
const loginWithRedirect = useCallback(async (
|
|
484
|
-
config
|
|
563
|
+
config?: LoginConfig,
|
|
485
564
|
options?: StandaloneLoginOptions,
|
|
486
565
|
): Promise<void> => {
|
|
487
566
|
setIsLoggingIn(true);
|
|
488
567
|
setError(null);
|
|
489
568
|
|
|
490
569
|
try {
|
|
491
|
-
|
|
570
|
+
const fullConfig = config ?? getSandboxLoginConfig();
|
|
571
|
+
await rawLoginWithRedirect(fullConfig, options);
|
|
492
572
|
// Note: The page will redirect, so this code may not run
|
|
493
573
|
} catch (err) {
|
|
494
574
|
const errorMessage = err instanceof Error ? err.message : 'Login failed';
|
|
@@ -518,9 +598,11 @@ export interface UseLogoutReturn {
|
|
|
518
598
|
* This ensures that when the user logs in again, they will be prompted to select
|
|
519
599
|
* an account instead of being automatically logged in with the previous account.
|
|
520
600
|
*
|
|
521
|
-
*
|
|
601
|
+
* Config is optional - defaults will be auto-derived if not provided.
|
|
602
|
+
*
|
|
603
|
+
* @param config - Optional logout configuration with clientId and optional redirectUri
|
|
522
604
|
*/
|
|
523
|
-
logout: (config
|
|
605
|
+
logout: (config?: LogoutConfig) => Promise<void>;
|
|
524
606
|
/** Whether logout is currently in progress */
|
|
525
607
|
isLoggingOut: boolean;
|
|
526
608
|
/** Error message from the last logout attempt, or null if none */
|
|
@@ -538,16 +620,38 @@ export interface UseLogoutReturn {
|
|
|
538
620
|
* an account (for social logins like Google) instead of being automatically logged
|
|
539
621
|
* in with the previous account.
|
|
540
622
|
*
|
|
623
|
+
* Config is optional - defaults will be auto-derived if not provided:
|
|
624
|
+
* - `clientId`: Auto-detected based on environment (sandbox vs production)
|
|
625
|
+
* - `logoutRedirectUri`: Auto-derived from `window.location.origin`
|
|
626
|
+
*
|
|
541
627
|
* Must be used within a SessionProvider from next-auth/react.
|
|
542
628
|
*
|
|
543
|
-
* @example
|
|
629
|
+
* @example Minimal usage (uses all defaults)
|
|
544
630
|
* ```tsx
|
|
545
631
|
* import { useLogout, useImmutableSession } from '@imtbl/auth-next-client';
|
|
546
632
|
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
633
|
+
* function LogoutButton() {
|
|
634
|
+
* const { isAuthenticated } = useImmutableSession();
|
|
635
|
+
* const { logout, isLoggingOut, error } = useLogout();
|
|
636
|
+
*
|
|
637
|
+
* if (!isAuthenticated) {
|
|
638
|
+
* return null;
|
|
639
|
+
* }
|
|
640
|
+
*
|
|
641
|
+
* return (
|
|
642
|
+
* <>
|
|
643
|
+
* <button onClick={() => logout()} disabled={isLoggingOut}>
|
|
644
|
+
* {isLoggingOut ? 'Signing out...' : 'Sign Out'}
|
|
645
|
+
* </button>
|
|
646
|
+
* {error && <p style={{ color: 'red' }}>{error}</p>}
|
|
647
|
+
* </>
|
|
648
|
+
* );
|
|
649
|
+
* }
|
|
650
|
+
* ```
|
|
651
|
+
*
|
|
652
|
+
* @example With custom configuration
|
|
653
|
+
* ```tsx
|
|
654
|
+
* import { useLogout, useImmutableSession } from '@imtbl/auth-next-client';
|
|
551
655
|
*
|
|
552
656
|
* function LogoutButton() {
|
|
553
657
|
* const { isAuthenticated } = useImmutableSession();
|
|
@@ -559,7 +663,13 @@ export interface UseLogoutReturn {
|
|
|
559
663
|
*
|
|
560
664
|
* return (
|
|
561
665
|
* <>
|
|
562
|
-
* <button
|
|
666
|
+
* <button
|
|
667
|
+
* onClick={() => logout({
|
|
668
|
+
* clientId: process.env.NEXT_PUBLIC_IMMUTABLE_CLIENT_ID,
|
|
669
|
+
* logoutRedirectUri: `${window.location.origin}/custom-logout`,
|
|
670
|
+
* })}
|
|
671
|
+
* disabled={isLoggingOut}
|
|
672
|
+
* >
|
|
563
673
|
* {isLoggingOut ? 'Signing out...' : 'Sign Out'}
|
|
564
674
|
* </button>
|
|
565
675
|
* {error && <p style={{ color: 'red' }}>{error}</p>}
|
|
@@ -575,8 +685,9 @@ export function useLogout(): UseLogoutReturn {
|
|
|
575
685
|
/**
|
|
576
686
|
* Logout with federated logout.
|
|
577
687
|
* First clears the NextAuth session, then redirects to the auth domain's logout endpoint.
|
|
688
|
+
* Config is optional - defaults will be auto-derived if not provided.
|
|
578
689
|
*/
|
|
579
|
-
const logout = useCallback(async (config
|
|
690
|
+
const logout = useCallback(async (config?: LogoutConfig): Promise<void> => {
|
|
580
691
|
setIsLoggingOut(true);
|
|
581
692
|
setError(null);
|
|
582
693
|
|
|
@@ -588,10 +699,13 @@ export function useLogout(): UseLogoutReturn {
|
|
|
588
699
|
// We use redirect: false to handle the redirect ourselves for federated logout
|
|
589
700
|
await signOut({ redirect: false });
|
|
590
701
|
|
|
702
|
+
// Create full config with defaults
|
|
703
|
+
const fullConfig = config ?? getSandboxLogoutConfig();
|
|
704
|
+
|
|
591
705
|
// Redirect to the auth domain's logout endpoint using the standalone function
|
|
592
706
|
// This clears the upstream session (Auth0/Immutable) so that on next login,
|
|
593
707
|
// the user will be prompted to select an account instead of auto-logging in
|
|
594
|
-
rawLogoutWithRedirect(
|
|
708
|
+
rawLogoutWithRedirect(fullConfig);
|
|
595
709
|
} catch (err) {
|
|
596
710
|
const errorMessage = err instanceof Error ? err.message : 'Logout failed';
|
|
597
711
|
setError(errorMessage);
|
package/src/index.ts
CHANGED
|
@@ -59,3 +59,15 @@ export type {
|
|
|
59
59
|
LogoutConfig,
|
|
60
60
|
} from '@imtbl/auth';
|
|
61
61
|
export { MarketingConsentStatus } from '@imtbl/auth';
|
|
62
|
+
|
|
63
|
+
// Re-export constants and default config helpers for consumer convenience
|
|
64
|
+
export {
|
|
65
|
+
DEFAULT_AUTH_DOMAIN,
|
|
66
|
+
DEFAULT_AUDIENCE,
|
|
67
|
+
DEFAULT_SCOPE,
|
|
68
|
+
IMMUTABLE_PROVIDER_ID,
|
|
69
|
+
DEFAULT_SANDBOX_CLIENT_ID,
|
|
70
|
+
DEFAULT_REDIRECT_URI_PATH,
|
|
71
|
+
DEFAULT_LOGOUT_REDIRECT_URI_PATH,
|
|
72
|
+
} from './constants';
|
|
73
|
+
export { deriveDefaultRedirectUri } from './defaultConfig';
|