From 591fdeae3dba3ad7178c10f847d5c4c463504905 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Thu, 19 Feb 2026 15:15:51 +0530 Subject: [PATCH 1/5] Implement multi organization context handling support for multi-org scenarios --- packages/browser/src/__legacy__/client.ts | 2 +- packages/javascript/src/StorageManager.ts | 16 ++- packages/javascript/src/__legacy__/client.ts | 132 ++++++++++-------- .../helpers/authentication-helper.ts | 26 +++- .../src/__legacy__/models/client-config.ts | 12 ++ packages/javascript/src/models/config.ts | 18 +++ packages/react/src/AsgardeoReactClient.ts | 5 +- .../control/OrganizationContext.tsx | 88 ++++++++++++ .../control/OrganizationContextController.tsx | 90 ++++++++++++ .../src/contexts/Asgardeo/AsgardeoContext.ts | 2 + .../contexts/Asgardeo/AsgardeoProvider.tsx | 6 + packages/react/src/index.ts | 7 +- 12 files changed, 338 insertions(+), 66 deletions(-) create mode 100644 packages/react/src/components/control/OrganizationContext.tsx create mode 100644 packages/react/src/components/control/OrganizationContextController.tsx diff --git a/packages/browser/src/__legacy__/client.ts b/packages/browser/src/__legacy__/client.ts index 74359341a..dc5aea82e 100755 --- a/packages/browser/src/__legacy__/client.ts +++ b/packages/browser/src/__legacy__/client.ts @@ -719,7 +719,7 @@ export class AsgardeoSPAClient { if (config.signInRequired) { await this._validateMethod(); } else { - await this._validateMethod(); + // await this._validateMethod(); } if (!config.id) { diff --git a/packages/javascript/src/StorageManager.ts b/packages/javascript/src/StorageManager.ts index c955b2efb..0d86707aa 100644 --- a/packages/javascript/src/StorageManager.ts +++ b/packages/javascript/src/StorageManager.ts @@ -75,8 +75,16 @@ class StorageManager { await this.store.setData(key, dataToBeSavedJSON); } - protected resolveKey(store: Stores | string, userId?: string): string { - return userId ? `${store}-${this.id}-${userId}` : `${store}-${this.id}`; + protected resolveKey(store: Stores | string, userId?: string, instanceId?: string): string { + if (userId && instanceId) { + return `${store}-${instanceId}-${userId}`; + } else if (userId) { + return `${store}-${this.id}-${userId}`; + } else if (instanceId) { + return `${store}-${instanceId}`; + } else { + return `${store}-${this.id}`; + } } protected static isLocalStorageAvailable(): boolean { @@ -124,8 +132,8 @@ class StorageManager { return JSON.parse((await this.store.getData(this.resolveKey(Stores.TemporaryData, userId))) ?? null); } - public async getSessionData(userId?: string): Promise { - return JSON.parse((await this.store.getData(this.resolveKey(Stores.SessionData, userId))) ?? null); + public async getSessionData(userId?: string, instanceId?: string): Promise { + return JSON.parse((await this.store.getData(this.resolveKey(Stores.SessionData, userId, instanceId))) ?? null); } public async getCustomData(key: string, userId?: string): Promise { diff --git a/packages/javascript/src/__legacy__/client.ts b/packages/javascript/src/__legacy__/client.ts index 043829737..3d0a55725 100644 --- a/packages/javascript/src/__legacy__/client.ts +++ b/packages/javascript/src/__legacy__/client.ts @@ -914,77 +914,95 @@ export class AsgardeoAuthClient { * @preserve */ public async exchangeToken(config: TokenExchangeRequestConfig, userId?: string): Promise { - const oidcProviderMetadata: OIDCDiscoveryApiResponse = await this.oidcProviderMetaDataProvider(); - const configData: StrictAuthClientConfig = await this.configProvider(); + const __TODO__ = async () => { + const oidcProviderMetadata: OIDCDiscoveryApiResponse = await this.oidcProviderMetaDataProvider(); + const configData: StrictAuthClientConfig = await this.configProvider(); - let tokenEndpoint: string | undefined; + let tokenEndpoint: string | undefined; - if (config.tokenEndpoint && config.tokenEndpoint.trim().length !== 0) { - tokenEndpoint = config.tokenEndpoint; - } else { - tokenEndpoint = oidcProviderMetadata.token_endpoint; - } + if (config.tokenEndpoint && config.tokenEndpoint.trim().length !== 0) { + tokenEndpoint = config.tokenEndpoint; + } else { + tokenEndpoint = oidcProviderMetadata.token_endpoint; + } - if (!tokenEndpoint || tokenEndpoint.trim().length === 0) { - throw new AsgardeoAuthException( - 'JS-AUTH_CORE-RCG-NF01', - 'Token endpoint not found.', - 'No token endpoint was found in the OIDC provider meta data returned by the well-known endpoint ' + - 'or the token endpoint passed to the SDK is empty.', - ); - } + if (!tokenEndpoint || tokenEndpoint.trim().length === 0) { + throw new AsgardeoAuthException( + 'JS-AUTH_CORE-RCG-NF01', + 'Token endpoint not found.', + 'No token endpoint was found in the OIDC provider meta data returned by the well-known endpoint ' + + 'or the token endpoint passed to the SDK is empty.', + ); + } - const data: string[] = await Promise.all( - Object.entries(config.data).map(async ([key, value]: [key: string, value: any]) => { - const newValue: string = await this.authHelper.replaceCustomGrantTemplateTags(value as string, userId); + const data: string[] = await Promise.all( + Object.entries(config.data).map(async ([key, value]: [key: string, value: any]) => { + const newValue: string = await this.authHelper.replaceCustomGrantTemplateTags( + value as string, + userId, + ); - return `${key}=${newValue}`; - }), - ); + return `${key}=${newValue}`; + }), + ); - let requestHeaders: Record = { - Accept: 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded', - }; + let requestHeaders: Record = { + Accept: 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + }; + + if (config.attachToken) { + requestHeaders = { + ...requestHeaders, + Authorization: `Bearer ${(await this.storageManager.getSessionData(userId)).access_token}`, + }; + } - if (config.attachToken) { - requestHeaders = { - ...requestHeaders, - Authorization: `Bearer ${(await this.storageManager.getSessionData(userId)).access_token}`, + const requestConfig: RequestInit = { + body: data.join('&'), + credentials: configData.sendCookiesInRequests ? 'include' : 'same-origin', + headers: new Headers(requestHeaders), + method: 'POST', }; - } - const requestConfig: RequestInit = { - body: data.join('&'), - credentials: configData.sendCookiesInRequests ? 'include' : 'same-origin', - headers: new Headers(requestHeaders), - method: 'POST', - }; + let response: Response; - let response: Response; + try { + response = await fetch(tokenEndpoint, requestConfig); + } catch (error: any) { + throw new AsgardeoAuthException( + 'JS-AUTH_CORE-RCG-NE02', + 'The custom grant request failed.', + error ?? 'The request sent to get the custom grant failed.', + ); + } - try { - response = await fetch(tokenEndpoint, requestConfig); - } catch (error: any) { - throw new AsgardeoAuthException( - 'JS-AUTH_CORE-RCG-NE02', - 'The custom grant request failed.', - error ?? 'The request sent to get the custom grant failed.', - ); - } + if (response.status !== 200 || !response.ok) { + throw new AsgardeoAuthException( + 'JS-AUTH_CORE-RCG-HE03', + `Invalid response status received for the custom grant request. (${response.statusText})`, + (await response.json()) as string, + ); + } - if (response.status !== 200 || !response.ok) { - throw new AsgardeoAuthException( - 'JS-AUTH_CORE-RCG-HE03', - `Invalid response status received for the custom grant request. (${response.statusText})`, - (await response.json()) as string, - ); - } + if (config.returnsSession) { + return this.authHelper.handleTokenResponse(response, userId); + } else { + return Promise.resolve((await response.json()) as TokenResponse | Response); + } + }; - if (config.returnsSession) { - return this.authHelper.handleTokenResponse(response, userId); + if ( + await this.storageManager.getTemporaryDataParameter( + OIDCDiscoveryConstants.Storage.StorageKeys.OPENID_PROVIDER_CONFIG_INITIATED, + ) + ) { + return __TODO__(); } - return Promise.resolve((await response.json()) as TokenResponse | Response); + + return this.loadOpenIDProviderConfiguration(false).then(() => { + return __TODO__(); + }); } /** diff --git a/packages/javascript/src/__legacy__/helpers/authentication-helper.ts b/packages/javascript/src/__legacy__/helpers/authentication-helper.ts index 4553b9011..e7e5df659 100644 --- a/packages/javascript/src/__legacy__/helpers/authentication-helper.ts +++ b/packages/javascript/src/__legacy__/helpers/authentication-helper.ts @@ -238,7 +238,31 @@ export class AuthenticationHelper { public async replaceCustomGrantTemplateTags(text: string, userId?: string): Promise { const configData: StrictAuthClientConfig = await this.config(); - const sessionData: SessionData = await this.storageManager.getSessionData(userId); + + const sourceInstanceId = configData.organizationChain?.sourceInstanceId ?? null; + + let sessionData: SessionData; + + if (sourceInstanceId) { + const clientId = configData.clientId; + let instanceKey: string; + if (clientId) { + instanceKey = `instance_${sourceInstanceId}-${clientId}`; + } else { + instanceKey = `instance_${sourceInstanceId}`; + } + sessionData = await this.storageManager.getSessionData(userId, instanceKey); + + if (!sessionData.access_token) { + throw new AsgardeoAuthException( + 'JS-AUTH_HELPER-RCGTT-NE01', + 'No session data found for source instance.', + 'Failed to retrieve session data from the source organization context.', + ); + } + } else { + sessionData = await this.storageManager.getSessionData(userId); + } const scope: string = processOpenIDScopes(configData.scopes); diff --git a/packages/javascript/src/__legacy__/models/client-config.ts b/packages/javascript/src/__legacy__/models/client-config.ts index 6a5c94b7c..636e41756 100644 --- a/packages/javascript/src/__legacy__/models/client-config.ts +++ b/packages/javascript/src/__legacy__/models/client-config.ts @@ -26,6 +26,18 @@ export interface DefaultAuthClientConfig { clientId?: string; clientSecret?: string; enablePKCE?: boolean; + organizationChain?: { + /** + * Instance ID of the source organization context to retrieve access token from for organization token exchange. + * Used in linked organization scenarios to automatically fetch the source organization's access token. + */ + sourceInstanceId?: string | number; + /** + * Organization ID for the target organization. + * When provided with sourceInstanceId, triggers automatic organization token exchange. + */ + targetOrganizationId?: string; + }; prompt?: string; responseMode?: OAuthResponseMode; scopes?: string | string[] | undefined; diff --git a/packages/javascript/src/models/config.ts b/packages/javascript/src/models/config.ts index 075a13510..c95babc8a 100644 --- a/packages/javascript/src/models/config.ts +++ b/packages/javascript/src/models/config.ts @@ -129,6 +129,24 @@ export interface BaseConfig extends WithPreferences { */ instanceId?: number; + /** + * Configuration for chaining authentication across multiple organization contexts. + * Used when you need to authenticate a user in one organization using credentials + * from another organization context. + */ + organizationChain?: { + /** + * Instance ID of the source organization context to retrieve access token from for organization token exchange. + * Used in linked organization scenarios to automatically fetch the source organization's access token. + */ + sourceInstanceId?: number; + /** + * Organization ID for the target organization. + * When provided with sourceInstanceId, triggers automatic organization token exchange. + */ + targetOrganizationId?: string; + }; + /** * Optional organization handle for the Organization in Asgardeo. * This is used to identify the organization in the Asgardeo identity server in cases like Branding, etc. diff --git a/packages/react/src/AsgardeoReactClient.ts b/packages/react/src/AsgardeoReactClient.ts index eb3d2884d..71931402d 100644 --- a/packages/react/src/AsgardeoReactClient.ts +++ b/packages/react/src/AsgardeoReactClient.ts @@ -276,6 +276,9 @@ class AsgardeoReactClient e override async switchOrganization(organization: Organization): Promise { return this.withLoading(async () => { try { + const configData = await this.asgardeo.getConfigData(); + const sourceInstanceId = configData?.organizationChain?.sourceInstanceId; + if (!organization.id) { throw new AsgardeoRuntimeError( 'Organization ID is required for switching organizations', @@ -296,7 +299,7 @@ class AsgardeoReactClient e }, id: 'organization-switch', returnsSession: true, - signInRequired: true, + signInRequired: sourceInstanceId !== undefined ? false : true , }; return (await this.asgardeo.exchangeToken(exchangeConfig, () => {})) as TokenResponse | Response; diff --git a/packages/react/src/components/control/OrganizationContext.tsx b/packages/react/src/components/control/OrganizationContext.tsx new file mode 100644 index 000000000..c4abaed8c --- /dev/null +++ b/packages/react/src/components/control/OrganizationContext.tsx @@ -0,0 +1,88 @@ +/** + * 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. + */ + +import { FC, PropsWithChildren } from "react"; +import AsgardeoProvider, { AsgardeoProviderProps } from "../../contexts/Asgardeo/AsgardeoProvider"; +import useAsgardeo from "../../contexts/Asgardeo/useAsgardeo"; +import OrganizationContextController from "./OrganizationContextController"; + +interface OrganizationContextProps extends Omit { + /** + * Instance ID for this organization context. Must be unique across the app if multiple contexts are used. + */ + instanceId: number; + /** + * ID of the organization to authenticate with + */ + targetOrganizationId: string; + + /** + * Optional source instance ID. If not provided, immediate parent provider is used as source. + */ + sourceInstanceId?: number; + /** + * Optional base URL for the organization context. If not provided, it will default to the source provider's base URL. + */ + baseUrl?: string; +} + +const OrganizationContext: FC> = ({ + instanceId, + baseUrl, + clientId, + afterSignInUrl, + afterSignOutUrl, + targetOrganizationId, + sourceInstanceId, + scopes, + children, + ...rest +}) => { + // Get the source provider's signed-in status + const { + isSignedIn: isSourceSignedIn, + instanceId: sourceInstanceIdFromContext, + baseUrl: sourceBaseUrl, + clientId: sourceClientId + } = useAsgardeo(); + + return ( + + + {children} + + + ); +}; + +export default OrganizationContext; \ No newline at end of file diff --git a/packages/react/src/components/control/OrganizationContextController.tsx b/packages/react/src/components/control/OrganizationContextController.tsx new file mode 100644 index 000000000..e4c9acc3a --- /dev/null +++ b/packages/react/src/components/control/OrganizationContextController.tsx @@ -0,0 +1,90 @@ +import { FC, useEffect, useRef } from "react"; +import { Organization } from "@asgardeo/browser"; +import useAsgardeo from "../../contexts/Asgardeo/useAsgardeo"; + +interface OrganizationContextControllerProps { + /** + * ID of the organization to authenticate with + */ + targetOrganizationId: string; + + /** + * Whether the source provider is signed in + */ + isSourceSignedIn: boolean; + + /** + * Children to render + */ + children: React.ReactNode; +} + +const OrganizationContextController: FC = ({ + targetOrganizationId, + isSourceSignedIn, + children, +}) => { + const { isInitialized, isSignedIn, switchOrganization, isLoading } = useAsgardeo(); + const hasAuthenticatedRef = useRef(false); + const isAuthenticatingRef = useRef(false); + + /** + * Handle the organization switch when: + * - Current instance is initialized and NOT signed in + * - Source provider IS signed in + * Uses the `switchOrganization` function from the Asgardeo context. + */ + useEffect(() => { + const performOrganizationSwitch = async () => { + // Prevent multiple authentication attempts + if (hasAuthenticatedRef.current || isAuthenticatingRef.current) { + return; + } + + // Wait for initialization to complete + if (!isInitialized || isLoading) { + return; + } + + // Only proceed if user is not already signed in to this instance + if (isSignedIn) { + hasAuthenticatedRef.current = true; + return; + } + + // CRITICAL: Only proceed if source provider is signed in + if (!isSourceSignedIn) { + return; + } + + try { + isAuthenticatingRef.current = true; + hasAuthenticatedRef.current = true; + + // Build the organization object for authentication + const targetOrganization: Organization = { + id: targetOrganizationId, + name: '', // Name will be populated after authentication + orgHandle: '', // Will be populated after authentication + }; + + // Call the switchOrganization API from context (handles token exchange) + await switchOrganization(targetOrganization); + + } catch (error) { + console.error('Linked organization authentication failed:', error); + + // Reset the flag to allow retry + hasAuthenticatedRef.current = false; + } finally { + isAuthenticatingRef.current = false; + } + }; + + performOrganizationSwitch(); + }, [isInitialized, isSignedIn, isLoading, isSourceSignedIn, targetOrganizationId, switchOrganization]); + + return <>{children}; +}; + +export default OrganizationContextController; \ No newline at end of file diff --git a/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts b/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts index 1989b9d5f..20559e2c1 100644 --- a/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts +++ b/packages/react/src/contexts/Asgardeo/AsgardeoContext.ts @@ -37,6 +37,7 @@ export type AsgardeoContextProps = { afterSignInUrl: string | undefined; applicationId: string | undefined; baseUrl: string | undefined; + clientId: string | undefined; /** * Swaps the current access token with a new one based on the provided configuration (with a grant type). * @param config - Configuration for the token exchange request. @@ -169,6 +170,7 @@ const AsgardeoContext: Context = createContext {}, + clientId: undefined, exchangeToken: null, getAccessToken: null, getDecodedIdToken: null, diff --git a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx index 6584d6dca..73f24e92f 100644 --- a/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx +++ b/packages/react/src/contexts/Asgardeo/AsgardeoProvider.tsx @@ -67,6 +67,7 @@ const AsgardeoProvider: FC> = ({ signInOptions, syncSession, instanceId = 0, + organizationChain, ...rest }: PropsWithChildren): ReactElement => { const reRenderCheckRef: RefObject = useRef(false); @@ -88,6 +89,7 @@ const AsgardeoProvider: FC> = ({ applicationId, baseUrl, clientId, + organizationChain, organizationHandle, scopes, signInOptions, @@ -563,6 +565,7 @@ const AsgardeoProvider: FC> = ({ applicationId, baseUrl, clearSession, + clientId, exchangeToken, getAccessToken, getDecodedIdToken, @@ -576,6 +579,7 @@ const AsgardeoProvider: FC> = ({ isLoading: isLoadingSync, isSignedIn: isSignedInSync, organization: currentOrganization, + organizationChain, organizationHandle: config?.organizationHandle, platform: config?.platform, reInitialize, @@ -597,6 +601,7 @@ const AsgardeoProvider: FC> = ({ signUpUrl, afterSignInUrl, baseUrl, + clientId, isInitializedSync, isLoadingSync, isSignedInSync, @@ -618,6 +623,7 @@ const AsgardeoProvider: FC> = ({ clearSession, reInitialize, instanceId, + organizationChain, ], ); diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 4c51b7ac0..c8e1d6a44 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -32,8 +32,8 @@ export * from './contexts/User/UserProvider'; export {default as useUser} from './contexts/User/useUser'; -export {default as OrganizationContext} from './contexts/Organization/OrganizationContext'; -export * from './contexts/Organization/OrganizationContext'; +// export {default as OrganizationContext} from './contexts/Organization/OrganizationContext'; +// export * from './contexts/Organization/OrganizationContext'; export {default as OrganizationProvider} from './contexts/Organization/OrganizationProvider'; export * from './contexts/Organization/OrganizationProvider'; @@ -109,6 +109,9 @@ export * from './components/control/SignedOut'; export {default as Loading} from './components/control/Loading'; export * from './components/control/Loading'; +export {default as OrganizationContext} from './components/control/OrganizationContext'; +export * from './components/control/OrganizationContext'; + export {default as BaseSignIn} from './components/presentation/auth/SignIn/BaseSignIn'; export * from './components/presentation/auth/SignIn/BaseSignIn'; From 758695e59b6fd24faea6b2dd676d35cdaba8d4cc Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Fri, 20 Feb 2026 10:54:30 +0530 Subject: [PATCH 2/5] Fix lint issues in javascript and react packages --- packages/javascript/src/StorageManager.ts | 9 ++-- packages/javascript/src/__legacy__/client.ts | 16 ++---- .../helpers/authentication-helper.ts | 4 +- packages/react/src/AsgardeoReactClient.ts | 6 +-- .../control/OrganizationContext.tsx | 42 +++++++--------- .../control/OrganizationContextController.tsx | 50 ++++++++++++------- packages/react/src/index.ts | 3 -- 7 files changed, 67 insertions(+), 63 deletions(-) diff --git a/packages/javascript/src/StorageManager.ts b/packages/javascript/src/StorageManager.ts index 0d86707aa..d27d353c8 100644 --- a/packages/javascript/src/StorageManager.ts +++ b/packages/javascript/src/StorageManager.ts @@ -78,13 +78,14 @@ class StorageManager { protected resolveKey(store: Stores | string, userId?: string, instanceId?: string): string { if (userId && instanceId) { return `${store}-${instanceId}-${userId}`; - } else if (userId) { + } + if (userId) { return `${store}-${this.id}-${userId}`; - } else if (instanceId) { + } + if (instanceId) { return `${store}-${instanceId}`; - } else { - return `${store}-${this.id}`; } + return `${store}-${this.id}`; } protected static isLocalStorageAvailable(): boolean { diff --git a/packages/javascript/src/__legacy__/client.ts b/packages/javascript/src/__legacy__/client.ts index 3d0a55725..d96b4b3e2 100644 --- a/packages/javascript/src/__legacy__/client.ts +++ b/packages/javascript/src/__legacy__/client.ts @@ -914,7 +914,7 @@ export class AsgardeoAuthClient { * @preserve */ public async exchangeToken(config: TokenExchangeRequestConfig, userId?: string): Promise { - const __TODO__ = async () => { + const executeTokenExchange = async (): Promise => { const oidcProviderMetadata: OIDCDiscoveryApiResponse = await this.oidcProviderMetaDataProvider(); const configData: StrictAuthClientConfig = await this.configProvider(); @@ -937,10 +937,7 @@ export class AsgardeoAuthClient { const data: string[] = await Promise.all( Object.entries(config.data).map(async ([key, value]: [key: string, value: any]) => { - const newValue: string = await this.authHelper.replaceCustomGrantTemplateTags( - value as string, - userId, - ); + const newValue: string = await this.authHelper.replaceCustomGrantTemplateTags(value as string, userId); return `${key}=${newValue}`; }), @@ -987,9 +984,8 @@ export class AsgardeoAuthClient { if (config.returnsSession) { return this.authHelper.handleTokenResponse(response, userId); - } else { - return Promise.resolve((await response.json()) as TokenResponse | Response); } + return Promise.resolve((await response.json()) as TokenResponse | Response); }; if ( @@ -997,12 +993,10 @@ export class AsgardeoAuthClient { OIDCDiscoveryConstants.Storage.StorageKeys.OPENID_PROVIDER_CONFIG_INITIATED, ) ) { - return __TODO__(); + return executeTokenExchange(); } - return this.loadOpenIDProviderConfiguration(false).then(() => { - return __TODO__(); - }); + return this.loadOpenIDProviderConfiguration(false).then(() => executeTokenExchange()); } /** diff --git a/packages/javascript/src/__legacy__/helpers/authentication-helper.ts b/packages/javascript/src/__legacy__/helpers/authentication-helper.ts index e7e5df659..e0ca352f1 100644 --- a/packages/javascript/src/__legacy__/helpers/authentication-helper.ts +++ b/packages/javascript/src/__legacy__/helpers/authentication-helper.ts @@ -239,12 +239,12 @@ export class AuthenticationHelper { public async replaceCustomGrantTemplateTags(text: string, userId?: string): Promise { const configData: StrictAuthClientConfig = await this.config(); - const sourceInstanceId = configData.organizationChain?.sourceInstanceId ?? null; + const sourceInstanceId: string | number | null = configData.organizationChain?.sourceInstanceId ?? null; let sessionData: SessionData; if (sourceInstanceId) { - const clientId = configData.clientId; + const {clientId} = configData; let instanceKey: string; if (clientId) { instanceKey = `instance_${sourceInstanceId}-${clientId}`; diff --git a/packages/react/src/AsgardeoReactClient.ts b/packages/react/src/AsgardeoReactClient.ts index 71931402d..e9655beec 100644 --- a/packages/react/src/AsgardeoReactClient.ts +++ b/packages/react/src/AsgardeoReactClient.ts @@ -276,8 +276,8 @@ class AsgardeoReactClient e override async switchOrganization(organization: Organization): Promise { return this.withLoading(async () => { try { - const configData = await this.asgardeo.getConfigData(); - const sourceInstanceId = configData?.organizationChain?.sourceInstanceId; + const configData: any = await this.asgardeo.getConfigData(); + const sourceInstanceId: number | undefined = configData?.organizationChain?.sourceInstanceId; if (!organization.id) { throw new AsgardeoRuntimeError( @@ -299,7 +299,7 @@ class AsgardeoReactClient e }, id: 'organization-switch', returnsSession: true, - signInRequired: sourceInstanceId !== undefined ? false : true , + signInRequired: sourceInstanceId === undefined, }; return (await this.asgardeo.exchangeToken(exchangeConfig, () => {})) as TokenResponse | Response; diff --git a/packages/react/src/components/control/OrganizationContext.tsx b/packages/react/src/components/control/OrganizationContext.tsx index c4abaed8c..4418771d7 100644 --- a/packages/react/src/components/control/OrganizationContext.tsx +++ b/packages/react/src/components/control/OrganizationContext.tsx @@ -16,29 +16,28 @@ * under the License. */ -import { FC, PropsWithChildren } from "react"; -import AsgardeoProvider, { AsgardeoProviderProps } from "../../contexts/Asgardeo/AsgardeoProvider"; -import useAsgardeo from "../../contexts/Asgardeo/useAsgardeo"; -import OrganizationContextController from "./OrganizationContextController"; +import {FC, PropsWithChildren} from 'react'; +import OrganizationContextController from './OrganizationContextController'; +import AsgardeoProvider, {AsgardeoProviderProps} from '../../contexts/Asgardeo/AsgardeoProvider'; +import useAsgardeo from '../../contexts/Asgardeo/useAsgardeo'; -interface OrganizationContextProps extends Omit { +export interface OrganizationContextProps extends Omit { /** - * Instance ID for this organization context. Must be unique across the app if multiple contexts are used. + * Optional base URL for the organization context. If not provided, it will default to the source provider's base URL. */ - instanceId: number; + baseUrl?: string; /** - * ID of the organization to authenticate with + * Instance ID for this organization context. Must be unique across the app if multiple contexts are used. */ - targetOrganizationId: string; - + instanceId: number; /** * Optional source instance ID. If not provided, immediate parent provider is used as source. */ sourceInstanceId?: number; /** - * Optional base URL for the organization context. If not provided, it will default to the source provider's base URL. + * ID of the organization to authenticate with */ - baseUrl?: string; + targetOrganizationId: string; } const OrganizationContext: FC> = ({ @@ -52,13 +51,13 @@ const OrganizationContext: FC> = ({ scopes, children, ...rest -}) => { +}: PropsWithChildren) => { // Get the source provider's signed-in status - const { + const { isSignedIn: isSourceSignedIn, - instanceId: sourceInstanceIdFromContext, - baseUrl: sourceBaseUrl, - clientId: sourceClientId + instanceId: sourceInstanceIdFromContext, + baseUrl: sourceBaseUrl, + clientId: sourceClientId, } = useAsgardeo(); return ( @@ -71,18 +70,15 @@ const OrganizationContext: FC> = ({ scopes={scopes} organizationChain={{ sourceInstanceId: sourceInstanceId || sourceInstanceIdFromContext, - targetOrganizationId: targetOrganizationId, + targetOrganizationId, }} {...rest} > - + {children} ); }; -export default OrganizationContext; \ No newline at end of file +export default OrganizationContext; diff --git a/packages/react/src/components/control/OrganizationContextController.tsx b/packages/react/src/components/control/OrganizationContextController.tsx index e4c9acc3a..416266825 100644 --- a/packages/react/src/components/control/OrganizationContextController.tsx +++ b/packages/react/src/components/control/OrganizationContextController.tsx @@ -1,32 +1,48 @@ -import { FC, useEffect, useRef } from "react"; -import { Organization } from "@asgardeo/browser"; -import useAsgardeo from "../../contexts/Asgardeo/useAsgardeo"; +/** + * 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. + */ + +import {Organization} from '@asgardeo/browser'; +import {FC, useEffect, useRef} from 'react'; +import useAsgardeo from '../../contexts/Asgardeo/useAsgardeo'; interface OrganizationContextControllerProps { /** - * ID of the organization to authenticate with + * Children to render */ - targetOrganizationId: string; - + children: React.ReactNode; /** * Whether the source provider is signed in */ isSourceSignedIn: boolean; - /** - * Children to render + * ID of the organization to authenticate with */ - children: React.ReactNode; + targetOrganizationId: string; } const OrganizationContextController: FC = ({ targetOrganizationId, isSourceSignedIn, children, -}) => { - const { isInitialized, isSignedIn, switchOrganization, isLoading } = useAsgardeo(); - const hasAuthenticatedRef = useRef(false); - const isAuthenticatingRef = useRef(false); +}: OrganizationContextControllerProps) => { + const {isInitialized, isSignedIn, switchOrganization, isLoading} = useAsgardeo(); + const hasAuthenticatedRef: React.MutableRefObject = useRef(false); + const isAuthenticatingRef: React.MutableRefObject = useRef(false); /** * Handle the organization switch when: @@ -35,7 +51,7 @@ const OrganizationContextController: FC = ({ * Uses the `switchOrganization` function from the Asgardeo context. */ useEffect(() => { - const performOrganizationSwitch = async () => { + const performOrganizationSwitch = async (): Promise => { // Prevent multiple authentication attempts if (hasAuthenticatedRef.current || isAuthenticatingRef.current) { return; @@ -70,10 +86,10 @@ const OrganizationContextController: FC = ({ // Call the switchOrganization API from context (handles token exchange) await switchOrganization(targetOrganization); - } catch (error) { + // eslint-disable-next-line no-console console.error('Linked organization authentication failed:', error); - + // Reset the flag to allow retry hasAuthenticatedRef.current = false; } finally { @@ -87,4 +103,4 @@ const OrganizationContextController: FC = ({ return <>{children}; }; -export default OrganizationContextController; \ No newline at end of file +export default OrganizationContextController; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index c8e1d6a44..d4a1a928a 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -32,9 +32,6 @@ export * from './contexts/User/UserProvider'; export {default as useUser} from './contexts/User/useUser'; -// export {default as OrganizationContext} from './contexts/Organization/OrganizationContext'; -// export * from './contexts/Organization/OrganizationContext'; - export {default as OrganizationProvider} from './contexts/Organization/OrganizationProvider'; export * from './contexts/Organization/OrganizationProvider'; From 283de289e8a93ceac887bb2394966924bba314d2 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Fri, 20 Feb 2026 14:34:29 +0530 Subject: [PATCH 3/5] Group OrganizationContext and OrganizationContextController components into a single directory --- .../control/{ => OrganizationContext}/OrganizationContext.tsx | 4 ++-- .../OrganizationContextController.tsx | 2 +- packages/react/src/index.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename packages/react/src/components/control/{ => OrganizationContext}/OrganizationContext.tsx (93%) rename packages/react/src/components/control/{ => OrganizationContext}/OrganizationContextController.tsx (98%) diff --git a/packages/react/src/components/control/OrganizationContext.tsx b/packages/react/src/components/control/OrganizationContext/OrganizationContext.tsx similarity index 93% rename from packages/react/src/components/control/OrganizationContext.tsx rename to packages/react/src/components/control/OrganizationContext/OrganizationContext.tsx index 4418771d7..890d1879a 100644 --- a/packages/react/src/components/control/OrganizationContext.tsx +++ b/packages/react/src/components/control/OrganizationContext/OrganizationContext.tsx @@ -18,8 +18,8 @@ import {FC, PropsWithChildren} from 'react'; import OrganizationContextController from './OrganizationContextController'; -import AsgardeoProvider, {AsgardeoProviderProps} from '../../contexts/Asgardeo/AsgardeoProvider'; -import useAsgardeo from '../../contexts/Asgardeo/useAsgardeo'; +import AsgardeoProvider, {AsgardeoProviderProps} from '../../../contexts/Asgardeo/AsgardeoProvider'; +import useAsgardeo from '../../../contexts/Asgardeo/useAsgardeo'; export interface OrganizationContextProps extends Omit { /** diff --git a/packages/react/src/components/control/OrganizationContextController.tsx b/packages/react/src/components/control/OrganizationContext/OrganizationContextController.tsx similarity index 98% rename from packages/react/src/components/control/OrganizationContextController.tsx rename to packages/react/src/components/control/OrganizationContext/OrganizationContextController.tsx index 416266825..c2a4cdb90 100644 --- a/packages/react/src/components/control/OrganizationContextController.tsx +++ b/packages/react/src/components/control/OrganizationContext/OrganizationContextController.tsx @@ -18,7 +18,7 @@ import {Organization} from '@asgardeo/browser'; import {FC, useEffect, useRef} from 'react'; -import useAsgardeo from '../../contexts/Asgardeo/useAsgardeo'; +import useAsgardeo from '../../../contexts/Asgardeo/useAsgardeo'; interface OrganizationContextControllerProps { /** diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index d4a1a928a..e2930f71e 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -106,8 +106,8 @@ export * from './components/control/SignedOut'; export {default as Loading} from './components/control/Loading'; export * from './components/control/Loading'; -export {default as OrganizationContext} from './components/control/OrganizationContext'; -export * from './components/control/OrganizationContext'; +export {default as OrganizationContext} from './components/control/OrganizationContext/OrganizationContext'; +export * from './components/control/OrganizationContext/OrganizationContext'; export {default as BaseSignIn} from './components/presentation/auth/SignIn/BaseSignIn'; export * from './components/presentation/auth/SignIn/BaseSignIn'; From 6091a45b9658b97017135e62d3f2c4f426abdc01 Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Mon, 23 Feb 2026 11:57:10 +0530 Subject: [PATCH 4/5] Remove unnecessary validation call in exchangeToken method --- packages/browser/src/__legacy__/client.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/browser/src/__legacy__/client.ts b/packages/browser/src/__legacy__/client.ts index dc5aea82e..34c885651 100755 --- a/packages/browser/src/__legacy__/client.ts +++ b/packages/browser/src/__legacy__/client.ts @@ -718,8 +718,6 @@ export class AsgardeoSPAClient { public async exchangeToken(config: TokenExchangeRequestConfig): Promise { if (config.signInRequired) { await this._validateMethod(); - } else { - // await this._validateMethod(); } if (!config.id) { From f36dab18cc02a0c297fbc944cbd4c05df55563ed Mon Sep 17 00:00:00 2001 From: kavindadewmith Date: Tue, 24 Feb 2026 14:48:40 +0530 Subject: [PATCH 5/5] Update instanceID to instanceId for consistency across message, utils, and worker files - Changed all occurrences of instanceID to instanceId in message.ts, spa-utils.ts, worker-core.ts, and worker-receiver.ts for uniformity. - Added instanceId property to the Message interface in message.ts. --- packages/browser/src/__legacy__/client.ts | 10 +++++----- .../src/__legacy__/clients/main-thread-client.ts | 8 ++++---- .../src/__legacy__/clients/web-worker-client.ts | 15 ++++++++------- .../__legacy__/helpers/authentication-helper.ts | 6 +++--- packages/browser/src/__legacy__/models/message.ts | 1 + .../browser/src/__legacy__/utils/spa-utils.ts | 8 ++++---- .../browser/src/__legacy__/worker/worker-core.ts | 6 +++--- .../src/__legacy__/worker/worker-receiver.ts | 6 +++--- 8 files changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/browser/src/__legacy__/client.ts b/packages/browser/src/__legacy__/client.ts index 34c885651..06f0a88fe 100755 --- a/packages/browser/src/__legacy__/client.ts +++ b/packages/browser/src/__legacy__/client.ts @@ -87,10 +87,10 @@ export class AsgardeoSPAClient { protected _onEndUserSession: (response: any) => void = () => null; protected _onInitialize: (response: boolean) => void = () => null; protected _onCustomGrant: Map void> = new Map(); - protected _instanceID: number; + protected _instanceId: number; protected constructor(id: number) { - this._instanceID = id; + this._instanceId = id; } public instantiateAuthHelper(authHelper?: typeof AuthenticationHelper): void { @@ -309,7 +309,7 @@ export class AsgardeoSPAClient { * @preserve */ public getInstanceId(): number { - return this._instanceID; + return this._instanceId; } /** @@ -364,7 +364,7 @@ export class AsgardeoSPAClient { // Tracker: https://github.com/asgardeo/asgardeo-auth-react-sdk/issues/240 if (!this._client || (this._client && (!_config || Object.keys(_config)?.length === 0))) { this._client = await MainThreadClient( - this._instanceID, + this._instanceId, mergedConfig, (authClient: AsgardeoAuthClient, spaHelper: SPAHelper) => { return new this._authHelper(authClient, spaHelper); @@ -399,7 +399,7 @@ export class AsgardeoSPAClient { if (!this._client || (this._client && (!_config || Object.keys(_config)?.length === 0))) { const webWorkerClientConfig = config as AuthClientConfig; this._client = (await WebWorkerClient( - this._instanceID, + this._instanceId, { ...DefaultConfig, ...webWorkerClientConfig, diff --git a/packages/browser/src/__legacy__/clients/main-thread-client.ts b/packages/browser/src/__legacy__/clients/main-thread-client.ts index 79900c12b..8802c2229 100755 --- a/packages/browser/src/__legacy__/clients/main-thread-client.ts +++ b/packages/browser/src/__legacy__/clients/main-thread-client.ts @@ -57,7 +57,7 @@ const initiateStore = (store: BrowserStorage | undefined): Storage => { }; export const MainThreadClient = async ( - instanceID: number, + instanceId: number, config: AuthClientConfig, getAuthHelper: ( authClient: AsgardeoAuthClient, @@ -67,7 +67,7 @@ export const MainThreadClient = async ( const _store: Storage = initiateStore(config.storage as BrowserStorage); const _cryptoUtils: SPACryptoUtils = new SPACryptoUtils(); const _authenticationClient = new AsgardeoAuthClient(); - await _authenticationClient.initialize(config, _store, _cryptoUtils, instanceID); + await _authenticationClient.initialize(config, _store, _cryptoUtils, instanceId); const _spaHelper = new SPAHelper(_authenticationClient); const _dataLayer = _authenticationClient.getStorageManager(); @@ -85,7 +85,7 @@ export const MainThreadClient = async ( let _getSignOutURLFromSessionStorage: boolean = false; - const _httpClient: HttpClientInstance = HttpClient.getInstance(instanceID); + const _httpClient: HttpClientInstance = HttpClient.getInstance(instanceId); let _isHttpHandlerEnabled: boolean = true; let _httpErrorCallback: (error: HttpError) => void | Promise; let _httpFinishCallback: () => void; @@ -261,7 +261,7 @@ export const MainThreadClient = async ( if ((await _authenticationClient.isSignedIn()) && !_getSignOutURLFromSessionStorage) { location.href = await _authenticationClient.getSignOutUrl(); } else { - location.href = SPAUtils.getSignOutUrl(config.clientId, instanceID); + location.href = SPAUtils.getSignOutUrl(config.clientId, instanceId); } _spaHelper.clearRefreshTokenTimeout(); diff --git a/packages/browser/src/__legacy__/clients/web-worker-client.ts b/packages/browser/src/__legacy__/clients/web-worker-client.ts index 0ce4518d1..ad981ec9d 100755 --- a/packages/browser/src/__legacy__/clients/web-worker-client.ts +++ b/packages/browser/src/__legacy__/clients/web-worker-client.ts @@ -92,7 +92,7 @@ const initiateStore = (store: BrowserStorage | undefined): Storage => { }; export const WebWorkerClient = async ( - instanceID: number, + instanceId: number, config: AuthClientConfig, webWorker: new () => Worker, getAuthHelper: ( @@ -114,7 +114,7 @@ export const WebWorkerClient = async ( const _store: Storage = initiateStore(config.storage as BrowserStorage); const _cryptoUtils: SPACryptoUtils = new SPACryptoUtils(); const _authenticationClient = new AsgardeoAuthClient(); - await _authenticationClient.initialize(config, _store, _cryptoUtils, instanceID); + await _authenticationClient.initialize(config, _store, _cryptoUtils, instanceId); const _spaHelper = new SPAHelper(_authenticationClient); const _sessionManagementHelper = await SessionManagementHelper( @@ -128,7 +128,7 @@ export const WebWorkerClient = async ( return signOutURL; } catch { - return SPAUtils.getSignOutUrl(config.clientId, instanceID); + return SPAUtils.getSignOutUrl(config.clientId, instanceId); } }, config.storage as BrowserStorage, @@ -365,8 +365,9 @@ export const WebWorkerClient = async ( } }; - const message: Message & {instanceID: number}> = { - data: {...config, instanceID}, + const message: Message> = { + data: config, + instanceId, type: INIT, }; @@ -524,7 +525,7 @@ export const WebWorkerClient = async ( return communicate(message) .then((url: string) => { - SPAUtils.setSignOutURL(url, config.clientId, instanceID); + SPAUtils.setSignOutURL(url, config.clientId, instanceId); // Enable OIDC Sessions Management only if it is set to true in the config. if (config.syncSession) { @@ -656,7 +657,7 @@ export const WebWorkerClient = async ( return reject(error); }); } else { - window.location.href = SPAUtils.getSignOutUrl(config.clientId, instanceID); + window.location.href = SPAUtils.getSignOutUrl(config.clientId, instanceId); return SPAUtils.waitTillPageRedirect().then(() => { return Promise.resolve(true); diff --git a/packages/browser/src/__legacy__/helpers/authentication-helper.ts b/packages/browser/src/__legacy__/helpers/authentication-helper.ts index 4309075d7..7511dda1b 100644 --- a/packages/browser/src/__legacy__/helpers/authentication-helper.ts +++ b/packages/browser/src/__legacy__/helpers/authentication-helper.ts @@ -63,14 +63,14 @@ export class AuthenticationHelper; protected _storageManager: StorageManager; protected _spaHelper: SPAHelper; - protected _instanceID: number; + protected _instanceId: number; protected _isTokenRefreshing: boolean; public constructor(authClient: AsgardeoAuthClient, spaHelper: SPAHelper) { this._authenticationClient = authClient; this._storageManager = this._authenticationClient.getStorageManager(); this._spaHelper = spaHelper; - this._instanceID = this._authenticationClient.getInstanceId(); + this._instanceId = this._authenticationClient.getInstanceId(); this._isTokenRefreshing = false; } @@ -486,7 +486,7 @@ export class AuthenticationHelper { export interface Message { type: MessageType; data?: T; + instanceId?: number; } export interface AuthorizationInfo { diff --git a/packages/browser/src/__legacy__/utils/spa-utils.ts b/packages/browser/src/__legacy__/utils/spa-utils.ts index bf35ff957..7122a09cc 100644 --- a/packages/browser/src/__legacy__/utils/spa-utils.ts +++ b/packages/browser/src/__legacy__/utils/spa-utils.ts @@ -45,17 +45,17 @@ export class SPAUtils { sessionStorage.setItem(pkceKey, pkce); } - public static setSignOutURL(url: string, clientId: string, instanceID: number): void { + public static setSignOutURL(url: string, clientId: string, instanceId: number): void { sessionStorage.setItem( - `${OIDCRequestConstants.SignOut.Storage.StorageKeys.SIGN_OUT_URL}-instance_${instanceID}-${clientId}`, + `${OIDCRequestConstants.SignOut.Storage.StorageKeys.SIGN_OUT_URL}-instance_${instanceId}-${clientId}`, url, ); } - public static getSignOutUrl(clientId: string, instanceID: number): string { + public static getSignOutUrl(clientId: string, instanceId: number): string { return ( sessionStorage.getItem( - `${OIDCRequestConstants.SignOut.Storage.StorageKeys.SIGN_OUT_URL}-instance_${instanceID}-${clientId}`, + `${OIDCRequestConstants.SignOut.Storage.StorageKeys.SIGN_OUT_URL}-instance_${instanceId}-${clientId}`, ) ?? '' ); } diff --git a/packages/browser/src/__legacy__/worker/worker-core.ts b/packages/browser/src/__legacy__/worker/worker-core.ts index 6fc518a46..047b82053 100755 --- a/packages/browser/src/__legacy__/worker/worker-core.ts +++ b/packages/browser/src/__legacy__/worker/worker-core.ts @@ -42,7 +42,7 @@ import {MemoryStore} from '../stores'; import {SPACryptoUtils} from '../utils/crypto-utils'; export const WebWorkerCore = async ( - instanceID: number, + instanceId: number, config: AuthClientConfig, getAuthHelper: ( authClient: AsgardeoAuthClient, @@ -52,7 +52,7 @@ export const WebWorkerCore = async ( const _store: Storage = new MemoryStore(); const _cryptoUtils: SPACryptoUtils = new SPACryptoUtils(); const _authenticationClient = new AsgardeoAuthClient(); - await _authenticationClient.initialize(config, _store, _cryptoUtils, instanceID); + await _authenticationClient.initialize(config, _store, _cryptoUtils, instanceId); const _spaHelper = new SPAHelper(_authenticationClient); @@ -63,7 +63,7 @@ export const WebWorkerCore = async ( const _dataLayer = _authenticationClient.getStorageManager(); - const _httpClient: HttpClientInstance = HttpClient.getInstance(instanceID); + const _httpClient: HttpClientInstance = HttpClient.getInstance(instanceId); const attachToken = async (request: HttpRequestConfig): Promise => { await _authenticationHelper.attachTokenToRequestConfig(request); diff --git a/packages/browser/src/__legacy__/worker/worker-receiver.ts b/packages/browser/src/__legacy__/worker/worker-receiver.ts index ac75e7e78..074e815ce 100644 --- a/packages/browser/src/__legacy__/worker/worker-receiver.ts +++ b/packages/browser/src/__legacy__/worker/worker-receiver.ts @@ -84,9 +84,9 @@ export const workerReceiver = ( switch (data.type) { case INIT: try { - const {instanceID = 0, ...configData} = data.data; - const config: AuthClientConfig = {...configData}; - webWorker = await WebWorkerCore(instanceID, config, getAuthHelper); + const instanceId: number = data.instanceId ?? 0; + const config: AuthClientConfig = {...data.data}; + webWorker = await WebWorkerCore(instanceId, config, getAuthHelper); webWorker.setHttpRequestFinishCallback(onRequestFinishCallback); webWorker.setHttpRequestStartCallback(onRequestStartCallback); webWorker.setHttpRequestSuccessCallback(onRequestSuccessCallback);