Skip to content

Commit 4a36906

Browse files
authored
Merge pull request #274 from brionmario/fix-asgardeo-v2-signup
fix: add ability to call external endpoints without explicitly allowing them in non `webWorker` storage modes
2 parents c270577 + d632b69 commit 4a36906

File tree

7 files changed

+80
-34
lines changed

7 files changed

+80
-34
lines changed

.changeset/afraid-buckets-jog.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+
Add ability to call external endpoints without explicitly allowing them in non `webWorker` storage modes

packages/browser/src/__legacy__/helpers/authentication-helper.ts

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
OIDCEndpoints,
3030
TokenResponse,
3131
extractPkceStorageKeyFromState,
32+
Config,
3233
} from '@asgardeo/javascript';
3334
import {SPAHelper} from './spa-helper';
3435
import {
@@ -103,26 +104,31 @@ export class AuthenticationHelper<T extends MainThreadClientConfig | WebWorkerCl
103104
config: SPACustomGrantConfig,
104105
enableRetrievingSignOutURLFromSession?: (config: SPACustomGrantConfig) => void,
105106
): Promise<User | Response> {
107+
const _config: Config = (await this._storageManager.getConfigData()) as Config;
106108
let useDefaultEndpoint = true;
107109
let matches = false;
108110

109111
// If the config does not contains a token endpoint, default token endpoint will be used.
110112
if (config?.tokenEndpoint) {
111113
useDefaultEndpoint = false;
112114

113-
for (const baseUrl of [
114-
...((await this._storageManager.getConfigData())?.resourceServerURLs ?? []),
115-
(config as any).baseUrl,
116-
]) {
117-
if (baseUrl && config.tokenEndpoint?.startsWith(baseUrl)) {
118-
matches = true;
119-
break;
115+
// Only validate URLs for WebWorker storage
116+
if (_config.storage === BrowserStorage.WebWorker) {
117+
for (const baseUrl of [...(_config?.allowedExternalUrls ?? []), (config as any).baseUrl]) {
118+
if (baseUrl && config.tokenEndpoint?.startsWith(baseUrl)) {
119+
matches = true;
120+
break;
121+
}
120122
}
123+
} else {
124+
matches = true;
121125
}
122126
}
127+
123128
if (config.shouldReplayAfterRefresh) {
124129
this._storageManager.setTemporaryDataParameter(CUSTOM_GRANT_CONFIG, JSON.stringify(config));
125130
}
131+
126132
if (useDefaultEndpoint || matches) {
127133
return this._authenticationClient
128134
.exchangeToken(config)
@@ -147,9 +153,9 @@ export class AuthenticationHelper<T extends MainThreadClientConfig | WebWorkerCl
147153
new AsgardeoAuthException(
148154
'SPA-MAIN_THREAD_CLIENT-RCG-IV01',
149155
'Request to the provided endpoint is prohibited.',
150-
'Requests can only be sent to resource servers specified by the `resourceServerURLs`' +
156+
'Requests can only be sent to resource servers specified by the `allowedExternalUrls`' +
151157
' attribute while initializing the SDK. The specified token endpoint in this request ' +
152-
'cannot be found among the `resourceServerURLs`',
158+
'cannot be found among the `allowedExternalUrls`',
153159
),
154160
);
155161
}
@@ -224,14 +230,19 @@ export class AuthenticationHelper<T extends MainThreadClientConfig | WebWorkerCl
224230
enableRetrievingSignOutURLFromSession?: (config: SPACustomGrantConfig) => void,
225231
): Promise<HttpResponse> {
226232
let matches = false;
227-
const config = await this._storageManager.getConfigData();
233+
const config: Config = (await this._storageManager.getConfigData()) as Config;
228234

229-
for (const baseUrl of [...((await config?.resourceServerURLs) ?? []), (config as any).baseUrl]) {
230-
if (baseUrl && requestConfig?.url?.startsWith(baseUrl)) {
231-
matches = true;
235+
// Only validate URLs for WebWorker storage
236+
if (config.storage === BrowserStorage.WebWorker) {
237+
for (const baseUrl of [...(config?.allowedExternalUrls ?? []), (config as any).baseUrl]) {
238+
if (baseUrl && requestConfig?.url?.startsWith(baseUrl)) {
239+
matches = true;
232240

233-
break;
241+
break;
242+
}
234243
}
244+
} else {
245+
matches = true;
235246
}
236247

237248
if (matches) {
@@ -319,9 +330,9 @@ export class AuthenticationHelper<T extends MainThreadClientConfig | WebWorkerCl
319330
new AsgardeoAuthException(
320331
'SPA-AUTH_HELPER-HR-IV02',
321332
'Request to the provided endpoint is prohibited.',
322-
'Requests can only be sent to resource servers specified by the `resourceServerURLs`' +
333+
'Requests can only be sent to resource servers specified by the `allowedExternalUrls`' +
323334
' attribute while initializing the SDK. The specified endpoint in this request ' +
324-
'cannot be found among the `resourceServerURLs`',
335+
'cannot be found among the `allowedExternalUrls`',
325336
),
326337
);
327338
}
@@ -335,23 +346,26 @@ export class AuthenticationHelper<T extends MainThreadClientConfig | WebWorkerCl
335346
httpFinishCallback?: () => void,
336347
): Promise<HttpResponse[] | undefined> {
337348
let matches = true;
338-
const config = await this._storageManager.getConfigData();
349+
const config: Config = (await this._storageManager.getConfigData()) as Config;
339350

340-
for (const requestConfig of requestConfigs) {
341-
let urlMatches = false;
351+
// Only validate URLs for WebWorker storage
352+
if (config.storage === BrowserStorage.WebWorker) {
353+
for (const requestConfig of requestConfigs) {
354+
let urlMatches = false;
342355

343-
for (const baseUrl of [...((await config)?.resourceServerURLs ?? []), (config as any).baseUrl]) {
344-
if (baseUrl && requestConfig.url?.startsWith(baseUrl)) {
345-
urlMatches = true;
356+
for (const baseUrl of [...(config?.allowedExternalUrls ?? []), (config as any).baseUrl]) {
357+
if (baseUrl && requestConfig.url?.startsWith(baseUrl)) {
358+
urlMatches = true;
346359

347-
break;
360+
break;
361+
}
348362
}
349-
}
350363

351-
if (!urlMatches) {
352-
matches = false;
364+
if (!urlMatches) {
365+
matches = false;
353366

354-
break;
367+
break;
368+
}
355369
}
356370
}
357371

@@ -436,9 +450,9 @@ export class AuthenticationHelper<T extends MainThreadClientConfig | WebWorkerCl
436450
throw new AsgardeoAuthException(
437451
'SPA-AUTH_HELPER-HRA-IV02',
438452
'Request to the provided endpoint is prohibited.',
439-
'Requests can only be sent to resource servers specified by the `resourceServerURLs`' +
453+
'Requests can only be sent to resource servers specified by the `allowedExternalUrls`' +
440454
' attribute while initializing the SDK. The specified endpoint in this request ' +
441-
'cannot be found among the `resourceServerURLs`',
455+
'cannot be found among the `allowedExternalUrls`',
442456
);
443457
}
444458
}

packages/browser/src/__legacy__/models/client-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface SPAConfig {
2828
syncSession?: boolean;
2929
checkSessionInterval?: number;
3030
sessionRefreshInterval?: number;
31-
resourceServerURLs?: string[];
31+
allowedExternalUrls?: string[];
3232
authParams?: Record<string, string>;
3333
periodicTokenRefresh?: boolean;
3434
autoLogoutOnTokenRefreshError?: boolean;

packages/javascript/src/models/config.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,24 @@ export interface BaseConfig<T = unknown> extends WithPreferences {
7878
*/
7979
afterSignOutUrl?: string | undefined;
8080

81+
/**
82+
* A list of external API base URLs that the SDK is allowed to attach access tokens to when making HTTP requests.
83+
*
84+
* When making authenticated HTTP requests using the SDK's HTTP client, the access token will only be attached
85+
* to requests whose URLs start with one of these specified base URLs. This provides a security layer by
86+
* preventing tokens from being sent to unauthorized servers.
87+
*
88+
* @remarks
89+
* - This is only applicable when the storage type is `webWorker`.
90+
* - Each URL should be a base URL without trailing slashes (e.g., "https://api.example.com").
91+
* - The SDK will check if the request URL starts with any of these base URLs before attaching the token.
92+
* - If a request is made to a URL that doesn't match any of these base URLs, an error will be thrown.
93+
*
94+
* @example
95+
* allowedExternalUrls: ["https://api.example.com", "https://api.another-service.com"]
96+
*/
97+
allowedExternalUrls?: string[];
98+
8199
/**
82100
* Optional organization handle for the Organization in Asgardeo.
83101
* This is used to identify the organization in the Asgardeo identity server in cases like Branding, etc.

packages/react/src/contexts/Asgardeo/AsgardeoContext.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export type AsgardeoContextProps = {
139139
*/
140140
reInitialize: (config: Partial<AsgardeoReactConfig>) => Promise<boolean>;
141141
} & Pick<AsgardeoReactConfig, 'storage' | 'platform'> &
142-
Pick<AsgardeoReactClient, 'clearSession'>;
142+
Pick<AsgardeoReactClient, 'clearSession' | 'switchOrganization'>;
143143

144144
/**
145145
* Context object for managing the Authentication flow builder core context.
@@ -170,6 +170,7 @@ const AsgardeoContext: Context<AsgardeoContextProps | null> = createContext<null
170170
getAccessToken: null,
171171
exchangeToken: null,
172172
storage: 'sessionStorage',
173+
switchOrganization: null,
173174
reInitialize: null,
174175
platform: undefined,
175176
});

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
Platform,
3232
extractUserClaimsFromIdToken,
3333
EmbeddedSignInFlowResponseV2,
34+
TokenResponse,
3435
} from '@asgardeo/browser';
3536
import {FC, RefObject, PropsWithChildren, ReactElement, useEffect, useMemo, useRef, useState, useCallback} from 'react';
3637
import AsgardeoContext from './AsgardeoContext';
@@ -459,15 +460,17 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
459460
}
460461
};
461462

462-
const switchOrganization = async (organization: Organization): Promise<void> => {
463+
const switchOrganization = async (organization: Organization): Promise<TokenResponse | Response> => {
463464
try {
464465
setIsUpdatingSession(true);
465466
setIsLoadingSync(true);
466-
await asgardeo.switchOrganization(organization);
467+
const response: TokenResponse | Response = await asgardeo.switchOrganization(organization);
467468

468469
if (await asgardeo.isSignedIn()) {
469470
await updateSession();
470471
}
472+
473+
return response;
471474
} catch (error) {
472475
throw new AsgardeoRuntimeError(
473476
`Failed to switch organization: ${error instanceof Error ? error.message : String(JSON.stringify(error))}`,
@@ -519,6 +522,7 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
519522
exchangeToken: asgardeo.exchangeToken.bind(asgardeo),
520523
syncSession,
521524
platform: config?.platform,
525+
switchOrganization,
522526
}),
523527
[
524528
applicationId,
@@ -537,6 +541,7 @@ const AsgardeoProvider: FC<PropsWithChildren<AsgardeoProviderProps>> = ({
537541
asgardeo,
538542
signInOptions,
539543
syncSession,
544+
switchOrganization,
540545
],
541546
);
542547

packages/react/src/contexts/Organization/OrganizationProvider.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
Organization,
2222
AllOrganizationsApiResponse,
2323
CreateOrganizationPayload,
24+
TokenResponse,
2425
} from '@asgardeo/browser';
2526
import {FC, PropsWithChildren, ReactElement, useCallback, useMemo, useState} from 'react';
2627
import OrganizationContext, {OrganizationContextProps} from './OrganizationContext';
@@ -56,7 +57,7 @@ export interface OrganizationProviderProps {
5657
/**
5758
* Callback function called when switching organizations
5859
*/
59-
onOrganizationSwitch?: (organization: Organization) => Promise<void>;
60+
onOrganizationSwitch?: (organization: Organization) => Promise<TokenResponse | Response>;
6061
/**
6162
* Refetch the my organizations list.
6263
* @returns

0 commit comments

Comments
 (0)