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
1 change: 1 addition & 0 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export * from './__legacy__/worker/worker-receiver';
export {AsgardeoBrowserConfig} from './models/config';

export {default as hasAuthParamsInUrl} from './utils/hasAuthParamsInUrl';
export {default as hasCalledForThisInstanceInUrl} from './utils/hasCalledForThisInstanceInUrl';
export {default as navigate} from './utils/navigate';

export {default as AsgardeoBrowserClient} from './AsgardeoBrowserClient';
Expand Down
32 changes: 32 additions & 0 deletions packages/browser/src/utils/hasCalledForThisInstanceInUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2025, 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
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/**
* Utility to check if `state` is available in the URL as a search param and matches the provided instance.
*
* @param params - The URL search params to check. Defaults to `window.location.search`.
* @param instanceId - The instance ID to match against the `state` param.
* @return `true` if the URL contains a matching `state` search param, otherwise `false`.
*/
const hasCalledForThisInstanceInUrl = (instanceId: number, params: string = window.location.search): boolean => {
const MATCHER: RegExp = new RegExp(`[?&]state=instance_${instanceId}-[^&]+`);

return MATCHER.test(params);
};

export default hasCalledForThisInstanceInUrl;
18 changes: 10 additions & 8 deletions packages/javascript/src/__legacy__/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class AsgardeoAuthClient<T> {
private _cryptoUtils: Crypto;
private _cryptoHelper: IsomorphicCrypto;

private static _instanceID: number;
private _instanceID: number;

// FIXME: Validate this.
// Ref: https://github.com/asgardeo/asgardeo-auth-js-core/pull/205
Expand Down Expand Up @@ -118,20 +118,20 @@ export class AsgardeoAuthClient<T> {
): Promise<void> {
const clientId: string = config.clientId;

if (!AsgardeoAuthClient._instanceID) {
AsgardeoAuthClient._instanceID = 0;
if (!this._instanceID) {
this._instanceID = 0;
} else {
AsgardeoAuthClient._instanceID += 1;
this._instanceID += 1;
}

if (instanceID) {
AsgardeoAuthClient._instanceID = instanceID;
this._instanceID = instanceID;
}

if (!clientId) {
this._storageManager = new StorageManager<T>(`instance_${AsgardeoAuthClient._instanceID}`, store);
this._storageManager = new StorageManager<T>(`instance_${this._instanceID}`, store);
} else {
this._storageManager = new StorageManager<T>(`instance_${AsgardeoAuthClient._instanceID}-${clientId}`, store);
this._storageManager = new StorageManager<T>(`instance_${this._instanceID}-${clientId}`, store);
}

this._cryptoUtils = cryptoUtils;
Expand Down Expand Up @@ -182,7 +182,7 @@ export class AsgardeoAuthClient<T> {
* @preserve
*/
public getInstanceId(): number {
return AsgardeoAuthClient._instanceID;
return this._instanceID;
}

/**
Expand Down Expand Up @@ -245,6 +245,8 @@ export class AsgardeoAuthClient<T> {
authRequestConfig['client_secret'] = configData.clientSecret;
}

authRequestConfig['state'] = 'instance_' + this.getInstanceId() + '-' + configData.clientId;

const authorizeRequestParams: Map<string, string> = getAuthorizeRequestUrlParams(
{
redirectUri: configData.afterSignInUrl,
Expand Down
18 changes: 16 additions & 2 deletions packages/react/src/AsgardeoReactClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,26 @@ import getAllOrganizations from './api/getAllOrganizations';
class AsgardeoReactClient<T extends AsgardeoReactConfig = AsgardeoReactConfig> extends AsgardeoBrowserClient<T> {
private asgardeo: AuthAPI;
private _isLoading: boolean = false;
private _instanceId: number;

constructor() {
/**
* Creates a new AsgardeoReactClient instance.
* @param instanceId - Optional instance ID for multi-auth context support. Defaults to 0 for backward compatibility.
*/
constructor(instanceId: number = 0) {
super();
this._instanceId = instanceId;

// FIXME: This has to be the browser client from `@asgardeo/browser` package.
this.asgardeo = new AuthAPI();
this.asgardeo = new AuthAPI(undefined, instanceId);
}

/**
* Get the instance ID for this client.
* @returns The instance ID used for multi-auth context support.
*/
public getInstanceId(): number {
return this._instanceId;
}

/**
Expand Down
14 changes: 12 additions & 2 deletions packages/react/src/__temp__/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ class AuthAPI {

private _authState = AuthAPI.DEFAULT_STATE;
private _client: AsgardeoSPAClient;
private _instanceId: number;

private _isLoading: boolean;

constructor(spaClient?: AsgardeoSPAClient) {
this._client = spaClient ?? AsgardeoSPAClient.getInstance();
constructor(spaClient?: AsgardeoSPAClient, instanceId: number = 0) {
this._instanceId = instanceId;
this._client = spaClient ?? AsgardeoSPAClient.getInstance(instanceId);

this.getState = this.getState.bind(this);
this.init = this.init.bind(this);
Expand All @@ -50,6 +52,14 @@ class AuthAPI {
this.updateState = this.updateState.bind(this);
}

/**
* Get the instance ID for this AuthAPI instance.
* @returns The instance ID used for multi-auth context support.
*/
public getInstanceId(): number {
return this._instanceId;
}

public _setIsLoading(isLoading: boolean): void {
this._isLoading = isLoading;
}
Expand Down
7 changes: 4 additions & 3 deletions packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,12 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
applicationId,
signInOptions,
syncSession,
instanceId = 0,
...rest
}: PropsWithChildren<AsgardeoProviderProps>): ReactElement => {
const reRenderCheckRef: RefObject<boolean> = useRef(false);
const asgardeo: AsgardeoReactClient = useMemo(() => new AsgardeoReactClient(), []);
const {hasAuthParams} = useBrowserUrl();
const asgardeo: AsgardeoReactClient = useMemo(() => new AsgardeoReactClient(instanceId), [instanceId]);
const {hasAuthParams, hasCalledForThisInstance} = useBrowserUrl();
const [user, setUser] = useState<any | null>(null);
const [currentOrganization, setCurrentOrganization] = useState<Organization | null>(null);

Expand Down Expand Up @@ -153,7 +154,7 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
}

const currentUrl: URL = new URL(window.location.href);
const hasAuthParamsResult: boolean = hasAuthParams(currentUrl, afterSignInUrl);
const hasAuthParamsResult: boolean = hasAuthParams(currentUrl, afterSignInUrl) && hasCalledForThisInstance(currentUrl, instanceId ?? 1);

const isV2Platform = config.platform === Platform.AsgardeoV2;

Expand Down
16 changes: 14 additions & 2 deletions packages/react/src/hooks/useBrowserUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* under the License.
*/

import {hasAuthParamsInUrl} from '@asgardeo/browser';
import {hasAuthParamsInUrl, hasCalledForThisInstanceInUrl} from '@asgardeo/browser';

/**
* Interface for the useBrowserUrl hook return value.
Expand All @@ -30,6 +30,15 @@ export interface UseBrowserUrl {
* @returns True if the URL contains authentication parameters and matches the afterSignInUrl, or if it contains an error parameter
*/
hasAuthParams: (url: URL, afterSignInUrl: string) => boolean;

/**
* Checks if the URL indicates that the authentication flow has been called for this instance.
*
* @param url - The URL object to check
* @param instanceId - The instance ID to check against
* @returns True if the URL indicates the flow has been called for this instance
*/
hasCalledForThisInstance: (url: URL, instanceId: number) => boolean;
}

/**
Expand All @@ -53,7 +62,10 @@ const useBrowserUrl = (): UseBrowserUrl => {
// authParams?.authorizationCode || // FIXME: These are sent externally. Need to see what we can do about this.
url.searchParams.get('error') !== null;

return {hasAuthParams};
const hasCalledForThisInstance = (url: URL, instanceId: number): boolean =>
(hasCalledForThisInstanceInUrl(instanceId, url.search));

return {hasAuthParams, hasCalledForThisInstance};
};

export default useBrowserUrl;
9 changes: 8 additions & 1 deletion packages/react/src/models/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,11 @@

import {AsgardeoBrowserConfig} from '@asgardeo/browser';

export type AsgardeoReactConfig = AsgardeoBrowserConfig;
export interface AsgardeoReactConfig extends AsgardeoBrowserConfig {
/**
* Optional instance ID for multi-auth context support.
* Use this when you need multiple authentication contexts in the same application.
* Defaults to 0 for backward compatibility.
*/
instanceId?: number;
}
Loading