diff --git a/change/@azure-msal-browser-88e0dc39-07d7-4080-a5bb-96379561a4ff.json b/change/@azure-msal-browser-88e0dc39-07d7-4080-a5bb-96379561a4ff.json new file mode 100644 index 0000000000..a741f9cece --- /dev/null +++ b/change/@azure-msal-browser-88e0dc39-07d7-4080-a5bb-96379561a4ff.json @@ -0,0 +1,7 @@ +{ + "type": "major", + "comment": "Remove onRedirectNavigate from endSessionRequest #8066", + "packageName": "@azure/msal-browser", + "email": "shylasummers@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-browser/apiReview/msal-browser.api.md b/lib/msal-browser/apiReview/msal-browser.api.md index ccf6773b8a..64595b085a 100644 --- a/lib/msal-browser/apiReview/msal-browser.api.md +++ b/lib/msal-browser/apiReview/msal-browser.api.md @@ -561,7 +561,6 @@ export type EndSessionPopupRequest = Partial> & { authority?: string; - onRedirectNavigate?: (url: string) => boolean | void; }; // Warning: (ae-missing-release-tag) "EventCallbackFunction" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/lib/msal-browser/docs/logout.md b/lib/msal-browser/docs/logout.md index 4583a88741..869e3ab435 100644 --- a/lib/msal-browser/docs/logout.md +++ b/lib/msal-browser/docs/logout.md @@ -60,18 +60,25 @@ await msalInstance.logoutRedirect({ ``` ### Skipping the server sign-out - **WARNING:** Skipping the server sign-out means the user's session will remain active on the server and can be signed back into your application without providing credentials again. -If you want your application to only perform local logout you can provide a callback to the `onRedirectNavigate` parameter on the request and have the callback return false. +If you want your application to only perform local logout you can provide a callback to the `onRedirectNavigate` parameter in the configuration and have the callback return false. ```javascript -msalInstance.logoutRedirect({ - onRedirectNavigate: (url) => { - // Return false if you would like to stop navigation after local logout - return false; +const msalConfig = { + auth: { + clientId: 'your_client_id', + authority: 'https://login.microsoftonline.con/{your_tenant_id}', + redirectUri: 'https://contoso.com', + postLogoutRedirectUri: 'https://contoso.com/homepage', + onRedirectNavigate: (url) => { + // Return false if you would like to stop navigation after local logout + return false; + } } -}); +}; +const msalInstance = new PublicClientApplication(msalConfig); +msalInstance.logoutRedirect(); ``` ## logoutPopup @@ -151,7 +158,10 @@ Example: ```typescript const msal = new PublicClientApplication({ auth: { - clientId: "my-client-id" + clientId: "my-client-id", + onRedirectNavigate: (url) => { + return false; + } }, system: { allowRedirectInIframe: true @@ -159,22 +169,15 @@ const msal = new PublicClientApplication({ }) // Automatically on page load -msal.logoutRedirect({ - onRedirectNavigate: () => { - // Return false to stop navigation after local logout - return false; - } -}); +msal.logoutRedirect(); ``` -Now when a user logouts out of another application, your application's front-channel logout url will be loaded in a hidden iframe, and MSAL.js will clear its cache to complete single-sign out. - +Please note that front-channel logout is not always supported across browsers. Chromium enabled [Storage Partitioning](https://privacysandbox.google.com/cookies/storage-partitioning) and Firefox supports [similar standard](https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/State_Partitioning) limiting applications to execute front-channel logout. For official Entra documentation on this topic, see [here](https://learn.microsoft.com/en-us/entra/identity-platform/reference-third-party-cookies-spas#limitations-on-front-channel-logout-without-third-party-cookies). ### Front-channel logout samples The following samples demonstrate how to implement front-channel logout using MSAL.js: -- MSAL Angular v2: [Angular 11 sample](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-angular-v2-samples/angular11-sample-app) - MSAL React: [React Router sample](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-react-samples/react-router-sample) ## Events diff --git a/lib/msal-browser/docs/v4-migration.md b/lib/msal-browser/docs/v4-migration.md index 9e6e7ace77..72ce21cf01 100644 --- a/lib/msal-browser/docs/v4-migration.md +++ b/lib/msal-browser/docs/v4-migration.md @@ -159,7 +159,7 @@ See the [Configuration doc](./configuration.md#system-config-options) for more d ### Removal of `onRedirectNavigate` parameter -The `onRedirectNavigate` parameter has been removed from the `RedirectRequest` object. It has *not* been removed from the `Configuration` object and can continue to be set there. +The `onRedirectNavigate` parameter will *only be supported* from `Configuration` object going forward and is removed from `RedirectRequest` and `EndSessionRequest` objects. Please ensure to set it in msal config if you need to use it. ## Behavioral Breaking Changes diff --git a/lib/msal-browser/src/interaction_client/RedirectClient.ts b/lib/msal-browser/src/interaction_client/RedirectClient.ts index 10a0e4d6f9..6862312a28 100644 --- a/lib/msal-browser/src/interaction_client/RedirectClient.ts +++ b/lib/msal-browser/src/interaction_client/RedirectClient.ts @@ -790,36 +790,16 @@ export class RedirectClient extends StandardInteractionClient { account: (logoutRequest && logoutRequest.account) || undefined, }); - // Create a serializable version of the request for events - const { - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onRedirectNavigate, - ...serializableLogoutRequest - } = validLogoutRequest; - if (authClient.authority.protocolMode === ProtocolMode.OIDC) { try { authClient.authority.endSessionEndpoint; } catch { if (validLogoutRequest.account?.homeAccountId) { - if ( - // @ts-ignore - typeof validLogoutRequest.onRedirectNavigate === - "function" - ) { - this.eventHandler.emitEvent( - EventType.LOGOUT_SUCCESS, - InteractionType.Redirect, - serializableLogoutRequest - ); - } else { - this.eventHandler.emitEvent( - EventType.LOGOUT_SUCCESS, - InteractionType.Redirect, - validLogoutRequest - ); - } + this.eventHandler.emitEvent( + EventType.LOGOUT_SUCCESS, + InteractionType.Redirect, + validLogoutRequest + ); return; } } @@ -830,29 +810,16 @@ export class RedirectClient extends StandardInteractionClient { authClient.getLogoutUri(validLogoutRequest); if (validLogoutRequest.account?.homeAccountId) { - if ( - // @ts-ignore - typeof validLogoutRequest.onRedirectNavigate === "function" - ) { - this.eventHandler.emitEvent( - EventType.LOGOUT_SUCCESS, - InteractionType.Redirect, - serializableLogoutRequest - ); - } else { - this.eventHandler.emitEvent( - EventType.LOGOUT_SUCCESS, - InteractionType.Redirect, - validLogoutRequest - ); - } + this.eventHandler.emitEvent( + EventType.LOGOUT_SUCCESS, + InteractionType.Redirect, + validLogoutRequest + ); } // Check if onRedirectNavigate is implemented, and invoke it if so - if ( - logoutRequest && - typeof logoutRequest.onRedirectNavigate === "function" - ) { - const navigate = logoutRequest.onRedirectNavigate(logoutUri); + const onRedirectNavigate = this.config.auth.onRedirectNavigate; + if (typeof onRedirectNavigate === "function") { + const navigate = onRedirectNavigate(logoutUri); if (navigate !== false) { this.logger.verbose( diff --git a/lib/msal-browser/src/request/EndSessionRequest.ts b/lib/msal-browser/src/request/EndSessionRequest.ts index 225f0bc9d5..02eeb6a431 100644 --- a/lib/msal-browser/src/request/EndSessionRequest.ts +++ b/lib/msal-browser/src/request/EndSessionRequest.ts @@ -12,12 +12,10 @@ import { CommonEndSessionRequest } from "@azure/msal-common/browser"; * - authority - Authority to send logout request to. * - correlationId - Unique GUID set per request to trace a request end-to-end for telemetry purposes. * - idTokenHint - ID Token used by B2C to validate logout if required by the policy - * - onRedirectNavigate - Callback that will be passed the url that MSAL will navigate to. Returning false in the callback will stop navigation. * - logoutHint - A string that specifies the account that is being logged out in order to skip the server account picker on logout */ export type EndSessionRequest = Partial< Omit > & { authority?: string; - onRedirectNavigate?: (url: string) => boolean | void; }; diff --git a/lib/msal-browser/test/interaction_client/RedirectClient.spec.ts b/lib/msal-browser/test/interaction_client/RedirectClient.spec.ts index 92cb2f96c7..07344e0d66 100644 --- a/lib/msal-browser/test/interaction_client/RedirectClient.spec.ts +++ b/lib/msal-browser/test/interaction_client/RedirectClient.spec.ts @@ -2008,7 +2008,7 @@ describe("RedirectClient", () => { redirectClient.acquireToken(loginRequest); }); - it("passes onRedirectNavigate callback", (done) => { + it("passes onRedirectNavigate callback from config", (done) => { const onRedirectNavigate = (url: string) => { verifyUrl(url, ["user.read"]); done(); @@ -2553,235 +2553,6 @@ describe("RedirectClient", () => { ); }); - it("doesnt navigate if onRedirectNavigate returns false", (done) => { - const logoutUriSpy = jest - .spyOn(AuthorizationCodeClient.prototype, "getLogoutUri") - .mockReturnValue(testLogoutUrl); - jest.spyOn( - NavigationClient.prototype, - "navigateExternal" - ).mockImplementation( - ( - urlNavigate: string, - options: NavigationOptions - ): Promise => { - // If onRedirectNavigate does not stop navigatation, this will be called, failing the test as done will be invoked twice - done(); - return Promise.resolve(true); - } - ); - browserStorage.setInteractionInProgress(true); - redirectClient - .logout({ - onRedirectNavigate: (url: string) => { - expect(url).toEqual(testLogoutUrl); - return false; - }, - }) - .then(() => { - expect( - browserStorage.getInteractionInProgress() - ).toBeFalsy(); - - const validatedLogoutRequest: CommonEndSessionRequest = { - correlationId: RANDOM_TEST_GUID, - postLogoutRedirectUri: TEST_URIS.TEST_REDIR_URI, - }; - expect(logoutUriSpy).toHaveBeenCalledWith( - expect.objectContaining(validatedLogoutRequest) - ); - done(); - }); - }); - - it("doesnt navigate if onRedirectNavigate returns false (specific account)", (done) => { - const testAccountInfo: AccountInfo = { - homeAccountId: TEST_DATA_CLIENT_INFO.TEST_HOME_ACCOUNT_ID, - localAccountId: TEST_DATA_CLIENT_INFO.TEST_UID, - environment: "login.windows.net", - tenantId: "3338040d-6c67-4c5b-b112-36a304b66dad", - username: "AbeLi@microsoft.com", - loginHint: "loginHint", - }; - - const testAccount: AccountEntity = { - homeAccountId: testAccountInfo.homeAccountId, - localAccountId: testAccountInfo.localAccountId, - environment: testAccountInfo.environment, - realm: testAccountInfo.tenantId, - username: testAccountInfo.username, - name: testAccountInfo.name, - authorityType: "MSSTS", - clientInfo: TEST_DATA_CLIENT_INFO.TEST_CLIENT_INFO_B64ENCODED, - lastUpdatedAt: Date.now().toString(), - }; - - const logoutUriSpy = jest - .spyOn(AuthorizationCodeClient.prototype, "getLogoutUri") - .mockReturnValue(testLogoutUrl); - jest.spyOn( - NavigationClient.prototype, - "navigateExternal" - ).mockImplementation( - ( - urlNavigate: string, - options: NavigationOptions - ): Promise => { - // If onRedirectNavigate does not stop navigatation, this will be called, failing the test as done will be invoked twice - done(); - return Promise.resolve(true); - } - ); - browserStorage.setInteractionInProgress(true); - browserStorage - .setAccount(testAccount, TEST_CONFIG.CORRELATION_ID) - .then(() => - redirectClient - .logout({ - account: testAccountInfo, - onRedirectNavigate: (url: string) => { - expect(url).toEqual(testLogoutUrl); - return false; - }, - }) - .then(() => { - expect( - browserStorage.getInteractionInProgress() - ).toBeFalsy(); - - const validatedLogoutRequest: CommonEndSessionRequest = - { - correlationId: RANDOM_TEST_GUID, - postLogoutRedirectUri: - TEST_URIS.TEST_REDIR_URI, - }; - expect(logoutUriSpy).toHaveBeenCalledWith( - expect.objectContaining(validatedLogoutRequest) - ); - done(); - }) - ); - }); - - it("does navigate if onRedirectNavigate returns true", (done) => { - const logoutUriSpy = jest - .spyOn(AuthorizationCodeClient.prototype, "getLogoutUri") - .mockReturnValue(testLogoutUrl); - jest.spyOn( - NavigationClient.prototype, - "navigateExternal" - ).mockImplementation( - ( - urlNavigate: string, - options: NavigationOptions - ): Promise => { - expect( - browserStorage.getInteractionInProgress() - ).toBeTruthy(); - expect(urlNavigate).toEqual(testLogoutUrl); - - return Promise.resolve(true); - } - ); - browserStorage.setInteractionInProgress(true); - redirectClient - .logout({ - onRedirectNavigate: (url) => { - expect(url).toEqual(testLogoutUrl); - return true; - }, - }) - .then(() => { - expect( - browserStorage.getInteractionInProgress() - ).toBeTruthy(); - - // Reset after testing it was properly set - browserStorage.setInteractionInProgress(false); - - const validatedLogoutRequest: CommonEndSessionRequest = { - correlationId: RANDOM_TEST_GUID, - postLogoutRedirectUri: TEST_URIS.TEST_REDIR_URI, - }; - expect(logoutUriSpy).toHaveBeenCalledWith( - expect.objectContaining(validatedLogoutRequest) - ); - done(); - }); - }); - - it("does navigate if onRedirectNavigate returns true (specific account)", (done) => { - const testAccountInfo: AccountInfo = { - homeAccountId: TEST_DATA_CLIENT_INFO.TEST_HOME_ACCOUNT_ID, - localAccountId: TEST_DATA_CLIENT_INFO.TEST_UID, - environment: "login.windows.net", - tenantId: "3338040d-6c67-4c5b-b112-36a304b66dad", - username: "AbeLi@microsoft.com", - loginHint: "loginHint", - }; - - const testAccount: AccountEntity = { - homeAccountId: testAccountInfo.homeAccountId, - localAccountId: testAccountInfo.localAccountId, - environment: testAccountInfo.environment, - realm: testAccountInfo.tenantId, - username: testAccountInfo.username, - name: testAccountInfo.name, - authorityType: "MSSTS", - clientInfo: TEST_DATA_CLIENT_INFO.TEST_CLIENT_INFO_B64ENCODED, - lastUpdatedAt: Date.now().toString(), - }; - - const logoutUriSpy = jest - .spyOn(AuthorizationCodeClient.prototype, "getLogoutUri") - .mockReturnValue(testLogoutUrl); - jest.spyOn( - NavigationClient.prototype, - "navigateExternal" - ).mockImplementation( - ( - urlNavigate: string, - options: NavigationOptions - ): Promise => { - expect(urlNavigate).toEqual(testLogoutUrl); - - return Promise.resolve(true); - } - ); - browserStorage.setInteractionInProgress(true); - browserStorage - .setAccount(testAccount, TEST_CONFIG.CORRELATION_ID) - .then(() => - redirectClient - .logout({ - account: testAccountInfo, - onRedirectNavigate: (url) => { - expect(url).toEqual(testLogoutUrl); - return true; - }, - }) - .then(() => { - expect( - browserStorage.getInteractionInProgress() - ).toBeTruthy(); - - // Reset after testing it was properly set - browserStorage.setInteractionInProgress(false); - - const validatedLogoutRequest: CommonEndSessionRequest = - { - correlationId: RANDOM_TEST_GUID, - postLogoutRedirectUri: - TEST_URIS.TEST_REDIR_URI, - }; - expect(logoutUriSpy).toHaveBeenCalledWith( - expect.objectContaining(validatedLogoutRequest) - ); - done(); - }) - ); - }); - it("errors thrown are cached for telemetry and logout failure event is raised", (done) => { const testError = createBrowserAuthError( BrowserAuthErrorCodes.emptyNavigateUri @@ -2878,6 +2649,327 @@ describe("RedirectClient", () => { expect(pca.getAllAccounts().length).toBe(0); }); }); + + describe("onRedirectNavigate tests", () => { + let pca2: PublicClientApplication, + pca3: PublicClientApplication, + redirectClient2: RedirectClient, + redirectClient3: RedirectClient, + browserStorage2: BrowserCacheManager, + browserStorage3: BrowserCacheManager; + beforeEach(async () => { + const onRedirectNavigateFalse = (url: string) => { + expect(url).toEqual(testLogoutUrl); + return false; + }; + pca2 = new PublicClientApplication({ + auth: { + clientId: TEST_CONFIG.MSAL_CLIENT_ID, + onRedirectNavigate: onRedirectNavigateFalse, + }, + telemetry: { + application: { + appName: TEST_CONFIG.applicationName, + appVersion: TEST_CONFIG.applicationVersion, + }, + }, + }); + + await pca2.initialize(); + pca2 = (pca2 as any).controller; + // @ts-ignore + redirectClient2 = new RedirectClient( + //@ts-ignore + pca2.config, + //@ts-ignore + pca2.browserStorage, + //@ts-ignore + pca2.browserCrypto, + //@ts-ignore + pca2.logger, + //@ts-ignore + pca2.eventHandler, + //@ts-ignore + pca2.navigationClient, + //@ts-ignore + pca2.performanceClient, + //@ts-ignore + pca2.nativeInternalStorage + ); + + // @ts-ignore + browserStorage2 = pca2.browserStorage; + + const onRedirectNavigateTrue = (url: string) => { + expect(url).toEqual(testLogoutUrl); + return true; + }; + pca3 = new PublicClientApplication({ + auth: { + clientId: TEST_CONFIG.MSAL_CLIENT_ID, + onRedirectNavigate: onRedirectNavigateTrue, + }, + telemetry: { + application: { + appName: TEST_CONFIG.applicationName, + appVersion: TEST_CONFIG.applicationVersion, + }, + }, + }); + + await pca3.initialize(); + pca3 = (pca3 as any).controller; + // @ts-ignore + redirectClient3 = new RedirectClient( + //@ts-ignore + pca3.config, + //@ts-ignore + pca3.browserStorage, + //@ts-ignore + pca3.browserCrypto, + //@ts-ignore + pca3.logger, + //@ts-ignore + pca3.eventHandler, + //@ts-ignore + pca3.navigationClient, + //@ts-ignore + pca3.performanceClient, + //@ts-ignore + pca3.nativeInternalStorage + ); + + // @ts-ignore + browserStorage3 = pca3.browserStorage; + }); + + it("doesnt navigate if onRedirectNavigate returns false", (done) => { + const logoutUriSpy = jest + .spyOn(AuthorizationCodeClient.prototype, "getLogoutUri") + .mockReturnValue(testLogoutUrl); + + jest.spyOn( + NavigationClient.prototype, + "navigateExternal" + ).mockImplementation( + ( + urlNavigate: string, + options: NavigationOptions + ): Promise => { + done( + "Navigation should not happen if onRedirectNavigate returns false" + ); + return Promise.reject(); + } + ); + + browserStorage2.setInteractionInProgress(true); + + redirectClient2 + .logout({ correlationId: RANDOM_TEST_GUID }) + .then(() => { + expect( + browserStorage2.getInteractionInProgress() + ).toBeFalsy(); + + const validatedLogoutRequest: CommonEndSessionRequest = + { + correlationId: RANDOM_TEST_GUID, + postLogoutRedirectUri: TEST_URIS.TEST_REDIR_URI, + }; + expect(logoutUriSpy).toHaveBeenCalledWith( + expect.objectContaining(validatedLogoutRequest) + ); + done(); + }); + }); + + it("doesnt navigate if onRedirectNavigate returns false (specific account)", (done) => { + const testAccountInfo: AccountInfo = { + homeAccountId: TEST_DATA_CLIENT_INFO.TEST_HOME_ACCOUNT_ID, + localAccountId: TEST_DATA_CLIENT_INFO.TEST_UID, + environment: "login.windows.net", + tenantId: "3338040d-6c67-4c5b-b112-36a304b66dad", + username: "AbeLi@microsoft.com", + loginHint: "loginHint", + }; + + const testAccount: AccountEntity = { + homeAccountId: testAccountInfo.homeAccountId, + localAccountId: testAccountInfo.localAccountId, + environment: testAccountInfo.environment, + realm: testAccountInfo.tenantId, + username: testAccountInfo.username, + name: testAccountInfo.name, + authorityType: "MSSTS", + clientInfo: + TEST_DATA_CLIENT_INFO.TEST_CLIENT_INFO_B64ENCODED, + lastUpdatedAt: Date.now().toString(), + }; + + const logoutUriSpy = jest + .spyOn(AuthorizationCodeClient.prototype, "getLogoutUri") + .mockReturnValue(testLogoutUrl); + + jest.spyOn( + NavigationClient.prototype, + "navigateExternal" + ).mockImplementation( + ( + urlNavigate: string, + options: NavigationOptions + ): Promise => { + done( + "Navigation should not happen if onRedirectNavigate returns false" + ); + return Promise.reject(); + } + ); + + browserStorage2.setInteractionInProgress(true); + browserStorage2 + .setAccount(testAccount, TEST_CONFIG.CORRELATION_ID) + .then(() => + redirectClient2 + .logout({ + account: testAccountInfo, + correlationId: RANDOM_TEST_GUID, + }) + .then(() => { + expect( + browserStorage2.getInteractionInProgress() + ).toBeFalsy(); + + const validatedLogoutRequest: CommonEndSessionRequest = + { + correlationId: RANDOM_TEST_GUID, + postLogoutRedirectUri: + TEST_URIS.TEST_REDIR_URI, + }; + expect(logoutUriSpy).toHaveBeenCalledWith( + expect.objectContaining( + validatedLogoutRequest + ) + ); + done(); + }) + ); + }); + + it("does navigate if onRedirectNavigate returns true", (done) => { + const logoutUriSpy = jest + .spyOn(AuthorizationCodeClient.prototype, "getLogoutUri") + .mockReturnValue(testLogoutUrl); + + jest.spyOn( + NavigationClient.prototype, + "navigateExternal" + ).mockImplementation( + ( + urlNavigate: string, + options: NavigationOptions + ): Promise => { + expect( + browserStorage.getInteractionInProgress() + ).toBeTruthy(); + expect(urlNavigate).toEqual(testLogoutUrl); + return Promise.resolve(true); + } + ); + + browserStorage3.setInteractionInProgress(true); + + redirectClient + .logout({ correlationId: RANDOM_TEST_GUID }) + .then(() => { + expect( + browserStorage3.getInteractionInProgress() + ).toBeTruthy(); + browserStorage3.setInteractionInProgress(false); + + const validatedLogoutRequest: CommonEndSessionRequest = + { + correlationId: RANDOM_TEST_GUID, + postLogoutRedirectUri: TEST_URIS.TEST_REDIR_URI, + }; + expect(logoutUriSpy).toHaveBeenCalledWith( + expect.objectContaining(validatedLogoutRequest) + ); + done(); + }); + }); + + it("does navigate if onRedirectNavigate returns true (specific account)", (done) => { + const testAccountInfo: AccountInfo = { + homeAccountId: TEST_DATA_CLIENT_INFO.TEST_HOME_ACCOUNT_ID, + localAccountId: TEST_DATA_CLIENT_INFO.TEST_UID, + environment: "login.windows.net", + tenantId: "3338040d-6c67-4c5b-b112-36a304b66dad", + username: "AbeLi@microsoft.com", + loginHint: "loginHint", + }; + + const testAccount: AccountEntity = { + homeAccountId: testAccountInfo.homeAccountId, + localAccountId: testAccountInfo.localAccountId, + environment: testAccountInfo.environment, + realm: testAccountInfo.tenantId, + username: testAccountInfo.username, + name: testAccountInfo.name, + authorityType: "MSSTS", + clientInfo: + TEST_DATA_CLIENT_INFO.TEST_CLIENT_INFO_B64ENCODED, + lastUpdatedAt: Date.now().toString(), + }; + + const logoutUriSpy = jest + .spyOn(AuthorizationCodeClient.prototype, "getLogoutUri") + .mockReturnValue(testLogoutUrl); + + jest.spyOn( + NavigationClient.prototype, + "navigateExternal" + ).mockImplementation( + ( + urlNavigate: string, + options: NavigationOptions + ): Promise => { + expect(urlNavigate).toEqual(testLogoutUrl); + return Promise.resolve(true); + } + ); + + browserStorage3.setInteractionInProgress(true); + browserStorage3 + .setAccount(testAccount, TEST_CONFIG.CORRELATION_ID) + .then(() => + redirectClient3 + .logout({ + account: testAccountInfo, + correlationId: RANDOM_TEST_GUID, + }) + .then(() => { + expect( + browserStorage3.getInteractionInProgress() + ).toBeTruthy(); + browserStorage3.setInteractionInProgress(false); + + const validatedLogoutRequest: CommonEndSessionRequest = + { + correlationId: RANDOM_TEST_GUID, + postLogoutRedirectUri: + TEST_URIS.TEST_REDIR_URI, + }; + expect(logoutUriSpy).toHaveBeenCalledWith( + expect.objectContaining( + validatedLogoutRequest + ) + ); + done(); + }) + ); + }); + }); }); describe("initiateAuthRequest()", () => { diff --git a/samples/msal-browser-samples/ChromiumExtensionSample/README.md b/samples/msal-browser-samples/ChromiumExtensionSample/README.md index 382070ff84..c1c58e367a 100644 --- a/samples/msal-browser-samples/ChromiumExtensionSample/README.md +++ b/samples/msal-browser-samples/ChromiumExtensionSample/README.md @@ -23,17 +23,32 @@ This folder contains a sample Chromium extensions demonstrating how to integrate Chromium extensions are unable to perform certain types of navigation, so applications should leverage the [`chrome.identity.launchWebAuthFlow`](https://developer.chrome.com/apps/identity#method-launchWebAuthFlow) API to perform interactive auth requests. This API takes a url to navigate to, and a callback that will be invoked once the auth flow is completed (which is signaled by Microsoft Entra ID redirecting back to the `chromiumapp.com` url mentioned earlier). Chromium extensions using MSAL Browser can build working auth flows by composing the MSAL `loginRedirect/acquireTokenRedirect` and `handleRedirectPromise` APIs to generate a url and handle the response: ```js +/** + * Initialize MSAL object + */ +const msalInstance = new msal.PublicClientApplication({ + auth: { + authority: "https://login.microsoftonline.com/common/", + clientId: "your-client-id-here", + redirectUri, + postLogoutRedirectUri: redirectUri, + onRedirectNavigate: (url) => { + resolve(url); + return false; + } + }, + cache: { + cacheLocation: "localStorage" + } +}); + /** * Generates a login url */ async function getLoginUrl(request) { return new Promise((resolve, reject) => { msalInstance.loginRedirect({ - ...request, - onRedirectNavigate: (url) => { - resolve(url); - return false; - } + request, }).catch(reject); }); } @@ -45,11 +60,7 @@ async function getLoginUrl(request) { async function getAcquireTokenUrl(request) { return new Promise((resolve, reject) => { msalInstance.acquireTokenRedirect({ - ...request, - onRedirectNavigate: (url) => { - resolve(url); - return false; - } + request, }).catch(reject); }); } @@ -81,13 +92,7 @@ async function launchWebAuthFlow(url) { */ async function getLogoutUrl(request) { return new Promise((resolve, reject) => { - msalInstance.logout({ - ...request, - onRedirectNavigate: (url) => { - resolve(url); - return false; - } - }).catch(reject); + msalInstance.logout(request).catch(reject); }); } diff --git a/samples/msal-browser-samples/ChromiumExtensionSample/auth.js b/samples/msal-browser-samples/ChromiumExtensionSample/auth.js index 6ef0ce3ccf..6d6a38e869 100644 --- a/samples/msal-browser-samples/ChromiumExtensionSample/auth.js +++ b/samples/msal-browser-samples/ChromiumExtensionSample/auth.js @@ -11,7 +11,11 @@ const msalInstance = new msal.PublicClientApplication({ authority: "https://login.microsoftonline.com/common/", clientId: "your-client-id-here", redirectUri, - postLogoutRedirectUri: redirectUri + postLogoutRedirectUri: redirectUri, + onRedirectNavigate: (url) => { + resolve(url); + return false; + } }, cache: { cacheLocation: "localStorage" @@ -97,13 +101,7 @@ async function getLoginUrl(request, reject) { */ async function getLogoutUrl(request) { return new Promise((resolve, reject) => { - msalInstance.logout({ - ...request, - onRedirectNavigate: (url) => { - resolve(url); - return false; - } - }).catch(reject); + msalInstance.logout(request).catch(reject); }); } diff --git a/samples/msal-react-samples/b2c-sample/src/authConfig.js b/samples/msal-react-samples/b2c-sample/src/authConfig.js index 24ab6cff3b..c42fca939c 100644 --- a/samples/msal-react-samples/b2c-sample/src/authConfig.js +++ b/samples/msal-react-samples/b2c-sample/src/authConfig.js @@ -1,4 +1,4 @@ -import { LogLevel } from "@azure/msal-browser"; +import { LogLevel, BrowserUtils } from "@azure/msal-browser"; // Browser check variables // If you support IE, our recommendation is that you sign-in using Redirect APIs // If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check @@ -39,7 +39,8 @@ export const msalConfig = { authority: b2cPolicies.authorities.signUpSignIn.authority, knownAuthorities: [b2cPolicies.authorityDomain], redirectUri: "/", - postLogoutRedirectUri: "/" + postLogoutRedirectUri: "/", + onRedirectNavigate: () => !BrowserUtils.isInIframe() }, cache: { cacheLocation: "localStorage", diff --git a/samples/msal-react-samples/b2c-sample/src/pages/Logout.jsx b/samples/msal-react-samples/b2c-sample/src/pages/Logout.jsx index a7f8591083..21d765b948 100644 --- a/samples/msal-react-samples/b2c-sample/src/pages/Logout.jsx +++ b/samples/msal-react-samples/b2c-sample/src/pages/Logout.jsx @@ -1,5 +1,4 @@ import React, { useEffect } from "react"; -import { BrowserUtils } from "@azure/msal-browser"; import { useMsal } from "@azure/msal-react"; export function Logout() { @@ -8,7 +7,6 @@ export function Logout() { useEffect(() => { instance.logoutRedirect({ account: instance.getActiveAccount(), - onRedirectNavigate: () => !BrowserUtils.isInIframe() }) }, [ instance ]); diff --git a/samples/msal-react-samples/react-router-sample/src/authConfig.js b/samples/msal-react-samples/react-router-sample/src/authConfig.js index c60c2b005a..fbd983d578 100644 --- a/samples/msal-react-samples/react-router-sample/src/authConfig.js +++ b/samples/msal-react-samples/react-router-sample/src/authConfig.js @@ -1,4 +1,4 @@ -import { LogLevel } from "@azure/msal-browser"; +import { LogLevel, BrowserUtils } from "@azure/msal-browser"; // Browser check variables // If you support IE, our recommendation is that you sign-in using Redirect APIs // If you as a developer are testing using Edge InPrivate mode, please add "isEdge" to the if check @@ -18,6 +18,7 @@ export const msalConfig = { authority: process.env.REACT_APP_AUTHORITY, redirectUri: "/", postLogoutRedirectUri: "/", + onRedirectNavigate: () => !BrowserUtils.isInIframe() }, cache: { cacheLocation: "localStorage", diff --git a/samples/msal-react-samples/react-router-sample/src/pages/Logout.jsx b/samples/msal-react-samples/react-router-sample/src/pages/Logout.jsx index 20fa9213d1..21d765b948 100644 --- a/samples/msal-react-samples/react-router-sample/src/pages/Logout.jsx +++ b/samples/msal-react-samples/react-router-sample/src/pages/Logout.jsx @@ -1,6 +1,5 @@ import React, { useEffect } from "react"; import { useMsal } from "@azure/msal-react"; -import { BrowserUtils } from "@azure/msal-browser"; export function Logout() { const { instance } = useMsal(); @@ -8,7 +7,6 @@ export function Logout() { useEffect(() => { instance.logoutRedirect({ account: instance.getActiveAccount(), - onRedirectNavigate: () => !BrowserUtils.isInIframe() }) }, [ instance ]);