Skip to content

Commit 972b993

Browse files
Fix a bug in handleRedirectPromise when invoked after logoutRedirect (#7680)
- Add interaction type to "interaction_in_progress" temporary key to return from "handleRedirectPromise" early if invoked after signout. This fixes a bug with the login flow being blocked after user logs out and improves sign-out perf. - Add try-catch block to the first parst of "handleRedirectPromise" to make sure users can recover from "no_token_request_cache_error" if session storage gets cleared during page navigation. - Simplify redirect telemetry instrumentation.
1 parent c08754d commit 972b993

File tree

6 files changed

+208
-89
lines changed

6 files changed

+208
-89
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "Fix a bug in handleRedirectPromise when invoked after logoutRedirect #7680",
4+
"packageName": "@azure/msal-browser",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

lib/msal-browser/src/cache/BrowserCacheManager.ts

+43-31
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,50 @@
44
*/
55

66
import {
7-
Constants,
8-
PersistentCacheKeys,
9-
StringUtils,
10-
ICrypto,
11-
AccountEntity,
12-
IdTokenEntity,
137
AccessTokenEntity,
14-
RefreshTokenEntity,
15-
AppMetadataEntity,
16-
CacheManager,
17-
ServerTelemetryEntity,
18-
ThrottlingEntity,
19-
Logger,
20-
AuthorityMetadataEntity,
21-
DEFAULT_CRYPTO_IMPLEMENTATION,
8+
AccountEntity,
229
AccountInfo,
2310
ActiveAccountFilters,
24-
TokenKeys,
25-
CredentialType,
26-
CacheRecord,
11+
AppMetadataEntity,
2712
AuthenticationScheme,
28-
createClientAuthError,
13+
AuthorityMetadataEntity,
14+
CacheError,
15+
CacheHelpers,
16+
CacheManager,
17+
CacheRecord,
2918
ClientAuthErrorCodes,
30-
PerformanceEvents,
19+
CommonAuthorizationUrlRequest,
20+
Constants,
21+
createClientAuthError,
22+
CredentialType,
23+
DEFAULT_CRYPTO_IMPLEMENTATION,
24+
ICrypto,
25+
IdTokenEntity,
26+
invokeAsync,
3127
IPerformanceClient,
28+
Logger,
29+
PerformanceEvents,
30+
PersistentCacheKeys,
31+
RefreshTokenEntity,
32+
ServerTelemetryEntity,
3233
StaticAuthorityOptions,
33-
CacheHelpers,
3434
StoreInCache,
35-
CacheError,
36-
invokeAsync,
35+
StringUtils,
36+
ThrottlingEntity,
3737
TimeUtils,
38-
CommonAuthorizationUrlRequest,
38+
TokenKeys,
3939
} from "@azure/msal-common/browser";
4040
import { CacheOptions } from "../config/Configuration.js";
4141
import {
42-
createBrowserAuthError,
4342
BrowserAuthErrorCodes,
43+
createBrowserAuthError,
4444
} from "../error/BrowserAuthError.js";
4545
import {
4646
BrowserCacheLocation,
47-
TemporaryCacheKeys,
4847
InMemoryCacheKeys,
48+
INTERACTION_TYPE,
4949
StaticCacheKeys,
50+
TemporaryCacheKeys,
5051
} from "../utils/BrowserConstants.js";
5152
import { LocalStorage } from "./LocalStorage.js";
5253
import { SessionStorage } from "./SessionStorage.js";
@@ -1187,7 +1188,7 @@ export class BrowserCacheManager extends CacheManager {
11871188
}
11881189

11891190
isInteractionInProgress(matchClientId?: boolean): boolean {
1190-
const clientId = this.getInteractionInProgress();
1191+
const clientId = this.getInteractionInProgress()?.clientId;
11911192

11921193
if (matchClientId) {
11931194
return clientId === this.clientId;
@@ -1196,12 +1197,19 @@ export class BrowserCacheManager extends CacheManager {
11961197
}
11971198
}
11981199

1199-
getInteractionInProgress(): string | null {
1200+
getInteractionInProgress(): {
1201+
clientId: string;
1202+
type: INTERACTION_TYPE;
1203+
} | null {
12001204
const key = `${Constants.CACHE_PREFIX}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`;
1201-
return this.getTemporaryCache(key, false);
1205+
const value = this.getTemporaryCache(key, false);
1206+
return value ? JSON.parse(value) : null;
12021207
}
12031208

1204-
setInteractionInProgress(inProgress: boolean): void {
1209+
setInteractionInProgress(
1210+
inProgress: boolean,
1211+
type: INTERACTION_TYPE = INTERACTION_TYPE.SIGNIN
1212+
): void {
12051213
// Ensure we don't overwrite interaction in progress for a different clientId
12061214
const key = `${Constants.CACHE_PREFIX}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`;
12071215
if (inProgress) {
@@ -1211,11 +1219,15 @@ export class BrowserCacheManager extends CacheManager {
12111219
);
12121220
} else {
12131221
// No interaction is in progress
1214-
this.setTemporaryCache(key, this.clientId, false);
1222+
this.setTemporaryCache(
1223+
key,
1224+
JSON.stringify({ clientId: this.clientId, type }),
1225+
false
1226+
);
12151227
}
12161228
} else if (
12171229
!inProgress &&
1218-
this.getInteractionInProgress() === this.clientId
1230+
this.getInteractionInProgress()?.clientId === this.clientId
12191231
) {
12201232
this.removeTemporaryItem(key);
12211233
}

lib/msal-browser/src/controllers/StandardController.ts

+82-54
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
DEFAULT_REQUEST,
4646
BrowserConstants,
4747
iFrameRenewalPolicies,
48+
INTERACTION_TYPE,
4849
} from "../utils/BrowserConstants.js";
4950
import * as BrowserUtils from "../utils/BrowserUtils.js";
5051
import { RedirectRequest } from "../request/RedirectRequest.js";
@@ -443,6 +444,16 @@ export class StandardController implements IController {
443444
return null;
444445
}
445446

447+
const interactionType =
448+
this.browserStorage.getInteractionInProgress()?.type;
449+
if (interactionType === INTERACTION_TYPE.SIGNOUT) {
450+
this.logger.verbose(
451+
"handleRedirectPromise removing interaction_in_progress flag and returning null after sign-out"
452+
);
453+
this.browserStorage.setInteractionInProgress(false);
454+
return Promise.resolve(null);
455+
}
456+
446457
const loggedInAccounts = this.getAllAccounts();
447458
const platformBrokerRequest: NativeTokenRequest | null =
448459
this.browserStorage.getCachedNativeRequest();
@@ -455,63 +466,68 @@ export class StandardController implements IController {
455466
) &&
456467
this.nativeExtensionProvider &&
457468
!hash;
458-
let rootMeasurement = this.performanceClient.startMeasurement(
459-
PerformanceEvents.AcquireTokenRedirect,
460-
platformBrokerRequest?.correlationId || ""
461-
);
469+
let rootMeasurement: InProgressPerformanceEvent;
462470
this.eventHandler.emitEvent(
463471
EventType.HANDLE_REDIRECT_START,
464472
InteractionType.Redirect
465473
);
466474

467475
let redirectResponse: Promise<AuthenticationResult | null>;
468-
if (useNative && this.nativeExtensionProvider) {
469-
this.logger.trace(
470-
"handleRedirectPromise - acquiring token from native platform"
471-
);
472-
const nativeClient = new NativeInteractionClient(
473-
this.config,
474-
this.browserStorage,
475-
this.browserCrypto,
476-
this.logger,
477-
this.eventHandler,
478-
this.navigationClient,
479-
ApiId.handleRedirectPromise,
480-
this.performanceClient,
481-
this.nativeExtensionProvider,
482-
platformBrokerRequest.accountId,
483-
this.nativeInternalStorage,
484-
platformBrokerRequest.correlationId
485-
);
476+
try {
477+
if (useNative && this.nativeExtensionProvider) {
478+
rootMeasurement = this.performanceClient.startMeasurement(
479+
PerformanceEvents.AcquireTokenRedirect,
480+
platformBrokerRequest?.correlationId || ""
481+
);
482+
this.logger.trace(
483+
"handleRedirectPromise - acquiring token from native platform"
484+
);
485+
const nativeClient = new NativeInteractionClient(
486+
this.config,
487+
this.browserStorage,
488+
this.browserCrypto,
489+
this.logger,
490+
this.eventHandler,
491+
this.navigationClient,
492+
ApiId.handleRedirectPromise,
493+
this.performanceClient,
494+
this.nativeExtensionProvider,
495+
platformBrokerRequest.accountId,
496+
this.nativeInternalStorage,
497+
platformBrokerRequest.correlationId
498+
);
486499

487-
redirectResponse = invokeAsync(
488-
nativeClient.handleRedirectPromise.bind(nativeClient),
489-
PerformanceEvents.HandleNativeRedirectPromiseMeasurement,
490-
this.logger,
491-
this.performanceClient,
492-
rootMeasurement.event.correlationId
493-
)(this.performanceClient, rootMeasurement.event.correlationId);
494-
} else {
495-
const [standardRequest, codeVerifier] =
496-
this.browserStorage.getCachedRequest();
497-
const correlationId = standardRequest.correlationId;
498-
// Reset rootMeasurement now that we have correlationId
499-
rootMeasurement.discard();
500-
rootMeasurement = this.performanceClient.startMeasurement(
501-
PerformanceEvents.AcquireTokenRedirect,
502-
correlationId
503-
);
504-
this.logger.trace(
505-
"handleRedirectPromise - acquiring token from web flow"
506-
);
507-
const redirectClient = this.createRedirectClient(correlationId);
508-
redirectResponse = invokeAsync(
509-
redirectClient.handleRedirectPromise.bind(redirectClient),
510-
PerformanceEvents.HandleRedirectPromiseMeasurement,
511-
this.logger,
512-
this.performanceClient,
513-
rootMeasurement.event.correlationId
514-
)(hash, standardRequest, codeVerifier, rootMeasurement);
500+
redirectResponse = invokeAsync(
501+
nativeClient.handleRedirectPromise.bind(nativeClient),
502+
PerformanceEvents.HandleNativeRedirectPromiseMeasurement,
503+
this.logger,
504+
this.performanceClient,
505+
rootMeasurement.event.correlationId
506+
)(this.performanceClient, rootMeasurement.event.correlationId);
507+
} else {
508+
const [standardRequest, codeVerifier] =
509+
this.browserStorage.getCachedRequest();
510+
const correlationId = standardRequest.correlationId;
511+
// Reset rootMeasurement now that we have correlationId
512+
rootMeasurement = this.performanceClient.startMeasurement(
513+
PerformanceEvents.AcquireTokenRedirect,
514+
correlationId
515+
);
516+
this.logger.trace(
517+
"handleRedirectPromise - acquiring token from web flow"
518+
);
519+
const redirectClient = this.createRedirectClient(correlationId);
520+
redirectResponse = invokeAsync(
521+
redirectClient.handleRedirectPromise.bind(redirectClient),
522+
PerformanceEvents.HandleRedirectPromiseMeasurement,
523+
this.logger,
524+
this.performanceClient,
525+
rootMeasurement.event.correlationId
526+
)(hash, standardRequest, codeVerifier, rootMeasurement);
527+
}
528+
} catch (e) {
529+
this.browserStorage.resetRequestCache();
530+
throw e;
515531
}
516532

517533
return redirectResponse
@@ -657,7 +673,10 @@ export class StandardController implements IController {
657673
const isLoggedIn = this.getAllAccounts().length > 0;
658674
try {
659675
BrowserUtils.redirectPreflightCheck(this.initialized, this.config);
660-
this.browserStorage.setInteractionInProgress(true);
676+
this.browserStorage.setInteractionInProgress(
677+
true,
678+
INTERACTION_TYPE.SIGNIN
679+
);
661680

662681
if (isLoggedIn) {
663682
this.eventHandler.emitEvent(
@@ -768,7 +787,10 @@ export class StandardController implements IController {
768787
try {
769788
this.logger.verbose("acquireTokenPopup called", correlationId);
770789
preflightCheck(this.initialized, atPopupMeasurement);
771-
this.browserStorage.setInteractionInProgress(true);
790+
this.browserStorage.setInteractionInProgress(
791+
true,
792+
INTERACTION_TYPE.SIGNIN
793+
);
772794
} catch (e) {
773795
// Since this function is syncronous we need to reject
774796
return Promise.reject(e);
@@ -1349,7 +1371,10 @@ export class StandardController implements IController {
13491371
async logoutRedirect(logoutRequest?: EndSessionRequest): Promise<void> {
13501372
const correlationId = this.getRequestCorrelationId(logoutRequest);
13511373
BrowserUtils.redirectPreflightCheck(this.initialized, this.config);
1352-
this.browserStorage.setInteractionInProgress(true);
1374+
this.browserStorage.setInteractionInProgress(
1375+
true,
1376+
INTERACTION_TYPE.SIGNOUT
1377+
);
13531378

13541379
const redirectClient = this.createRedirectClient(correlationId);
13551380
return redirectClient.logout(logoutRequest);
@@ -1363,7 +1388,10 @@ export class StandardController implements IController {
13631388
try {
13641389
const correlationId = this.getRequestCorrelationId(logoutRequest);
13651390
BrowserUtils.preflightCheck(this.initialized);
1366-
this.browserStorage.setInteractionInProgress(true);
1391+
this.browserStorage.setInteractionInProgress(
1392+
true,
1393+
INTERACTION_TYPE.SIGNOUT
1394+
);
13671395

13681396
const popupClient = this.createPopupClient(correlationId);
13691397
return popupClient.logout(logoutRequest).finally(() => {

lib/msal-browser/src/interaction_client/RedirectClient.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import { StandardInteractionClient } from "./StandardInteractionClient.js";
2525
import {
2626
ApiId,
27+
INTERACTION_TYPE,
2728
InteractionType,
2829
TemporaryCacheKeys,
2930
} from "../utils/BrowserConstants.js";
@@ -740,7 +741,10 @@ export class RedirectClient extends StandardInteractionClient {
740741
);
741742
// Ensure interaction is in progress
742743
if (!this.browserStorage.getInteractionInProgress()) {
743-
this.browserStorage.setInteractionInProgress(true);
744+
this.browserStorage.setInteractionInProgress(
745+
true,
746+
INTERACTION_TYPE.SIGNOUT
747+
);
744748
}
745749
await this.navigationClient.navigateExternal(
746750
logoutUri,
@@ -757,7 +761,10 @@ export class RedirectClient extends StandardInteractionClient {
757761
} else {
758762
// Ensure interaction is in progress
759763
if (!this.browserStorage.getInteractionInProgress()) {
760-
this.browserStorage.setInteractionInProgress(true);
764+
this.browserStorage.setInteractionInProgress(
765+
true,
766+
INTERACTION_TYPE.SIGNOUT
767+
);
761768
}
762769
await this.navigationClient.navigateExternal(
763770
logoutUri,

lib/msal-browser/src/utils/BrowserConstants.ts

+7
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ export const HTTP_REQUEST_TYPE = {
7474
export type HTTP_REQUEST_TYPE =
7575
(typeof HTTP_REQUEST_TYPE)[keyof typeof HTTP_REQUEST_TYPE];
7676

77+
export const INTERACTION_TYPE = {
78+
SIGNIN: "signin",
79+
SIGNOUT: "signout",
80+
} as const;
81+
export type INTERACTION_TYPE =
82+
(typeof INTERACTION_TYPE)[keyof typeof INTERACTION_TYPE];
83+
7784
/**
7885
* Temporary cache keys for MSAL, deleted after any request.
7986
*/

0 commit comments

Comments
 (0)