Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/light-queens-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@asgardeo/browser': minor
---

Fix failure of calling authenticated APIs from secondary AsgardeoProvider Instances in Multi Provider scenarios
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const MainThreadClient = async (

let _getSignOutURLFromSessionStorage: boolean = false;

const _httpClient: HttpClientInstance = HttpClient.getInstance();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: worker-core.ts has the same pattern at the same line — HttpClient.getInstance() without an instanceId — but was missed in this PR. The web worker path will always fall back to instanceId=0 regardless of which provider spawned it.

Suggestion: Thread the instanceID through to WebWorkerCore the same way it's done in MainThreadClient:

export const WebWorkerCore = async (
  instanceID: number,
  config: AuthClientConfig<WebWorkerClientConfig>,
  ...
): Promise<WebWorkerCoreInterface> => {
  ...
  const _httpClient: HttpClientInstance = HttpClient.getInstance(instanceID);
  ...
};

See packages/browser/src/__legacy__/worker/worker-core.ts:65.

const _httpClient: HttpClientInstance = HttpClient.getInstance(instanceID);
let _isHttpHandlerEnabled: boolean = true;
let _httpErrorCallback: (error: HttpError) => void | Promise<void>;
let _httpFinishCallback: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ import {HttpClientInstance, HttpClientInterface, HttpClientStatic} from '../mode
*/
@staticDecorator<HttpClientStatic<HttpClientInstance>>()
export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpResponse, HttpError> {
private static axiosInstance: HttpClientInstance;
private static clientInstance: HttpClient;
private static isHandlerEnabled: boolean;
private static instances: Map<number, HttpClientInstance> = new Map();
private static clientInstances: Map<number, HttpClient> = new Map();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code: clientInstances is write-only — nothing ever calls clientInstances.get(). If you add a destroyInstance() method (see my other comment), this map becomes useful for cleanup. Otherwise, it should be removed to avoid confusion.

private isHandlerEnabled: boolean = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory leak: Instances are added to these static Maps but never removed. If an AsgardeoProvider unmounts and remounts (e.g. React hot-reload, dynamic multi-tenant switching), stale axios instances with outdated interceptors and callbacks will accumulate.

Suggestion: Add a static cleanup method:

public static destroyInstance(instanceId: number): void {
  const axiosInstance = this.instances.get(instanceId);
  if (axiosInstance) {
    // Eject interceptors to prevent memory leaks
    axiosInstance.interceptors.request.clear();
    axiosInstance.interceptors.response.clear();
  }
  this.instances.delete(instanceId);
  this.clientInstances.delete(instanceId);
}

This should be called during provider teardown.

private attachToken: (request: HttpRequestConfig) => Promise<void> = () => Promise.resolve();
private requestStartCallback: (request: HttpRequestConfig) => void = () => null;
private requestSuccessCallback: (response: HttpResponse) => void = () => null;
Expand All @@ -59,55 +59,61 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
*/
private constructor() {
this.init = this.init.bind(this);
this.enableHandler = this.enableHandler.bind(this);
this.disableHandler = this.disableHandler.bind(this);
this.disableHandlerWithTimeout = this.disableHandlerWithTimeout.bind(this);
this.setHttpRequestErrorCallback = this.setHttpRequestErrorCallback.bind(this);
this.setHttpRequestFinishCallback = this.setHttpRequestFinishCallback.bind(this);
this.setHttpRequestStartCallback = this.setHttpRequestStartCallback.bind(this);
this.setHttpRequestSuccessCallback = this.setHttpRequestSuccessCallback.bind(this);
}

/**
* Returns an aggregated instance of type `HttpInstance` of `HttpClient`.
* Returns an instance-specific HttpClient instance.
* Each instance ID gets its own axios instance and HttpClient to avoid state conflicts.
*
* @return {any}
* @param instanceId - The instance ID for multi-auth context support. Defaults to 0.
* @return {HttpClientInstance}
*/
public static getInstance(): HttpClientInstance {
if (this.axiosInstance) {
return this.axiosInstance;
public static getInstance(instanceId: number = 0): HttpClientInstance {
if (this.instances.has(instanceId)) {
return this.instances.get(instanceId)!;
}

this.axiosInstance = axios.create({
const axiosInstance = axios.create({
withCredentials: true,
});
}) as HttpClientInstance;

if (!this.clientInstance) {
this.clientInstance = new HttpClient();
}
const clientInstance = new HttpClient();
this.clientInstances.set(instanceId, clientInstance);

// Register request interceptor
this.axiosInstance.interceptors.request.use(async (request: InternalAxiosRequestConfig) => await this.clientInstance.requestHandler(request as HttpRequestConfig) as InternalAxiosRequestConfig);
axiosInstance.interceptors.request.use(async (request: InternalAxiosRequestConfig) => await clientInstance.requestHandler(request as HttpRequestConfig) as InternalAxiosRequestConfig);

// Register response interceptor
this.axiosInstance.interceptors.response.use(
response => this.clientInstance.successHandler(response),
error => this.clientInstance.errorHandler(error),
axiosInstance.interceptors.response.use(
response => clientInstance.successHandler(response),
error => clientInstance.errorHandler(error),
);

// Add the missing helper methods from axios
this.axiosInstance.all = axios.all;
this.axiosInstance.spread = axios.spread;
axiosInstance.all = axios.all;
axiosInstance.spread = axios.spread;

// Add the init method from the `HttpClient` instance.
this.axiosInstance.init = this.clientInstance.init;
axiosInstance.init = clientInstance.init;

// Add the handler enabling & disabling methods to the instance.
this.axiosInstance.enableHandler = this.clientInstance.enableHandler;
this.axiosInstance.disableHandler = this.clientInstance.disableHandler;
this.axiosInstance.disableHandlerWithTimeout = this.clientInstance.disableHandlerWithTimeout;
this.axiosInstance.setHttpRequestStartCallback = this.clientInstance.setHttpRequestStartCallback;
this.axiosInstance.setHttpRequestSuccessCallback = this.clientInstance.setHttpRequestSuccessCallback;
this.axiosInstance.setHttpRequestErrorCallback = this.clientInstance.setHttpRequestErrorCallback;
this.axiosInstance.setHttpRequestFinishCallback = this.clientInstance.setHttpRequestFinishCallback;
return this.axiosInstance;
axiosInstance.enableHandler = clientInstance.enableHandler;
axiosInstance.disableHandler = clientInstance.disableHandler;
axiosInstance.disableHandlerWithTimeout = clientInstance.disableHandlerWithTimeout;
axiosInstance.setHttpRequestStartCallback = clientInstance.setHttpRequestStartCallback;
axiosInstance.setHttpRequestSuccessCallback = clientInstance.setHttpRequestSuccessCallback;
axiosInstance.setHttpRequestErrorCallback = clientInstance.setHttpRequestErrorCallback;
axiosInstance.setHttpRequestFinishCallback = clientInstance.setHttpRequestFinishCallback;

this.instances.set(instanceId, axiosInstance);
return axiosInstance;
}

/**
Expand All @@ -134,7 +140,7 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe

request.startTimeInMs = new Date().getTime();

if (HttpClient.isHandlerEnabled) {
if (this.isHandlerEnabled) {
if (this.requestStartCallback && typeof this.requestStartCallback === 'function') {
this.requestStartCallback(request);
}
Expand All @@ -151,7 +157,7 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
* @return {HttpError}
*/
public errorHandler(error: HttpError): HttpError {
if (HttpClient.isHandlerEnabled) {
if (this.isHandlerEnabled) {
if (this.requestErrorCallback && typeof this.requestErrorCallback === 'function') {
this.requestErrorCallback(error);
}
Expand All @@ -171,7 +177,7 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
* @return {HttpResponse}
*/
public successHandler(response: HttpResponse): HttpResponse {
if (HttpClient.isHandlerEnabled) {
if (this.isHandlerEnabled) {
if (this.requestSuccessCallback && typeof this.requestSuccessCallback === 'function') {
this.requestSuccessCallback(response);
}
Expand All @@ -195,22 +201,22 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
isHandlerEnabled = true,
attachToken: (request: HttpRequestConfig) => Promise<void>,
): Promise<void> {
HttpClient.isHandlerEnabled = isHandlerEnabled;
this.isHandlerEnabled = isHandlerEnabled;
this.attachToken = attachToken;
}

/**
* Enables the handler.
*/
public enableHandler(): void {
HttpClient.isHandlerEnabled = true;
this.isHandlerEnabled = true;
}

/**
* Disables the handler.
*/
public disableHandler(): void {
HttpClient.isHandlerEnabled = false;
this.isHandlerEnabled = false;
}

/**
Expand All @@ -219,10 +225,10 @@ export class HttpClient implements HttpClientInterface<HttpRequestConfig, HttpRe
* @param {number} timeout - Timeout in milliseconds.
*/
public disableHandlerWithTimeout(timeout: number = HttpClient.DEFAULT_HANDLER_DISABLE_TIMEOUT): void {
HttpClient.isHandlerEnabled = false;
this.isHandlerEnabled = false;

setTimeout(() => {
HttpClient.isHandlerEnabled = true;
this.isHandlerEnabled = true;
}, timeout);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { HttpError, HttpResponse } from "../../models";
* Http client interface with static functions.
*/
export interface HttpClientStatic<S> {
getInstance(): S;
getInstance(instanceId?: number): S;
}

/**
Expand Down
Loading