From ed8f055630cb4078bd0164a1969718510a5d1b5b Mon Sep 17 00:00:00 2001 From: NipuniBhagya Date: Tue, 7 Apr 2026 22:59:37 +0530 Subject: [PATCH 1/5] Fix auto refresh token logic to ensure timely token renewal --- .changeset/short-lemons-change.md | 5 +++++ .../src/__legacy__/helpers/spa-helper.ts | 18 ++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 .changeset/short-lemons-change.md diff --git a/.changeset/short-lemons-change.md b/.changeset/short-lemons-change.md new file mode 100644 index 000000000..ee5605048 --- /dev/null +++ b/.changeset/short-lemons-change.md @@ -0,0 +1,5 @@ +--- +'@asgardeo/browser': patch +--- + +Fix auto refresh token logic error diff --git a/packages/browser/src/__legacy__/helpers/spa-helper.ts b/packages/browser/src/__legacy__/helpers/spa-helper.ts index ab5085c6d..6a2e31576 100644 --- a/packages/browser/src/__legacy__/helpers/spa-helper.ts +++ b/packages/browser/src/__legacy__/helpers/spa-helper.ts @@ -42,13 +42,23 @@ export class SPAHelper const sessionData = await this._storageManager.getSessionData(); if (sessionData.refresh_token) { - // Refresh 10 seconds before the expiry time - const expiryTime = parseInt(sessionData.expires_in); - const time = expiryTime <= 10 ? expiryTime : expiryTime - 10; + if (sessionData.created_at == null || sessionData.expires_in == null) { + return; + } + + const TOKEN_REFRESH_BUFFER_MS = 10_000; + const expiryTime = Number(sessionData.expires_in) * 1000; + const absoluteExpiryTime: number = sessionData.created_at + expiryTime; + const timeUntilRefresh = absoluteExpiryTime - Date.now() - TOKEN_REFRESH_BUFFER_MS; + + if (timeUntilRefresh <= 0) { + await authenticationHelper.refreshAccessToken(); + return; + } const timer = setTimeout(async () => { await authenticationHelper.refreshAccessToken(); - }, time * 1000); + }, timeUntilRefresh); await this._storageManager.setTemporaryDataParameter( TokenConstants.Storage.StorageKeys.REFRESH_TOKEN_TIMER, From 77639b0792a1b1afd1de91fc5996bb210e8e6cbd Mon Sep 17 00:00:00 2001 From: NipuniBhagya Date: Wed, 8 Apr 2026 11:56:34 +0530 Subject: [PATCH 2/5] Improve isSignedIn method --- .changeset/loud-trams-post.md | 5 +++++ packages/browser/src/__legacy__/client.ts | 6 ++++++ .../browser/src/__legacy__/clients/main-thread-client.ts | 6 ++++++ packages/browser/src/__legacy__/models/client.ts | 1 + packages/react/src/AsgardeoReactClient.ts | 4 ++++ packages/react/src/__temp__/api.ts | 4 ++++ packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx | 8 ++++++++ 7 files changed, 34 insertions(+) create mode 100644 .changeset/loud-trams-post.md diff --git a/.changeset/loud-trams-post.md b/.changeset/loud-trams-post.md new file mode 100644 index 000000000..006fc63bc --- /dev/null +++ b/.changeset/loud-trams-post.md @@ -0,0 +1,5 @@ +--- +'@asgardeo/react': patch +--- + +Improve isSignedIn method diff --git a/packages/browser/src/__legacy__/client.ts b/packages/browser/src/__legacy__/client.ts index eb8226294..fc5d1186c 100755 --- a/packages/browser/src/__legacy__/client.ts +++ b/packages/browser/src/__legacy__/client.ts @@ -1107,6 +1107,12 @@ export class AsgardeoSPAClient { return this._client?.isSignedIn(); } + public async startAutoRefreshToken(): Promise { + await this.isInitialized(); + + return (this._client as MainThreadClientInterface)?.startAutoRefreshToken(); + } + /** * This method specifies if there is an active session in the browser or not. * diff --git a/packages/browser/src/__legacy__/clients/main-thread-client.ts b/packages/browser/src/__legacy__/clients/main-thread-client.ts index 86d98ce9a..1b43d85d4 100755 --- a/packages/browser/src/__legacy__/clients/main-thread-client.ts +++ b/packages/browser/src/__legacy__/clients/main-thread-client.ts @@ -415,6 +415,11 @@ export const MainThreadClient = async ( return _authenticationClient.decodeJwtToken(token); }; + const startAutoRefreshToken = async (): Promise => { + _spaHelper.clearRefreshTokenTimeout(); + await _spaHelper.refreshAccessTokenAutomatically(_authenticationHelper); + }; + return { disableHttpHandler, enableHttpHandler, @@ -441,6 +446,7 @@ export const MainThreadClient = async ( signIn, signOut, signInSilently, + startAutoRefreshToken, reInitialize, decodeJwtToken, }; diff --git a/packages/browser/src/__legacy__/models/client.ts b/packages/browser/src/__legacy__/models/client.ts index 4a453386c..0501c8741 100755 --- a/packages/browser/src/__legacy__/models/client.ts +++ b/packages/browser/src/__legacy__/models/client.ts @@ -67,6 +67,7 @@ export interface MainThreadClientInterface { getAccessToken(sessionId?: string): Promise; getStorageManager(): Promise>; isSignedIn(): Promise; + startAutoRefreshToken(): Promise; reInitialize(config: Partial>): Promise; signInSilently( additionalParams?: Record, diff --git a/packages/react/src/AsgardeoReactClient.ts b/packages/react/src/AsgardeoReactClient.ts index c4bc250bc..5b8f31230 100644 --- a/packages/react/src/AsgardeoReactClient.ts +++ b/packages/react/src/AsgardeoReactClient.ts @@ -336,6 +336,10 @@ class AsgardeoReactClient e return this.asgardeo.isSignedIn(); } + async startAutoRefreshToken(): Promise { + return this.asgardeo.startAutoRefreshToken(); + } + override getConfiguration(): T { return this.asgardeo.getConfigData() as unknown as T; } diff --git a/packages/react/src/__temp__/api.ts b/packages/react/src/__temp__/api.ts index affc85e2e..88a44762d 100644 --- a/packages/react/src/__temp__/api.ts +++ b/packages/react/src/__temp__/api.ts @@ -383,6 +383,10 @@ class AuthAPI { return this.client.isSignedIn(); } + public async startAutoRefreshToken(): Promise { + return this.client.startAutoRefreshToken(); + } + /** * This method specifies if the session is active or not. * diff --git a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx index bfbfd5cea..2a45c2231 100644 --- a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx @@ -271,6 +271,14 @@ const AsgardeoProvider: FC> = ({ if (isAlreadySignedIn) { await updateSession(); + await asgardeo.startAutoRefreshToken(); + return; + } + + await asgardeo.startAutoRefreshToken().catch(() => {}); + if (await asgardeo.isSignedIn()) { + await updateSession(); + await asgardeo.startAutoRefreshToken(); return; } From 7f893ff45caad8a454a195102c734feb25126e5d Mon Sep 17 00:00:00 2001 From: NipuniBhagya Date: Wed, 8 Apr 2026 15:05:10 +0530 Subject: [PATCH 3/5] Resolve PR comments --- .../contexts/Asgardeo/AsgardeoProvider.tsx | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx index 2a45c2231..5a5d08e7a 100644 --- a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * Copyright (c) 2025-2026, WSO2 LLC. (https://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -34,6 +34,7 @@ import { extractUserClaimsFromIdToken, EmbeddedSignInFlowResponseV2, TokenResponse, + createPackageComponentLogger, } from '@asgardeo/browser'; import {FC, RefObject, PropsWithChildren, ReactElement, useEffect, useMemo, useRef, useState, useCallback} from 'react'; import AsgardeoContext from './AsgardeoContext'; @@ -48,6 +49,11 @@ import OrganizationProvider from '../Organization/OrganizationProvider'; import ThemeProvider from '../Theme/ThemeProvider'; import UserProvider from '../User/UserProvider'; +const logger: ReturnType = createPackageComponentLogger( + '@asgardeo/react', + 'AsgardeoProvider', +); + /** * Props interface of {@link AsgardeoProvider} */ @@ -269,16 +275,32 @@ const AsgardeoProvider: FC> = ({ // User is already authenticated. Skip... const isAlreadySignedIn: boolean = await asgardeo.isSignedIn(); - if (isAlreadySignedIn) { + // Start auto-refresh with a soft failure. + const scheduleAutoRefresh = async (): Promise => { + try { + await asgardeo.startAutoRefreshToken(); + } catch (error) { + logger.warn('Failed to schedule automatic token refresh.', error); + } + }; + + // Restore session state and kick off the refresh timer. + const resumeSession = async (): Promise => { await updateSession(); - await asgardeo.startAutoRefreshToken(); - return; + await scheduleAutoRefresh(); + }; + + if (isAlreadySignedIn) { + await resumeSession(); } - await asgardeo.startAutoRefreshToken().catch(() => {}); + // The access token may have expired while the refresh token is still valid. + // Attempt a silent refresh — startAutoRefreshToken() calls refreshAccessToken() + // immediately when timeUntilRefresh <= 0, then re-check sign-in status. + await scheduleAutoRefresh(); + if (await asgardeo.isSignedIn()) { - await updateSession(); - await asgardeo.startAutoRefreshToken(); + await resumeSession(); return; } From 158bc163a3843cfed18f598f8da83cb21bbef2d2 Mon Sep 17 00:00:00 2001 From: Nipuni Paaris Date: Wed, 8 Apr 2026 15:07:04 +0530 Subject: [PATCH 4/5] Fix auto refresh token logic error Fixes an error in the auto refresh token logic. --- .changeset/short-lemons-change.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/short-lemons-change.md b/.changeset/short-lemons-change.md index ee5605048..3cd3842f2 100644 --- a/.changeset/short-lemons-change.md +++ b/.changeset/short-lemons-change.md @@ -1,5 +1,6 @@ --- '@asgardeo/browser': patch +'@asgardeo/react': patch --- Fix auto refresh token logic error From 5cf6b9408d0c4f8062d88dcc0c842c2c7cfa6868 Mon Sep 17 00:00:00 2001 From: Nipuni Paaris Date: Wed, 8 Apr 2026 15:07:27 +0530 Subject: [PATCH 5/5] Delete .changeset/loud-trams-post.md --- .changeset/loud-trams-post.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/loud-trams-post.md diff --git a/.changeset/loud-trams-post.md b/.changeset/loud-trams-post.md deleted file mode 100644 index 006fc63bc..000000000 --- a/.changeset/loud-trams-post.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@asgardeo/react': patch ---- - -Improve isSignedIn method