Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/short-lemons-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@asgardeo/browser': patch
'@asgardeo/react': patch
---

Fix auto refresh token logic error
6 changes: 6 additions & 0 deletions packages/browser/src/__legacy__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,12 @@ export class AsgardeoSPAClient {
return this._client?.isSignedIn();
}

public async startAutoRefreshToken(): Promise<void> {
await this.isInitialized();

return (this._client as MainThreadClientInterface)?.startAutoRefreshToken();
}

/**
* This method specifies if there is an active session in the browser or not.
*
Expand Down
6 changes: 6 additions & 0 deletions packages/browser/src/__legacy__/clients/main-thread-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,11 @@ export const MainThreadClient = async (
return _authenticationClient.decodeJwtToken<T>(token);
};

const startAutoRefreshToken = async (): Promise<void> => {
_spaHelper.clearRefreshTokenTimeout();
await _spaHelper.refreshAccessTokenAutomatically(_authenticationHelper);
};

return {
disableHttpHandler,
enableHttpHandler,
Expand All @@ -441,6 +446,7 @@ export const MainThreadClient = async (
signIn,
signOut,
signInSilently,
startAutoRefreshToken,
reInitialize,
decodeJwtToken,
};
Expand Down
18 changes: 14 additions & 4 deletions packages/browser/src/__legacy__/helpers/spa-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,23 @@ export class SPAHelper<T extends MainThreadClientConfig | WebWorkerClientConfig>

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,
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/__legacy__/models/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export interface MainThreadClientInterface {
getAccessToken(sessionId?: string): Promise<string>;
getStorageManager(): Promise<StorageManager<MainThreadClientConfig>>;
isSignedIn(): Promise<boolean>;
startAutoRefreshToken(): Promise<void>;
reInitialize(config: Partial<AuthClientConfig<MainThreadClientConfig>>): Promise<void>;
signInSilently(
additionalParams?: Record<string, string | boolean>,
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/AsgardeoReactClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,10 @@ class AsgardeoReactClient<T extends AsgardeoReactConfig = AsgardeoReactConfig> e
return this.asgardeo.isSignedIn();
}

async startAutoRefreshToken(): Promise<void> {
return this.asgardeo.startAutoRefreshToken();
}

override getConfiguration(): T {
return this.asgardeo.getConfigData() as unknown as T;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/react/src/__temp__/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ class AuthAPI {
return this.client.isSignedIn();
}

public async startAutoRefreshToken(): Promise<void> {
return this.client.startAutoRefreshToken();
}

/**
* This method specifies if the session is active or not.
*
Expand Down
34 changes: 32 additions & 2 deletions packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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';
Expand All @@ -48,6 +49,11 @@ import OrganizationProvider from '../Organization/OrganizationProvider';
import ThemeProvider from '../Theme/ThemeProvider';
import UserProvider from '../User/UserProvider';

const logger: ReturnType<typeof createPackageComponentLogger> = createPackageComponentLogger(
'@asgardeo/react',
'AsgardeoProvider',
);

/**
* Props interface of {@link AsgardeoProvider}
*/
Expand Down Expand Up @@ -269,8 +275,32 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
// User is already authenticated. Skip...
const isAlreadySignedIn: boolean = await asgardeo.isSignedIn();

if (isAlreadySignedIn) {
// Start auto-refresh with a soft failure.
const scheduleAutoRefresh = async (): Promise<void> => {
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<void> => {
await updateSession();
await scheduleAutoRefresh();
};

if (isAlreadySignedIn) {
await resumeSession();
}

// 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 resumeSession();
return;
}

Expand Down
Loading