diff --git a/change/@azure-msal-angular-52541f6d-ba1b-4649-a135-7f48be83e01e.json b/change/@azure-msal-angular-52541f6d-ba1b-4649-a135-7f48be83e01e.json new file mode 100644 index 0000000000..abc8d204a9 --- /dev/null +++ b/change/@azure-msal-angular-52541f6d-ba1b-4649-a135-7f48be83e01e.json @@ -0,0 +1,7 @@ +{ + "type": "major", + "comment": "Update handleRedirectPromise signature to consolidate hash into options type", + "packageName": "@azure/msal-angular", + "email": "hemoral@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-msal-browser-cfc942e4-b61f-4c9d-b4af-ed9cb7636840.json b/change/@azure-msal-browser-cfc942e4-b61f-4c9d-b4af-ed9cb7636840.json new file mode 100644 index 0000000000..dca180a76e --- /dev/null +++ b/change/@azure-msal-browser-cfc942e4-b61f-4c9d-b4af-ed9cb7636840.json @@ -0,0 +1,7 @@ +{ + "type": "major", + "comment": "Move navigateToLoginRequestUrl to request config", + "packageName": "@azure/msal-browser", + "email": "hemoral@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-angular/src/msal.service.spec.ts b/lib/msal-angular/src/msal.service.spec.ts index 6dca7c74aa..025c84e5c7 100644 --- a/lib/msal-angular/src/msal.service.spec.ts +++ b/lib/msal-angular/src/msal.service.spec.ts @@ -440,7 +440,7 @@ describe("MsalService", () => { expect(response.accessToken).toBe(sampleAccessToken.accessToken); expect( PublicClientApplication.prototype.handleRedirectPromise - ).toHaveBeenCalledWith(hash); + ).toHaveBeenCalledWith({ hash: hash }); done(); }); }); diff --git a/lib/msal-angular/src/msal.service.ts b/lib/msal-angular/src/msal.service.ts index 4c0d80015a..b6a1f0bcb8 100644 --- a/lib/msal-angular/src/msal.service.ts +++ b/lib/msal-angular/src/msal.service.ts @@ -59,7 +59,9 @@ export class MsalService implements IMsalService { this.instance .initialize() .then(() => - this.instance.handleRedirectPromise(hash || this.redirectHash) + this.instance.handleRedirectPromise({ + hash: hash || this.redirectHash, + }) ) .finally(() => { // update inProgress state to none diff --git a/lib/msal-browser/FAQ.md b/lib/msal-browser/FAQ.md index f555db01f2..0db4a92947 100644 --- a/lib/msal-browser/FAQ.md +++ b/lib/msal-browser/FAQ.md @@ -171,7 +171,7 @@ For a full implementation, please refer to the app creation scripts in the [Vani The redirect flow can be confusing, as redirecting away from the page means you are creating a whole new instance of the application when you return. This means that calling a redirect method cannot return anything. Rather, what happens is that the page is redirected away, you enter your credentials, and you are redirected back to your application with the response in the url hash. -If `navigateToLoginRequestUrl` property in MSAL configuration parameters is set to **true**, you will be redirected again to the page you were on when you called `loginRedirect`, unless that page was also set as your `redirectUri`. On the final page your application must call `handleRedirectPromise()` in order to process the hash and cache tokens in local/session storage. +If `navigateToLoginRequestUrl` property is passed in as an option to `handleRedirectPromise` and set to **true**, you will be redirected again to the page you were on when you called `loginRedirect`, unless that page was also set as your `redirectUri`. Your application must call `handleRedirectPromise()` in the page your `redirectUri` points to in order to process the hash and cache tokens in local/session storage. As this function returns a promise you can call `.then` and `.catch`, similar to `loginPopup`. diff --git a/lib/msal-browser/apiReview/msal-browser.api.md b/lib/msal-browser/apiReview/msal-browser.api.md index 5b6f362868..d57b8ba857 100644 --- a/lib/msal-browser/apiReview/msal-browser.api.md +++ b/lib/msal-browser/apiReview/msal-browser.api.md @@ -281,7 +281,6 @@ export type BrowserAuthOptions = { authorityMetadata?: string; redirectUri?: string; postLogoutRedirectUri?: string | null; - navigateToLoginRequestUrl?: boolean; clientCapabilities?: Array; OIDCOptions?: OIDCOptions; azureCloudOptions?: AzureCloudOptions; @@ -682,6 +681,14 @@ function getHomepage(): string; // @public (undocumented) const getRequestFailed = "get_request_failed"; +// Warning: (ae-missing-release-tag) "HandleRedirectPromiseOptions" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type HandleRedirectPromiseOptions = { + hash?: string; + navigateToLoginRequestUrl?: boolean; +}; + // Warning: (ae-missing-release-tag) "hashDoesNotContainKnownProperties" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -731,7 +738,7 @@ export interface IController { // @internal (undocumented) getPerformanceClient(): IPerformanceClient; // (undocumented) - handleRedirectPromise(hash?: string): Promise; + handleRedirectPromise(options?: HandleRedirectPromiseOptions): Promise; // (undocumented) hydrateCache(result: AuthenticationResult, request: SilentRequest | SsoSilentRequest | RedirectRequest | PopupRequest): Promise; // (undocumented) @@ -885,7 +892,7 @@ export interface IPublicClientApplication { // (undocumented) getLogger(): Logger; // (undocumented) - handleRedirectPromise(hash?: string): Promise; + handleRedirectPromise(options?: HandleRedirectPromiseOptions): Promise; // (undocumented) hydrateCache(result: AuthenticationResult, request: SilentRequest | SsoSilentRequest | RedirectRequest | PopupRequest): Promise; // (undocumented) @@ -1257,7 +1264,8 @@ export class PublicClientApplication implements IPublicClientApplication { getConfiguration(): BrowserConfiguration; getLogger(): Logger; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - handleRedirectPromise(hash?: string | undefined): Promise; + // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen + handleRedirectPromise(options?: HandleRedirectPromiseOptions): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen hydrateCache(result: AuthenticationResult, request: SilentRequest | SsoSilentRequest | RedirectRequest | PopupRequest): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen @@ -1330,7 +1338,7 @@ export class PublicClientNext implements IPublicClientApplication { getConfiguration(): BrowserConfiguration; getLogger(): Logger; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - handleRedirectPromise(hash?: string | undefined): Promise; + handleRedirectPromise(options?: HandleRedirectPromiseOptions): Promise; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen hydrateCache(result: AuthenticationResult, request: SilentRequest | SsoSilentRequest | RedirectRequest | PopupRequest): Promise; initialize(): Promise; @@ -1555,21 +1563,21 @@ export type WrapperSKU = (typeof WrapperSKU)[keyof typeof WrapperSKU]; // Warnings were encountered during analysis: // -// src/app/PublicClientNext.ts:69:8 - (tsdoc-undefined-tag) The TSDoc tag "@constructor" is not defined in this configuration -// src/app/PublicClientNext.ts:78:87 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/app/PublicClientNext.ts:78:60 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/app/PublicClientNext.ts:84:64 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/app/PublicClientNext.ts:84:77 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/app/PublicClientNext.ts:84:90 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/app/PublicClientNext.ts:84:55 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/app/PublicClientNext.ts:84:70 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/app/PublicClientNext.ts:84:79 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/app/PublicClientNext.ts:87:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/app/PublicClientNext.ts:88:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/app/PublicClientNext.ts:72:8 - (tsdoc-undefined-tag) The TSDoc tag "@constructor" is not defined in this configuration +// src/app/PublicClientNext.ts:81:87 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/app/PublicClientNext.ts:81:60 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/app/PublicClientNext.ts:87:64 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/app/PublicClientNext.ts:87:77 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/app/PublicClientNext.ts:87:90 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/app/PublicClientNext.ts:87:55 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/app/PublicClientNext.ts:87:70 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/app/PublicClientNext.ts:87:79 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/app/PublicClientNext.ts:90:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/app/PublicClientNext.ts:91:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/cache/LocalStorage.ts:297:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/cache/LocalStorage.ts:355:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/cache/LocalStorage.ts:386:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/config/Configuration.ts:210:5 - (ae-forgotten-export) The symbol "InternalAuthOptions" needs to be exported by the entry point index.d.ts +// src/config/Configuration.ts:207:5 - (ae-forgotten-export) The symbol "InternalAuthOptions" needs to be exported by the entry point index.d.ts // src/event/EventHandler.ts:113:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/event/EventHandler.ts:139:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/index.ts:8:12 - (tsdoc-characters-after-block-tag) The token "@azure" looks like a TSDoc tag but contains an invalid character "/"; if it is not a tag, use a backslash to escape the "@" diff --git a/lib/msal-browser/docs/v4-migration.md b/lib/msal-browser/docs/v4-migration.md index 884446f2a9..70b74c6c23 100644 --- a/lib/msal-browser/docs/v4-migration.md +++ b/lib/msal-browser/docs/v4-migration.md @@ -58,6 +58,23 @@ await loadExternalTokens( ); ``` +### `handleRedirectPromise` API signature has changed + +Previously, `PublicClientApplication.handleRedirectPromise` took in an optional hash parameter. A new options type called `HandleRedirectPromiseOptions` has been introduced. As of MSAL Browser v5, an optional object with type `HandleRedirectPromiseOptions` is the only parameter `handleRedirectPromise()` accepts. + +```javascript +// BEFORE +const hash = window.location.hash; // Arbitrary example value +pca.handleRedirectPromise(hash) + + +// AFTER +pca.handleRedirectPromise({ + hash: window.location.hash, // Option nested inside a `HandleRedirectPromiseOptions` object + navigateToLoginRequestUrl: true // Additional option +}) +``` + ## Configuration changes ### BrowserAuthOptions changes @@ -65,6 +82,11 @@ await loadExternalTokens( 1. The `skipAuthorityMetadataCache` parameter has been removed from BrowserAuthOptions in Configuration. 1. The `protocolMode` parameter has been moved to SystemOptions instead of BrowserAuthOptions in Configuration. 1. The `supportsNestedAppAuth` parameter has been removed. Use the `createNestablePublicClientApplication` API for Nested Apps instead. Read more about Nested Apps [here](./initialization.md#nested-app-configuration). +1. The `navigateTologinRequestUrl` parameter has been removed from BrowserAuthOptions in Configuration and can instead now be provided inside an options object as a parameter on the call to `handleRedirectPromise`: + + ```typescript + pca.handleRedirectPromise({ navigateToLoginRequestUrl: false }) + ``` ### CacheOptions changes diff --git a/lib/msal-browser/src/app/IPublicClientApplication.ts b/lib/msal-browser/src/app/IPublicClientApplication.ts index 7bc33b6f7a..2ace05d685 100644 --- a/lib/msal-browser/src/app/IPublicClientApplication.ts +++ b/lib/msal-browser/src/app/IPublicClientApplication.ts @@ -28,6 +28,7 @@ import { EventCallbackFunction } from "../event/EventMessage.js"; import { ClearCacheRequest } from "../request/ClearCacheRequest.js"; import { InitializeApplicationRequest } from "../request/InitializeApplicationRequest.js"; import { EventType } from "../event/EventType.js"; +import { HandleRedirectPromiseOptions } from "../controllers/IController.js"; export interface IPublicClientApplication { // TODO: Make request mandatory in the next major version? @@ -49,7 +50,9 @@ export interface IPublicClientApplication { removePerformanceCallback(callbackId: string): boolean; getAccount(accountFilter: AccountFilter): AccountInfo | null; getAllAccounts(): AccountInfo[]; - handleRedirectPromise(hash?: string): Promise; + handleRedirectPromise( + options?: HandleRedirectPromiseOptions + ): Promise; loginPopup(request?: PopupRequest): Promise; loginRedirect(request?: RedirectRequest): Promise; logoutRedirect(logoutRequest?: EndSessionRequest): Promise; diff --git a/lib/msal-browser/src/app/PublicClientApplication.ts b/lib/msal-browser/src/app/PublicClientApplication.ts index fc630be260..8a0b42d123 100644 --- a/lib/msal-browser/src/app/PublicClientApplication.ts +++ b/lib/msal-browser/src/app/PublicClientApplication.ts @@ -10,7 +10,10 @@ import { RedirectRequest } from "../request/RedirectRequest.js"; import { SilentRequest } from "../request/SilentRequest.js"; import { WrapperSKU } from "../utils/BrowserConstants.js"; import { IPublicClientApplication } from "./IPublicClientApplication.js"; -import { IController } from "../controllers/IController.js"; +import { + HandleRedirectPromiseOptions, + IController, +} from "../controllers/IController.js"; import { PerformanceCallbackFunction, AccountInfo, @@ -212,12 +215,13 @@ export class PublicClientApplication implements IPublicClientApplication { * has loaded during redirect flows. This should be invoked on all page loads involved in redirect * auth flows. * @param hash Hash to process. Defaults to the current value of window.location.hash. Only needs to be provided explicitly if the response to be handled is not contained in the current value. + * @param options Object containing optional configuration for redirect promise handling. * @returns Token response or null. If the return value is null, then no auth redirect was detected. */ handleRedirectPromise( - hash?: string | undefined + options?: HandleRedirectPromiseOptions ): Promise { - return this.controller.handleRedirectPromise(hash); + return this.controller.handleRedirectPromise(options); } /** diff --git a/lib/msal-browser/src/app/PublicClientNext.ts b/lib/msal-browser/src/app/PublicClientNext.ts index 7ff65e6f96..b88515ab46 100644 --- a/lib/msal-browser/src/app/PublicClientNext.ts +++ b/lib/msal-browser/src/app/PublicClientNext.ts @@ -10,7 +10,10 @@ import { RedirectRequest } from "../request/RedirectRequest.js"; import { SilentRequest } from "../request/SilentRequest.js"; import { WrapperSKU } from "../utils/BrowserConstants.js"; import { IPublicClientApplication } from "./IPublicClientApplication.js"; -import { IController } from "../controllers/IController.js"; +import { + HandleRedirectPromiseOptions, + IController, +} from "../controllers/IController.js"; import { PerformanceCallbackFunction, AccountInfo, @@ -237,9 +240,9 @@ export class PublicClientNext implements IPublicClientApplication { * @returns Token response or null. If the return value is null, then no auth redirect was detected. */ handleRedirectPromise( - hash?: string | undefined + options?: HandleRedirectPromiseOptions ): Promise { - return this.controller.handleRedirectPromise(hash); + return this.controller.handleRedirectPromise(options); } /** diff --git a/lib/msal-browser/src/config/Configuration.ts b/lib/msal-browser/src/config/Configuration.ts index f88cd771fe..8b138a5a26 100644 --- a/lib/msal-browser/src/config/Configuration.ts +++ b/lib/msal-browser/src/config/Configuration.ts @@ -69,10 +69,7 @@ export type BrowserAuthOptions = { * The redirect URI where the window navigates after a successful logout. */ postLogoutRedirectUri?: string | null; - /** - * Boolean indicating whether to navigate to the original request URL after the auth server navigates to the redirect URL. - */ - navigateToLoginRequestUrl?: boolean; + /** * Array of capabilities which will be added to the claims.access_token.xms_cc request property on every network request. */ @@ -241,7 +238,6 @@ export function buildConfiguration( redirectUri: typeof window !== "undefined" ? BrowserUtils.getCurrentUri() : "", postLogoutRedirectUri: "", - navigateToLoginRequestUrl: true, clientCapabilities: [], OIDCOptions: { responseMode: Constants.ResponseMode.FRAGMENT, diff --git a/lib/msal-browser/src/controllers/IController.ts b/lib/msal-browser/src/controllers/IController.ts index c70722614e..41c46df22c 100644 --- a/lib/msal-browser/src/controllers/IController.ts +++ b/lib/msal-browser/src/controllers/IController.ts @@ -72,7 +72,9 @@ export interface IController { getAllAccounts(accountFilter?: AccountFilter): AccountInfo[]; - handleRedirectPromise(hash?: string): Promise; + handleRedirectPromise( + options?: HandleRedirectPromiseOptions + ): Promise; loginPopup(request?: PopupRequest): Promise; @@ -116,3 +118,8 @@ export interface IController { /** @internal */ getPerformanceClient(): IPerformanceClient; } + +export type HandleRedirectPromiseOptions = { + hash?: string; + navigateToLoginRequestUrl?: boolean; +}; diff --git a/lib/msal-browser/src/controllers/NestedAppAuthController.ts b/lib/msal-browser/src/controllers/NestedAppAuthController.ts index c91777b631..35df9972ea 100644 --- a/lib/msal-browser/src/controllers/NestedAppAuthController.ts +++ b/lib/msal-browser/src/controllers/NestedAppAuthController.ts @@ -37,7 +37,7 @@ import { DEFAULT_REQUEST, CacheLookupPolicy, } from "../utils/BrowserConstants.js"; -import { IController } from "./IController.js"; +import { IController, HandleRedirectPromiseOptions } from "./IController.js"; import { NestedAppOperatingContext } from "../operatingcontext/NestedAppOperatingContext.js"; import { IBridgeProxy } from "../naa/IBridgeProxy.js"; import { CryptoOps } from "../crypto/CryptoOps.js"; @@ -752,7 +752,7 @@ export class NestedAppAuthController implements IController { // #endregion handleRedirectPromise( - hash?: string | undefined // eslint-disable-line @typescript-eslint/no-unused-vars + options?: HandleRedirectPromiseOptions // eslint-disable-line @typescript-eslint/no-unused-vars ): Promise { return Promise.resolve(null); } diff --git a/lib/msal-browser/src/controllers/StandardController.ts b/lib/msal-browser/src/controllers/StandardController.ts index 85bfd17742..2e22f20dae 100644 --- a/lib/msal-browser/src/controllers/StandardController.ts +++ b/lib/msal-browser/src/controllers/StandardController.ts @@ -78,7 +78,7 @@ import { AuthorizationCodeRequest } from "../request/AuthorizationCodeRequest.js import { PlatformAuthRequest } from "../broker/nativeBroker/PlatformAuthRequest.js"; import { StandardOperatingContext } from "../operatingcontext/StandardOperatingContext.js"; import { BaseOperatingContext } from "../operatingcontext/BaseOperatingContext.js"; -import { IController } from "./IController.js"; +import { HandleRedirectPromiseOptions, IController } from "./IController.js"; import { AuthenticationResult } from "../response/AuthenticationResult.js"; import { ClearCacheRequest } from "../request/ClearCacheRequest.js"; import { createNewGuid } from "../crypto/BrowserCrypto.js"; @@ -392,25 +392,25 @@ export class StandardController implements IController { * has loaded during redirect flows. This should be invoked on all page loads involved in redirect * auth flows. * @param hash Hash to process. Defaults to the current value of window.location.hash. Only needs to be provided explicitly if the response to be handled is not contained in the current value. + * @param options Object containing optional configuration for redirect promise handling. * @returns Token response or null. If the return value is null, then no auth redirect was detected. */ async handleRedirectPromise( - hash?: string + options?: HandleRedirectPromiseOptions ): Promise { this.logger.verbose("handleRedirectPromise called"); // Block token acquisition before initialize has been called BrowserUtils.blockAPICallsBeforeInitialize(this.initialized); - if (this.isBrowserEnvironment) { /** * Store the promise on the PublicClientApplication instance if this is the first invocation of handleRedirectPromise, * otherwise return the promise from the first invocation. Prevents race conditions when handleRedirectPromise is called * several times concurrently. */ - const redirectResponseKey = hash || ""; + const redirectResponseKey = options?.hash || ""; let response = this.redirectResponse.get(redirectResponseKey); if (typeof response === "undefined") { - response = this.handleRedirectPromiseInternal(hash); + response = this.handleRedirectPromiseInternal(options); this.redirectResponse.set(redirectResponseKey, response); this.logger.verbose( "handleRedirectPromise has been called for the first time, storing the promise" @@ -435,7 +435,7 @@ export class StandardController implements IController { * @returns */ private async handleRedirectPromiseInternal( - hash?: string + options?: HandleRedirectPromiseOptions ): Promise { if (!this.browserStorage.isInteractionInProgress(true)) { this.logger.info( @@ -458,7 +458,9 @@ export class StandardController implements IController { const platformBrokerRequest: PlatformAuthRequest | null = this.browserStorage.getCachedNativeRequest(); const useNative = - platformBrokerRequest && this.platformAuthProvider && !hash; + platformBrokerRequest && + this.platformAuthProvider && + !options?.hash; let rootMeasurement: InProgressPerformanceEvent; @@ -518,7 +520,7 @@ export class StandardController implements IController { this.logger, this.performanceClient, rootMeasurement.event.correlationId - )(hash, standardRequest, codeVerifier, rootMeasurement); + )(standardRequest, codeVerifier, rootMeasurement, options); } } catch (e) { this.browserStorage.resetRequestCache(); diff --git a/lib/msal-browser/src/controllers/UnknownOperatingContextController.ts b/lib/msal-browser/src/controllers/UnknownOperatingContextController.ts index 882c9b6a9c..4272edc5a7 100644 --- a/lib/msal-browser/src/controllers/UnknownOperatingContextController.ts +++ b/lib/msal-browser/src/controllers/UnknownOperatingContextController.ts @@ -29,7 +29,7 @@ import { SilentRequest } from "../request/SilentRequest.js"; import { SsoSilentRequest } from "../request/SsoSilentRequest.js"; import { AuthenticationResult } from "../response/AuthenticationResult.js"; import { ApiId, WrapperSKU } from "../utils/BrowserConstants.js"; -import { IController } from "./IController.js"; +import { IController, HandleRedirectPromiseOptions } from "./IController.js"; import { UnknownOperatingContext } from "../operatingcontext/UnknownOperatingContext.js"; import { CryptoOps } from "../crypto/CryptoOps.js"; import { @@ -238,7 +238,7 @@ export class UnknownOperatingContextController implements IController { handleRedirectPromise( // eslint-disable-next-line @typescript-eslint/no-unused-vars - hash?: string | undefined + options?: HandleRedirectPromiseOptions ): Promise { blockAPICallsBeforeInitialize(this.initialized); return Promise.resolve(null); diff --git a/lib/msal-browser/src/index.ts b/lib/msal-browser/src/index.ts index 1c825a28e0..6738f0d043 100644 --- a/lib/msal-browser/src/index.ts +++ b/lib/msal-browser/src/index.ts @@ -18,7 +18,10 @@ export { createStandardPublicClientApplication, } from "./app/PublicClientApplication.js"; export { PublicClientNext } from "./app/PublicClientNext.js"; -export { IController } from "./controllers/IController.js"; +export { + IController, + HandleRedirectPromiseOptions, +} from "./controllers/IController.js"; export { Configuration, BrowserAuthOptions, diff --git a/lib/msal-browser/src/interaction_client/PlatformAuthInteractionClient.ts b/lib/msal-browser/src/interaction_client/PlatformAuthInteractionClient.ts index a401585163..e35f9181a9 100644 --- a/lib/msal-browser/src/interaction_client/PlatformAuthInteractionClient.ts +++ b/lib/msal-browser/src/interaction_client/PlatformAuthInteractionClient.ts @@ -72,6 +72,7 @@ import { AuthenticationResult } from "../response/AuthenticationResult.js"; import { base64Decode } from "../encode/Base64Decode.js"; import { version } from "../packageMetadata.js"; import { IPlatformAuthHandler } from "../broker/nativeBroker/IPlatformAuthHandler.js"; +import { HandleRedirectPromiseOptions } from "../controllers/IController.js"; export class PlatformAuthInteractionClient extends BaseInteractionClient { protected apiId: ApiId; @@ -301,16 +302,20 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient { * Acquires a token from native platform then redirects to the redirectUri instead of returning the response * @param {RedirectRequest} request * @param {InProgressPerformanceEvent} rootMeasurement + * @param {HandleRedirectPromiseOptions} options */ async acquireTokenRedirect( request: RedirectRequest, - rootMeasurement: InProgressPerformanceEvent + rootMeasurement: InProgressPerformanceEvent, + options?: HandleRedirectPromiseOptions ): Promise { this.logger.trace( "NativeInteractionClient - acquireTokenRedirect called." ); const nativeRequest = await this.initializeNativeRequest(request); + const navigateToLoginRequestUrl = + options?.navigateToLoginRequestUrl ?? true; try { await this.platformAuthProvider.sendMessage(nativeRequest); @@ -336,7 +341,7 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient { timeout: this.config.system.redirectNavigationTimeout, noHistory: false, }; - const redirectUri = this.config.auth.navigateToLoginRequestUrl + const redirectUri = navigateToLoginRequestUrl ? window.location.href : this.getRedirectUri(request.redirectUri); rootMeasurement.end({ success: true }); diff --git a/lib/msal-browser/src/interaction_client/RedirectClient.ts b/lib/msal-browser/src/interaction_client/RedirectClient.ts index 7a88256d56..1afba3d425 100644 --- a/lib/msal-browser/src/interaction_client/RedirectClient.ts +++ b/lib/msal-browser/src/interaction_client/RedirectClient.ts @@ -49,6 +49,7 @@ import { generatePkceCodes } from "../crypto/PkceGenerator.js"; import { isPlatformAuthAllowed } from "../broker/nativeBroker/PlatformAuthProvider.js"; import { generateEarKey } from "../crypto/BrowserCrypto.js"; import { IPlatformAuthHandler } from "../broker/nativeBroker/IPlatformAuthHandler.js"; +import { HandleRedirectPromiseOptions } from "../controllers/IController.js"; function getNavigationType(): NavigationTimingType | undefined { if ( @@ -283,20 +284,26 @@ export class RedirectClient extends StandardInteractionClient { * - if false, handles hash string and parses response * @param hash {string} url hash * @param parentMeasurement {InProgressPerformanceEvent} parent measurement + * @param request {CommonAuthorizationUrlRequest} request object + * @param pkceVerifier {string} PKCE verifier + * @param options {HandleRedirectPromiseOptions} options for handling redirect promise */ async handleRedirectPromise( - hash: string = "", request: CommonAuthorizationUrlRequest, pkceVerifier: string, - parentMeasurement: InProgressPerformanceEvent + parentMeasurement: InProgressPerformanceEvent, + options?: HandleRedirectPromiseOptions ): Promise { const serverTelemetryManager = this.initializeServerTelemetryManager( ApiId.handleRedirectPromise ); + const navigateToLoginRequestUrl = + options?.navigateToLoginRequestUrl ?? true; + try { const [serverParams, responseString] = this.getRedirectResponse( - hash || "" + options?.hash || "" ); if (!serverParams) { // Not a recognized server response hash or hash not associated with a redirect request @@ -330,7 +337,7 @@ export class RedirectClient extends StandardInteractionClient { if ( loginRequestUrlNormalized === currentUrlNormalized && - this.config.auth.navigateToLoginRequestUrl + navigateToLoginRequestUrl ) { // We are on the page we need to navigate to - handle hash this.logger.verbose( @@ -350,7 +357,7 @@ export class RedirectClient extends StandardInteractionClient { ); return handleHashResult; - } else if (!this.config.auth.navigateToLoginRequestUrl) { + } else if (!navigateToLoginRequestUrl) { this.logger.verbose( "NavigateToLoginRequestUrl set to false, handling response" ); diff --git a/lib/msal-browser/test/app/PublicClientApplication.spec.ts b/lib/msal-browser/test/app/PublicClientApplication.spec.ts index 4325db3949..973e6ae325 100644 --- a/lib/msal-browser/test/app/PublicClientApplication.spec.ts +++ b/lib/msal-browser/test/app/PublicClientApplication.spec.ts @@ -1275,7 +1275,9 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { const promise2 = pca.handleRedirectPromise(); const tokenResponse1 = await promise1; const tokenResponse2 = await promise2; - const tokenResponse3 = await pca.handleRedirectPromise("testHash"); + const tokenResponse3 = await pca.handleRedirectPromise({ + hash: "testHash", + }); expect(tokenResponse3).toBe(null); const tokenResponse4 = await pca.handleRedirectPromise(); diff --git a/lib/msal-browser/test/config/Configuration.spec.ts b/lib/msal-browser/test/config/Configuration.spec.ts index bd3a8d75d2..8b76c62ddf 100644 --- a/lib/msal-browser/test/config/Configuration.spec.ts +++ b/lib/msal-browser/test/config/Configuration.spec.ts @@ -42,7 +42,6 @@ describe("Configuration.ts Class Unit Tests", () => { ); expect(emptyConfig.auth.redirectUri).toBeDefined(); expect(emptyConfig.auth.postLogoutRedirectUri).toBe(""); - expect(emptyConfig.auth.navigateToLoginRequestUrl).toBe(true); expect(emptyConfig.auth?.azureCloudOptions?.azureCloudInstance).toBe( AzureCloudInstance.None ); @@ -234,7 +233,6 @@ describe("Configuration.ts Class Unit Tests", () => { authority: TEST_CONFIG.validAuthority, redirectUri: TEST_URIS.TEST_ALTERNATE_REDIR_URI, postLogoutRedirectUri: TEST_URIS.TEST_LOGOUT_URI, - navigateToLoginRequestUrl: false, }, cache: { cacheLocation: BrowserCacheLocation.LocalStorage, @@ -261,7 +259,6 @@ describe("Configuration.ts Class Unit Tests", () => { expect(newConfig.auth.postLogoutRedirectUri).toBe( TEST_URIS.TEST_LOGOUT_URI ); - expect(newConfig.auth.navigateToLoginRequestUrl).toBe(false); // Cache config checks expect(newConfig.cache).not.toBeNull(); expect(newConfig.cache?.cacheLocation).not.toBeNull(); diff --git a/lib/msal-browser/test/interaction_client/RedirectClient.spec.ts b/lib/msal-browser/test/interaction_client/RedirectClient.spec.ts index 649d873718..727b904efc 100644 --- a/lib/msal-browser/test/interaction_client/RedirectClient.spec.ts +++ b/lib/msal-browser/test/interaction_client/RedirectClient.spec.ts @@ -195,10 +195,10 @@ describe("RedirectClient", () => { redirectClient .handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ) .then((response) => { expect(response).toBe(null); @@ -216,10 +216,10 @@ describe("RedirectClient", () => { redirectClient .handleRedirectPromise( - "#code=ThisIsAnAuthCode", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "#code=ThisIsAnAuthCode" } ) .then((response) => { expect(response).toBe(null); @@ -237,10 +237,10 @@ describe("RedirectClient", () => { window.location.hash = TEST_HASHES.TEST_SUCCESS_CODE_HASH_POPUP; redirectClient .handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ) .then((response) => { expect(response).toBe(null); @@ -260,10 +260,10 @@ describe("RedirectClient", () => { browserStorage.setInteractionInProgress(true); redirectClient .handleRedirectPromise( - TEST_HASHES.TEST_SUCCESS_HASH_STATE_NO_META, testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: TEST_HASHES.TEST_SUCCESS_HASH_STATE_NO_META } ) .then((response) => { expect(response).toBe(null); @@ -285,10 +285,10 @@ describe("RedirectClient", () => { ).mockRejectedValue("Error in handleResponse"); redirectClient .handleRedirectPromise( - TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT } ) .catch((e) => { expect(e).toEqual("Error in handleResponse"); @@ -315,10 +315,10 @@ describe("RedirectClient", () => { ).mockRejectedValue("Error in handleResponse"); redirectClient .handleRedirectPromise( - TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT } ) .catch((e) => { expect(e).toEqual("Error in handleResponse"); @@ -346,7 +346,6 @@ describe("RedirectClient", () => { auth: { // @ts-ignore ...pca.config.auth, - navigateToLoginRequestUrl: false, }, }, browserStorage, @@ -365,10 +364,13 @@ describe("RedirectClient", () => { ); redirectClient .handleRedirectPromise( - TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { + hash: TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, + navigateToLoginRequestUrl: false, + } ) .catch((e) => { expect(e).toEqual("Error in handleResponse"); @@ -455,10 +457,10 @@ describe("RedirectClient", () => { ).mockResolvedValue(testServerTokenResponse); const tokenResponse = await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect(tokenResponse?.uniqueId).toEqual(testTokenResponse.uniqueId); expect(tokenResponse?.tenantId).toEqual(testTokenResponse.tenantId); @@ -607,10 +609,10 @@ describe("RedirectClient", () => { ).mockResolvedValue(testTokenResponse); const tokenResponse = await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect(tokenResponse?.uniqueId).toEqual(testTokenResponse.uniqueId); expect(tokenResponse?.tenantId).toEqual(testTokenResponse.tenantId); @@ -698,10 +700,10 @@ describe("RedirectClient", () => { redirectClient .handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ) .catch((e) => { expect(e.errorCode).toEqual( @@ -753,10 +755,10 @@ describe("RedirectClient", () => { redirectClient .handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ) .catch((err) => { expect(err instanceof ServerError).toBeTruthy(); @@ -850,7 +852,6 @@ describe("RedirectClient", () => { let pca = new PublicClientApplication({ auth: { clientId: TEST_CONFIG.MSAL_CLIENT_ID, - navigateToLoginRequestUrl: false, }, }); @@ -880,10 +881,13 @@ describe("RedirectClient", () => { ); const tokenResponse = await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { + hash: "", + navigateToLoginRequestUrl: false, + } ); expect(tokenResponse?.uniqueId).toEqual(testTokenResponse.uniqueId); expect(tokenResponse?.tenantId).toEqual(testTokenResponse.tenantId); @@ -1034,10 +1038,10 @@ describe("RedirectClient", () => { ); const tokenResponse = await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); if (!tokenResponse) { expect(tokenResponse).not.toBe(null); @@ -1147,7 +1151,6 @@ describe("RedirectClient", () => { let pca = new PublicClientApplication({ auth: { clientId: TEST_CONFIG.MSAL_CLIENT_ID, - navigateToLoginRequestUrl: false, }, }); @@ -1177,10 +1180,13 @@ describe("RedirectClient", () => { ); const tokenResponse = await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { + hash: "", + navigateToLoginRequestUrl: false, + } ); expect(tokenResponse?.uniqueId).toEqual(testTokenResponse.uniqueId); expect(tokenResponse?.tenantId).toEqual(testTokenResponse.tenantId); @@ -1210,10 +1216,10 @@ describe("RedirectClient", () => { ); expect( await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ) ).toBe(null); }); @@ -1246,10 +1252,10 @@ describe("RedirectClient", () => { ); expect( await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ) ).toBe(null); }); @@ -1278,10 +1284,10 @@ describe("RedirectClient", () => { } ); await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect( window.sessionStorage.getItem( @@ -1353,10 +1359,10 @@ describe("RedirectClient", () => { } ); await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect( window.sessionStorage.getItem( @@ -1389,10 +1395,10 @@ describe("RedirectClient", () => { } ); redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect( window.sessionStorage.getItem( @@ -1429,10 +1435,10 @@ describe("RedirectClient", () => { } ); redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect( window.sessionStorage.getItem( @@ -1465,10 +1471,10 @@ describe("RedirectClient", () => { } ); redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect( window.sessionStorage.getItem( @@ -1502,10 +1508,10 @@ describe("RedirectClient", () => { } ); redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect( window.sessionStorage.getItem( @@ -1534,10 +1540,10 @@ describe("RedirectClient", () => { }); redirectClient .handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ) .then(() => { expect(window.location.href).toEqual(loginRequestUrl); @@ -1563,10 +1569,10 @@ describe("RedirectClient", () => { }); redirectClient .handleRedirectPromise( - TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT } ) .then(() => { expect(window.location.href).toEqual(loginRequestUrl); @@ -1600,10 +1606,10 @@ describe("RedirectClient", () => { redirectClient .handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ) .then(() => { expect(clearHashSpy).not.toHaveBeenCalled(); @@ -1633,10 +1639,10 @@ describe("RedirectClient", () => { done(); }); redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); }); @@ -1652,10 +1658,10 @@ describe("RedirectClient", () => { redirectClient .handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ) .then((response) => { expect(response).toBe(null); @@ -1667,7 +1673,6 @@ describe("RedirectClient", () => { let pca = new PublicClientApplication({ auth: { clientId: TEST_CONFIG.MSAL_CLIENT_ID, - navigateToLoginRequestUrl: false, }, }); @@ -1714,10 +1719,13 @@ describe("RedirectClient", () => { done(); }); redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { + hash: "", + navigateToLoginRequestUrl: false, + } ); }); @@ -1735,10 +1743,10 @@ describe("RedirectClient", () => { loginRequestUrl ); const res = await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect(res).toBeNull(); expect(rootMeasurement.event.errorCode).toBeUndefined(); @@ -1758,10 +1766,10 @@ describe("RedirectClient", () => { loginRequestUrl ); const res = await redirectClient.handleRedirectPromise( - "", testRequest, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: "" } ); expect(res).toBeNull(); expect(rootMeasurement.event.errorCode).toEqual( @@ -2090,10 +2098,10 @@ describe("RedirectClient", () => { await redirectClient.acquireToken(request); const tokenResp = await redirectClient.handleRedirectPromise( - TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, request, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT } ); if (!tokenResp) { throw "Response should not be null!"; @@ -2131,10 +2139,10 @@ describe("RedirectClient", () => { await redirectClient.acquireToken(request); const tokenResp = await redirectClient.handleRedirectPromise( - TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, request, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT } ); if (!tokenResp) { throw "Response should not be null!"; @@ -2172,10 +2180,10 @@ describe("RedirectClient", () => { await redirectClient.acquireToken(request); const tokenResp = await redirectClient.handleRedirectPromise( - TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT, request, TEST_CONFIG.TEST_VERIFIER, - rootMeasurement + rootMeasurement, + { hash: TEST_HASHES.TEST_SUCCESS_CODE_HASH_REDIRECT } ); if (!tokenResp) { throw "Response should not be null!"; @@ -3051,9 +3059,9 @@ describe("RedirectClient", () => { await pca.acquireTokenRedirect(validRequest); expect(earFormSpy).toHaveBeenCalled(); - const result = await pca.handleRedirectPromise( - `#ear_jwe=${validEarJWE}&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}` - ); + const result = await pca.handleRedirectPromise({ + hash: `#ear_jwe=${validEarJWE}&state=${TEST_STATE_VALUES.TEST_STATE_REDIRECT}`, + }); expect(result).toEqual(getTestAuthenticationResult()); }); });