Skip to content

Commit fbc95de

Browse files
Merge pull request asgardeo#327 from kavindadimuthu/fix/provider-login-race
Fix isolation of login flows when Using Multiple AuthProvider Instances
2 parents fb6d190 + 8705827 commit fbc95de

7 files changed

Lines changed: 77 additions & 16 deletions

File tree

.changeset/brown-paths-report.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@asgardeo/javascript': patch
3+
'@asgardeo/browser': patch
4+
'@asgardeo/react': patch
5+
---
6+
7+
Fix login flow isolation when using multiple AuthProvider instances

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export * from './__legacy__/worker/worker-receiver';
4949
export {AsgardeoBrowserConfig} from './models/config';
5050

5151
export {default as hasAuthParamsInUrl} from './utils/hasAuthParamsInUrl';
52+
export {default as hasCalledForThisInstanceInUrl} from './utils/hasCalledForThisInstanceInUrl';
5253
export {default as navigate} from './utils/navigate';
5354

5455
export {default as AsgardeoBrowserClient} from './AsgardeoBrowserClient';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
/**
20+
* Utility to check if `state` is available in the URL as a search param and matches the provided instance.
21+
*
22+
* @param params - The URL search params to check. Defaults to `window.location.search`.
23+
* @param instanceId - The instance ID to match against the `state` param.
24+
* @return `true` if the URL contains a matching `state` search param, otherwise `false`.
25+
*/
26+
const hasCalledForThisInstanceInUrl = (instanceId: number, params: string = window.location.search): boolean => {
27+
const MATCHER: RegExp = new RegExp(`[?&]state=instance_${instanceId}_[^&]+`);
28+
29+
return MATCHER.test(params);
30+
};
31+
32+
export default hasCalledForThisInstanceInUrl;

packages/javascript/src/__legacy__/client.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export class AsgardeoAuthClient<T> {
6969

7070
private cryptoHelper: IsomorphicCrypto;
7171

72-
private static instanceIdValue: number;
72+
private instanceIdValue: number;
7373

7474
// FIXME: Validate this.
7575
// Ref: https://github.com/asgardeo/asgardeo-auth-js-core/pull/205
@@ -121,20 +121,20 @@ export class AsgardeoAuthClient<T> {
121121
): Promise<void> {
122122
const {clientId} = config;
123123

124-
if (!AsgardeoAuthClient.instanceIdValue) {
125-
AsgardeoAuthClient.instanceIdValue = 0;
124+
if (!this.instanceIdValue) {
125+
this.instanceIdValue = 0;
126126
} else {
127-
AsgardeoAuthClient.instanceIdValue += 1;
127+
this.instanceIdValue += 1;
128128
}
129129

130130
if (instanceID) {
131-
AsgardeoAuthClient.instanceIdValue = instanceID;
131+
this.instanceIdValue = instanceID;
132132
}
133133

134134
if (!clientId) {
135-
this.storageManager = new StorageManager<T>(`instance_${AsgardeoAuthClient.instanceIdValue}`, store);
135+
this.storageManager = new StorageManager<T>(`instance_${this.instanceIdValue}`, store);
136136
} else {
137-
this.storageManager = new StorageManager<T>(`instance_${AsgardeoAuthClient.instanceIdValue}-${clientId}`, store);
137+
this.storageManager = new StorageManager<T>(`instance_${this.instanceIdValue}-${clientId}`, store);
138138
}
139139

140140
this.cryptoUtils = inputCryptoUtils;
@@ -187,7 +187,7 @@ export class AsgardeoAuthClient<T> {
187187
*/
188188
// eslint-disable-next-line class-methods-use-this
189189
public getInstanceId(): number {
190-
return AsgardeoAuthClient.instanceIdValue;
190+
return this.instanceIdValue;
191191
}
192192

193193
/**
@@ -255,6 +255,7 @@ export class AsgardeoAuthClient<T> {
255255
clientId: configData.clientId,
256256
codeChallenge,
257257
codeChallengeMethod: PKCEConstants.DEFAULT_CODE_CHALLENGE_METHOD,
258+
instanceId: this.getInstanceId().toString(),
258259
prompt: configData.prompt,
259260
redirectUri: configData.afterSignInUrl,
260261
responseMode: configData.responseMode,

packages/javascript/src/utils/getAuthorizeRequestUrlParams.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const getAuthorizeRequestUrlParams = (
5555
clientId: string;
5656
codeChallenge?: string;
5757
codeChallengeMethod?: string;
58+
instanceId?: string;
5859
prompt?: string;
5960
redirectUri: string;
6061
responseMode?: string;
@@ -105,12 +106,18 @@ const getAuthorizeRequestUrlParams = (
105106
});
106107
}
107108

109+
const AUTH_INSTANCE_PREFIX: string = 'instance_';
110+
let customStateValue: string = '';
111+
112+
if (options.instanceId) {
113+
customStateValue = AUTH_INSTANCE_PREFIX + options.instanceId;
114+
} else if (customParams) {
115+
customStateValue = customParams[OIDCRequestConstants.Params.STATE]?.toString() ?? '';
116+
}
117+
108118
authorizeRequestParams.set(
109119
OIDCRequestConstants.Params.STATE,
110-
generateStateParamForRequestCorrelation(
111-
pkceKey,
112-
customParams ? customParams[OIDCRequestConstants.Params.STATE]?.toString() : '',
113-
),
120+
generateStateParamForRequestCorrelation(pkceKey, customStateValue),
114121
);
115122

116123
return authorizeRequestParams;

packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
7070
}: PropsWithChildren<AsgardeoProviderProps>): ReactElement => {
7171
const reRenderCheckRef: RefObject<boolean> = useRef(false);
7272
const asgardeo: AsgardeoReactClient = useMemo(() => new AsgardeoReactClient(instanceId), [instanceId]);
73-
const {hasAuthParams} = useBrowserUrl();
73+
const {hasAuthParams, hasCalledForThisInstance} = useBrowserUrl();
7474
const [user, setUser] = useState<any | null>(null);
7575
const [currentOrganization, setCurrentOrganization] = useState<Organization | null>(null);
7676

@@ -266,7 +266,8 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
266266
}
267267

268268
const currentUrl: URL = new URL(window.location.href);
269-
const hasAuthParamsResult: boolean = hasAuthParams(currentUrl, afterSignInUrl);
269+
const hasAuthParamsResult: boolean =
270+
hasAuthParams(currentUrl, afterSignInUrl) && hasCalledForThisInstance(currentUrl, instanceId ?? 0);
270271

271272
const isV2Platform: boolean = config.platform === Platform.AsgardeoV2;
272273

packages/react/src/hooks/useBrowserUrl.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* under the License.
1717
*/
1818

19-
import {hasAuthParamsInUrl} from '@asgardeo/browser';
19+
import {hasAuthParamsInUrl, hasCalledForThisInstanceInUrl} from '@asgardeo/browser';
2020

2121
/**
2222
* Interface for the useBrowserUrl hook return value.
@@ -30,6 +30,15 @@ export interface UseBrowserUrl {
3030
* @returns True if the URL contains authentication parameters and matches the afterSignInUrl, or if it contains an error parameter
3131
*/
3232
hasAuthParams: (url: URL, afterSignInUrl: string) => boolean;
33+
34+
/**
35+
* Checks if the URL indicates that the authentication flow has been called for this instance.
36+
*
37+
* @param url - The URL object to check
38+
* @param instanceId - The instance ID to check against
39+
* @returns True if the URL indicates the flow has been called for this instance
40+
*/
41+
hasCalledForThisInstance: (url: URL, instanceId: number) => boolean;
3342
}
3443

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

56-
return {hasAuthParams};
65+
const hasCalledForThisInstance = (url: URL, instanceId: number): boolean =>
66+
hasCalledForThisInstanceInUrl(instanceId, url.search);
67+
68+
return {hasAuthParams, hasCalledForThisInstance};
5769
};
5870

5971
export default useBrowserUrl;

0 commit comments

Comments
 (0)