Skip to content

Commit a944241

Browse files
authored
Fix iframe fallback when RT is not found in cache (#6599)
It seems this fallback behavior was missed in the transition from v2 to v3. When the RT cannot be found in the cache we should try to fallback to the iframe flow. Also includes minor refactor to make the code more readable and remove some unnecessary work.
1 parent 289b937 commit a944241

File tree

5 files changed

+100
-71
lines changed

5 files changed

+100
-71
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Fix iframe fallback when RT is not found in cache",
4+
"packageName": "@azure/msal-browser",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
Logger,
99
PerformanceCallbackFunction,
1010
IPerformanceClient,
11-
CommonSilentFlowRequest,
1211
AccountFilter,
1312
} from "@azure/msal-common";
1413
import { RedirectRequest } from "../request/RedirectRequest";
@@ -48,11 +47,6 @@ export interface IController {
4847
accountId?: string
4948
): Promise<AuthenticationResult>;
5049

51-
acquireTokenByRefreshToken(
52-
commonRequest: CommonSilentFlowRequest,
53-
silentRequest: SilentRequest
54-
): Promise<AuthenticationResult>;
55-
5650
addEventCallback(callback: EventCallbackFunction): string | null;
5751

5852
removeEventCallback(callbackId: string): void;

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

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
PromptValue,
2222
InProgressPerformanceEvent,
2323
RequestThumbprint,
24-
ServerError,
2524
AccountEntity,
2625
ServerResponseType,
2726
UrlString,
@@ -30,6 +29,7 @@ import {
3029
ClientAuthErrorCodes,
3130
AccountFilter,
3231
buildStaticAuthorityOptions,
32+
InteractionRequiredAuthErrorCodes,
3333
} from "@azure/msal-common";
3434
import {
3535
BrowserCacheManager,
@@ -1047,13 +1047,13 @@ export class StandardController implements IController {
10471047
protected async acquireTokenFromCache(
10481048
silentCacheClient: SilentCacheClient,
10491049
commonRequest: CommonSilentFlowRequest,
1050-
silentRequest: SilentRequest
1050+
cacheLookupPolicy: CacheLookupPolicy
10511051
): Promise<AuthenticationResult> {
10521052
this.performanceClient.addQueueMeasurement(
10531053
PerformanceEvents.AcquireTokenFromCache,
10541054
commonRequest.correlationId
10551055
);
1056-
switch (silentRequest.cacheLookupPolicy) {
1056+
switch (cacheLookupPolicy) {
10571057
case CacheLookupPolicy.Default:
10581058
case CacheLookupPolicy.AccessToken:
10591059
case CacheLookupPolicy.AccessTokenAndRefreshToken:
@@ -1074,18 +1074,18 @@ export class StandardController implements IController {
10741074
/**
10751075
* Attempt to acquire an access token via a refresh token
10761076
* @param commonRequest CommonSilentFlowRequest
1077-
* @param silentRequest SilentRequest
1077+
* @param cacheLookupPolicy CacheLookupPolicy
10781078
* @returns A promise that, when resolved, returns the access token
10791079
*/
10801080
public async acquireTokenByRefreshToken(
10811081
commonRequest: CommonSilentFlowRequest,
1082-
silentRequest: SilentRequest
1082+
cacheLookupPolicy: CacheLookupPolicy
10831083
): Promise<AuthenticationResult> {
10841084
this.performanceClient.addQueueMeasurement(
10851085
PerformanceEvents.AcquireTokenByRefreshToken,
10861086
commonRequest.correlationId
10871087
);
1088-
switch (silentRequest.cacheLookupPolicy) {
1088+
switch (cacheLookupPolicy) {
10891089
case CacheLookupPolicy.Default:
10901090
case CacheLookupPolicy.AccessTokenAndRefreshToken:
10911091
case CacheLookupPolicy.RefreshToken:
@@ -2055,23 +2055,19 @@ export class StandardController implements IController {
20552055
request.correlationId
20562056
)(request, account);
20572057

2058-
const requestWithCLP = {
2059-
...request,
2060-
// set the request's CacheLookupPolicy to Default if it was not optionally passed in
2061-
cacheLookupPolicy:
2062-
request.cacheLookupPolicy || CacheLookupPolicy.Default,
2063-
};
2058+
const cacheLookupPolicy =
2059+
request.cacheLookupPolicy || CacheLookupPolicy.Default;
20642060

20652061
result = invokeAsync(
20662062
this.acquireTokenFromCache.bind(this),
20672063
PerformanceEvents.AcquireTokenFromCache,
20682064
this.logger,
20692065
this.performanceClient,
20702066
silentRequest.correlationId
2071-
)(silentCacheClient, silentRequest, requestWithCLP).catch(
2067+
)(silentCacheClient, silentRequest, cacheLookupPolicy).catch(
20722068
(cacheError: AuthError) => {
20732069
if (
2074-
requestWithCLP.cacheLookupPolicy ===
2070+
request.cacheLookupPolicy ===
20752071
CacheLookupPolicy.AccessToken
20762072
) {
20772073
throw cacheError;
@@ -2091,42 +2087,42 @@ export class StandardController implements IController {
20912087
this.logger,
20922088
this.performanceClient,
20932089
silentRequest.correlationId
2094-
)(silentRequest, requestWithCLP).catch(
2090+
)(silentRequest, cacheLookupPolicy).catch(
20952091
(refreshTokenError: AuthError) => {
2096-
const isServerError =
2097-
refreshTokenError instanceof ServerError;
2098-
const isInteractionRequiredError =
2099-
refreshTokenError instanceof
2100-
InteractionRequiredAuthError;
2101-
const isInvalidGrantError =
2092+
const isSilentlyResolvable =
2093+
(!(
2094+
refreshTokenError instanceof
2095+
InteractionRequiredAuthError
2096+
) &&
2097+
(refreshTokenError.errorCode ===
2098+
BrowserConstants.INVALID_GRANT_ERROR ||
2099+
refreshTokenError.errorCode ===
2100+
ClientAuthErrorCodes.tokenRefreshRequired)) ||
21022101
refreshTokenError.errorCode ===
2103-
BrowserConstants.INVALID_GRANT_ERROR;
2104-
2105-
if (
2106-
(!isServerError ||
2107-
!isInvalidGrantError ||
2108-
isInteractionRequiredError ||
2109-
requestWithCLP.cacheLookupPolicy ===
2110-
CacheLookupPolicy.AccessTokenAndRefreshToken ||
2111-
requestWithCLP.cacheLookupPolicy ===
2112-
CacheLookupPolicy.RefreshToken) &&
2113-
requestWithCLP.cacheLookupPolicy !==
2114-
CacheLookupPolicy.Skip
2115-
) {
2102+
InteractionRequiredAuthErrorCodes.noTokensFound;
2103+
2104+
const tryIframeRenewal =
2105+
cacheLookupPolicy ===
2106+
CacheLookupPolicy.Default ||
2107+
cacheLookupPolicy === CacheLookupPolicy.Skip ||
2108+
cacheLookupPolicy ===
2109+
CacheLookupPolicy.RefreshTokenAndNetwork;
2110+
2111+
if (isSilentlyResolvable && tryIframeRenewal) {
2112+
this.logger.verbose(
2113+
"Refresh token expired/invalid or CacheLookupPolicy is set to Skip, attempting acquire token by iframe.",
2114+
request.correlationId
2115+
);
2116+
return invokeAsync(
2117+
this.acquireTokenBySilentIframe.bind(this),
2118+
PerformanceEvents.AcquireTokenBySilentIframe,
2119+
this.logger,
2120+
this.performanceClient,
2121+
silentRequest.correlationId
2122+
)(silentRequest);
2123+
} else {
21162124
throw refreshTokenError;
21172125
}
2118-
2119-
this.logger.verbose(
2120-
"Refresh token expired/invalid or CacheLookupPolicy is set to Skip, attempting acquire token by iframe.",
2121-
request.correlationId
2122-
);
2123-
return invokeAsync(
2124-
this.acquireTokenBySilentIframe.bind(this),
2125-
PerformanceEvents.AcquireTokenBySilentIframe,
2126-
this.logger,
2127-
this.performanceClient,
2128-
silentRequest.correlationId
2129-
)(silentRequest);
21302126
}
21312127
);
21322128
}

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

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export class SilentRefreshClient extends StandardInteractionClient {
5353
silentRequest.authority,
5454
silentRequest.azureCloudOptions
5555
);
56-
this.logger.verbose("Refresh token client created");
5756
// Send request to renew token. Auth module will throw errors if token cannot be renewed.
5857
return invokeAsync(
5958
refreshTokenClient.acquireTokenByRefreshToken.bind(
@@ -63,24 +62,11 @@ export class SilentRefreshClient extends StandardInteractionClient {
6362
this.logger,
6463
this.performanceClient,
6564
request.correlationId
66-
)(silentRequest)
67-
.then((result) => result as AuthenticationResult)
68-
.then((result: AuthenticationResult) => {
69-
this.performanceClient.addFields(
70-
{
71-
fromCache: result.fromCache,
72-
requestId: result.requestId,
73-
},
74-
request.correlationId
75-
);
76-
77-
return result;
78-
})
79-
.catch((e: AuthError) => {
80-
(e as AuthError).setCorrelationId(this.correlationId);
81-
serverTelemetryManager.cacheFailedRequest(e);
82-
throw e;
83-
});
65+
)(silentRequest).catch((e: AuthError) => {
66+
(e as AuthError).setCorrelationId(this.correlationId);
67+
serverTelemetryManager.cacheFailedRequest(e);
68+
throw e;
69+
}) as Promise<AuthenticationResult>;
8470
}
8571

8672
/**

lib/msal-browser/test/app/PublicClientApplication.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3461,6 +3461,52 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
34613461
expect(silentIframeSpy.calledOnce).toBe(true);
34623462
});
34633463

3464+
it("Calls SilentIframeClient.acquireToken and returns its response if no RT is cached", async () => {
3465+
const testAccount: AccountInfo = {
3466+
homeAccountId: TEST_DATA_CLIENT_INFO.TEST_HOME_ACCOUNT_ID,
3467+
localAccountId: TEST_DATA_CLIENT_INFO.TEST_UID,
3468+
environment: "login.windows.net",
3469+
tenantId: "3338040d-6c67-4c5b-b112-36a304b66dad",
3470+
username: "[email protected]",
3471+
};
3472+
const testTokenResponse: AuthenticationResult = {
3473+
authority: TEST_CONFIG.validAuthority,
3474+
uniqueId: testAccount.localAccountId,
3475+
tenantId: testAccount.tenantId,
3476+
scopes: TEST_CONFIG.DEFAULT_SCOPES,
3477+
idToken: "test-idToken",
3478+
idTokenClaims: {},
3479+
accessToken: "test-accessToken",
3480+
fromCache: false,
3481+
correlationId: RANDOM_TEST_GUID,
3482+
expiresOn: new Date(Date.now() + 3600000),
3483+
account: testAccount,
3484+
tokenType: AuthenticationScheme.BEARER,
3485+
};
3486+
const silentCacheSpy = sinon
3487+
.stub(SilentCacheClient.prototype, "acquireToken")
3488+
.rejects("Expired");
3489+
const silentRefreshSpy = sinon
3490+
.stub(SilentRefreshClient.prototype, "acquireToken")
3491+
.rejects(
3492+
createInteractionRequiredAuthError(
3493+
InteractionRequiredAuthErrorCodes.noTokensFound
3494+
)
3495+
);
3496+
const silentIframeSpy = sinon
3497+
.stub(SilentIframeClient.prototype, "acquireToken")
3498+
.resolves(testTokenResponse);
3499+
3500+
const response = await pca.acquireTokenSilent({
3501+
scopes: ["openid"],
3502+
account: testAccount,
3503+
});
3504+
expect(response).toEqual(testTokenResponse);
3505+
expect(silentCacheSpy.calledOnce).toBe(true);
3506+
expect(silentRefreshSpy.calledOnce).toBe(true);
3507+
expect(silentIframeSpy.calledOnce).toBe(true);
3508+
});
3509+
34643510
it("makes one network request with multiple parallel silent requests with same request", async () => {
34653511
const testServerTokenResponse = {
34663512
token_type: TEST_CONFIG.TOKEN_TYPE_BEARER,

0 commit comments

Comments
 (0)