antaeus.keycloak.react 2.2.3 → 2.3.0

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
@@ -325,6 +325,24 @@ If your refresh token expires (exceeds `maxRefreshTokenLifetime`):
325
325
  />
326
326
  ```
327
327
 
328
+ ### Refresh Tokens After Restart
329
+
330
+ When a refresh token is available the provider now reuses it on startup, ensuring long-lived sessions survive browser reloads or app restarts. The refresh token grant is attempted automatically before falling back to iframe-based silent renew.
331
+
332
+ If you need to disable this behavior and revert to iframe-only silent renew, set:
333
+
334
+ ```tsx
335
+ <KeycloakProvider
336
+ authority="..."
337
+ clientId="my-app"
338
+ config={{
339
+ silentRenew: {
340
+ useRefreshToken: false,
341
+ },
342
+ }}
343
+ />
344
+ ```
345
+
328
346
  ### Startup Token Refresh
329
347
 
330
348
  When your app loads with an existing session:
@@ -446,4 +464,3 @@ const { fetchJson } = useProtectedFetch({
446
464
  ## License
447
465
 
448
466
  MIT � Saeed Aghdam
449
-
package/dist/index.d.mts CHANGED
@@ -113,6 +113,12 @@ interface SilentRenewConfig {
113
113
  * @default 'Refreshing session...'
114
114
  */
115
115
  loadingMessage?: string;
116
+ /**
117
+ * Enable using the refresh token grant instead of iframe silent renew when available.
118
+ * Keeps long-lived sessions alive even after the app is closed.
119
+ * @default true
120
+ */
121
+ useRefreshToken?: boolean;
116
122
  }
117
123
  /**
118
124
  * Event callbacks for authentication lifecycle
@@ -318,6 +324,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
318
324
  * silentRenew: {
319
325
  * maxRefreshTokenLifetime: 604800, // Override: 7 days instead of 30
320
326
  * showLoadingIndicator: true,
327
+ * useRefreshToken: true, // Explicitly ensure refresh tokens are used when available
321
328
  * }
322
329
  * }}
323
330
  * debug={true}
@@ -361,6 +368,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
361
368
  * enabled: true,
362
369
  * refreshStrategy: 'both',
363
370
  * maxRefreshTokenLifetime: 2592000,
371
+ * useRefreshToken: true,
364
372
  * },
365
373
  * events: {
366
374
  * onLogin: (user) => console.log('User logged in', user),
@@ -380,6 +388,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
380
388
  * - ✅ Token lifecycle management (refresh on app startup)
381
389
  * - ✅ Session monitoring UI (countdown timer)
382
390
  * - ✅ Silent renew (background token refresh)
391
+ * - ✅ Refresh token grant fallback on app restart
383
392
  * - ✅ Error notifications (user-friendly messages)
384
393
  * - ✅ 401 retry logic (automatic token refresh on API errors)
385
394
  * - ✅ Maximum refresh token lifetime (security control)
package/dist/index.d.ts CHANGED
@@ -113,6 +113,12 @@ interface SilentRenewConfig {
113
113
  * @default 'Refreshing session...'
114
114
  */
115
115
  loadingMessage?: string;
116
+ /**
117
+ * Enable using the refresh token grant instead of iframe silent renew when available.
118
+ * Keeps long-lived sessions alive even after the app is closed.
119
+ * @default true
120
+ */
121
+ useRefreshToken?: boolean;
116
122
  }
117
123
  /**
118
124
  * Event callbacks for authentication lifecycle
@@ -318,6 +324,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
318
324
  * silentRenew: {
319
325
  * maxRefreshTokenLifetime: 604800, // Override: 7 days instead of 30
320
326
  * showLoadingIndicator: true,
327
+ * useRefreshToken: true, // Explicitly ensure refresh tokens are used when available
321
328
  * }
322
329
  * }}
323
330
  * debug={true}
@@ -361,6 +368,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
361
368
  * enabled: true,
362
369
  * refreshStrategy: 'both',
363
370
  * maxRefreshTokenLifetime: 2592000,
371
+ * useRefreshToken: true,
364
372
  * },
365
373
  * events: {
366
374
  * onLogin: (user) => console.log('User logged in', user),
@@ -380,6 +388,7 @@ type KeycloakProviderProps = KeycloakProviderZeroConfigProps | KeycloakProviderF
380
388
  * - ✅ Token lifecycle management (refresh on app startup)
381
389
  * - ✅ Session monitoring UI (countdown timer)
382
390
  * - ✅ Silent renew (background token refresh)
391
+ * - ✅ Refresh token grant fallback on app restart
383
392
  * - ✅ Error notifications (user-friendly messages)
384
393
  * - ✅ 401 retry logic (automatic token refresh on API errors)
385
394
  * - ✅ Maximum refresh token lifetime (security control)
package/dist/index.js CHANGED
@@ -30,7 +30,8 @@ var defaultConfig = {
30
30
  maxRefreshTokenLifetime: 2592e3,
31
31
  // 30 days in seconds
32
32
  showLoadingIndicator: false,
33
- loadingMessage: "Refreshing session..."
33
+ loadingMessage: "Refreshing session...",
34
+ useRefreshToken: true
34
35
  },
35
36
  advanced: {
36
37
  responseType: "code",
@@ -56,6 +57,7 @@ var KeycloakConfigBuilder = class {
56
57
  const config = this.mergeWithDefaults(userConfig);
57
58
  const storageType = config.advanced.storageType || "session";
58
59
  const storage = storageType === "local" ? window.localStorage : window.sessionStorage;
60
+ const { useRefreshToken } = config.silentRenew;
59
61
  const oidcSettings = {
60
62
  authority: config.authority,
61
63
  client_id: config.clientId,
@@ -73,6 +75,9 @@ var KeycloakConfigBuilder = class {
73
75
  response_mode: "query"
74
76
  }
75
77
  };
78
+ if (useRefreshToken !== void 0) {
79
+ oidcSettings.useRefreshToken = useRefreshToken;
80
+ }
76
81
  return oidcSettings;
77
82
  }
78
83
  /**
@@ -160,6 +165,7 @@ function useTokenLifecycle(options) {
160
165
  const [isRefreshing, setIsRefreshing] = react.useState(false);
161
166
  const [isRefreshTokenExpired, setIsRefreshTokenExpired] = react.useState(false);
162
167
  const hasCheckedOnStartup = react.useRef(false);
168
+ const supportsRefreshTokens = config.silentRenew?.useRefreshToken !== false;
163
169
  const log = react.useCallback(
164
170
  (message, ...args) => {
165
171
  if (debug) {
@@ -296,24 +302,49 @@ function useTokenLifecycle(options) {
296
302
  react.useEffect(() => {
297
303
  const strategy = config.silentRenew?.refreshStrategy || "both";
298
304
  const shouldCheckOnStartup = strategy === "startup" || strategy === "both";
305
+ const hasRefreshToken = Boolean(auth.user?.refresh_token);
299
306
  if (!shouldCheckOnStartup || hasCheckedOnStartup.current) {
300
307
  return;
301
308
  }
302
- if (!auth.isAuthenticated || auth.isLoading) {
309
+ if (auth.isLoading) {
303
310
  return;
304
311
  }
312
+ if (!auth.isAuthenticated) {
313
+ if (!supportsRefreshTokens) {
314
+ return;
315
+ }
316
+ if (!auth.user || !hasRefreshToken) {
317
+ return;
318
+ }
319
+ }
305
320
  hasCheckedOnStartup.current = true;
306
- log("Running startup token check with strategy:", strategy);
321
+ log(
322
+ "Running startup token check with strategy:",
323
+ strategy,
324
+ "using refresh token:",
325
+ !auth.isAuthenticated && supportsRefreshTokens
326
+ );
307
327
  checkAndRefreshTokens().catch((error) => {
308
328
  log("Startup token check failed:", error);
309
329
  });
310
330
  }, [
311
331
  auth.isAuthenticated,
312
332
  auth.isLoading,
333
+ auth.user,
313
334
  config.silentRenew?.refreshStrategy,
335
+ supportsRefreshTokens,
314
336
  checkAndRefreshTokens,
315
337
  log
316
338
  ]);
339
+ react.useEffect(() => {
340
+ if (hasCheckedOnStartup.current) {
341
+ const hasUser = Boolean(auth.user);
342
+ const hasRefreshToken = Boolean(auth.user?.refresh_token);
343
+ if (auth.isLoading || !auth.isAuthenticated && (!supportsRefreshTokens || !hasUser || !hasRefreshToken)) {
344
+ hasCheckedOnStartup.current = false;
345
+ }
346
+ }
347
+ }, [auth.isAuthenticated, auth.isLoading, auth.user, supportsRefreshTokens]);
317
348
  react.useEffect(() => {
318
349
  if (auth.user && !auth.isLoading) {
319
350
  storeFirstLoginTime();