Skip to content

Commit 29bf8d4

Browse files
authored
Fix native brokering use case for cache lookups (#7601)
This PR fixes a bug in double brokering use cases, native brokering should not be triggered when cache look up is set to `AccessToken` only.
1 parent 9650c04 commit 29bf8d4

File tree

4 files changed

+118
-8
lines changed

4 files changed

+118
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Fix double brokering bug: Native flow should not be triggered for cache only look up",
4+
"packageName": "@azure/msal-browser",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

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

+13-3
Original file line numberDiff line numberDiff line change
@@ -1519,7 +1519,8 @@ export class StandardController implements IController {
15191519
public async acquireTokenNative(
15201520
request: PopupRequest | SilentRequest | SsoSilentRequest,
15211521
apiId: ApiId,
1522-
accountId?: string
1522+
accountId?: string,
1523+
cacheLookupPolicy?: CacheLookupPolicy
15231524
): Promise<AuthenticationResult> {
15241525
this.logger.trace("acquireTokenNative called");
15251526
if (!this.nativeExtensionProvider) {
@@ -1543,7 +1544,7 @@ export class StandardController implements IController {
15431544
request.correlationId
15441545
);
15451546

1546-
return nativeClient.acquireToken(request);
1547+
return nativeClient.acquireToken(request, cacheLookupPolicy);
15471548
}
15481549

15491550
/**
@@ -2223,6 +2224,7 @@ export class StandardController implements IController {
22232224
silentRequest: CommonSilentFlowRequest,
22242225
cacheLookupPolicy: CacheLookupPolicy
22252226
): Promise<AuthenticationResult> {
2227+
// if the cache policy is set to access_token only, we should not be hitting the native layer yet
22262228
if (
22272229
NativeMessageHandler.isPlatformBrokerAvailable(
22282230
this.config,
@@ -2237,7 +2239,9 @@ export class StandardController implements IController {
22372239
);
22382240
return this.acquireTokenNative(
22392241
silentRequest,
2240-
ApiId.acquireTokenSilent_silentFlow
2242+
ApiId.acquireTokenSilent_silentFlow,
2243+
silentRequest.account.nativeAccountId,
2244+
cacheLookupPolicy
22412245
).catch(async (e: AuthError) => {
22422246
// If native token acquisition fails for availability reasons fallback to web flow
22432247
if (e instanceof NativeAuthError && isFatalNativeAuthError(e)) {
@@ -2257,6 +2261,12 @@ export class StandardController implements IController {
22572261
this.logger.verbose(
22582262
"acquireTokenSilent - attempting to acquire token from web flow"
22592263
);
2264+
// add logs to identify embedded cache retrieval
2265+
if (cacheLookupPolicy === CacheLookupPolicy.AccessToken) {
2266+
this.logger.verbose(
2267+
"acquireTokenSilent - cache lookup policy set to AccessToken, attempting to acquire token from local cache"
2268+
);
2269+
}
22602270
return invokeAsync(
22612271
this.acquireTokenFromCache.bind(this),
22622272
PerformanceEvents.AcquireTokenFromCache,

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
TemporaryCacheKeys,
5353
NativeConstants,
5454
BrowserConstants,
55+
CacheLookupPolicy,
5556
} from "../utils/BrowserConstants.js";
5657
import {
5758
NativeExtensionRequestBody,
@@ -157,7 +158,8 @@ export class NativeInteractionClient extends BaseInteractionClient {
157158
* @param request
158159
*/
159160
async acquireToken(
160-
request: PopupRequest | SilentRequest | SsoSilentRequest
161+
request: PopupRequest | SilentRequest | SsoSilentRequest,
162+
cacheLookupPolicy?: CacheLookupPolicy
161163
): Promise<AuthenticationResult> {
162164
this.performanceClient.addQueueMeasurement(
163165
PerformanceEvents.NativeInteractionClientAcquireToken,
@@ -192,6 +194,12 @@ export class NativeInteractionClient extends BaseInteractionClient {
192194
});
193195
return result;
194196
} catch (e) {
197+
if (cacheLookupPolicy === CacheLookupPolicy.AccessToken) {
198+
this.logger.info(
199+
"MSAL internal Cache does not contain tokens, return error as per cache policy"
200+
);
201+
throw e;
202+
}
195203
// continue with a native call for any and all errors
196204
this.logger.info(
197205
"MSAL internal Cache does not contain tokens, proceed to make a native call"

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

+89-4
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ import {
117117
Configuration,
118118
} from "../../src/config/Configuration.js";
119119
import { buildAccountFromIdTokenClaims, buildIdToken } from "msal-test-utils";
120+
import { nativeConnectionNotEstablished } from "../../src/error/BrowserAuthErrorCodes.js";
120121

121122
const cacheConfig = {
122123
temporaryCacheLocation: BrowserCacheLocation.SessionStorage,
@@ -5773,10 +5774,52 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
57735774
expect(silentIframeSpy).toHaveBeenCalledTimes(0);
57745775
});
57755776

5776-
it("Calls SilentCacheClient.acquireToken and SilentRefreshClient.acquireToken, and does not call SilentIframeClient.acquireToken if cache lookup throws and refresh token is expired when CacheLookupPolicy is set to AccessTokenAndRefreshToken", async () => {
5777+
it("Calls SilentCacheClient.acquireToken, and calls NativeInteractionClient.acquireToken when CacheLookupPolicy is set to AccessToken", async () => {
57775778
const silentCacheSpy: jest.SpyInstance = jest
57785779
.spyOn(SilentCacheClient.prototype, "acquireToken")
57795780
.mockRejectedValue(refreshRequiredCacheError);
5781+
const silentRefreshSpy = jest
5782+
.spyOn(SilentRefreshClient.prototype, "acquireToken")
5783+
.mockImplementation();
5784+
const silentIframeSpy = jest
5785+
.spyOn(SilentIframeClient.prototype, "acquireToken")
5786+
.mockImplementation();
5787+
5788+
const isPlatformBrokerAvailableSpy = jest
5789+
.spyOn(NativeMessageHandler, "isPlatformBrokerAvailable")
5790+
.mockReturnValue(true);
5791+
const nativeAcquireTokenSpy: jest.SpyInstance = jest
5792+
.spyOn(NativeInteractionClient.prototype, "acquireToken")
5793+
.mockImplementation();
5794+
const cacheAccount = testAccount;
5795+
cacheAccount.nativeAccountId = "nativeAccountId";
5796+
5797+
await expect(
5798+
pca.acquireTokenSilent({
5799+
scopes: ["openid"],
5800+
account: cacheAccount,
5801+
cacheLookupPolicy: CacheLookupPolicy.AccessToken,
5802+
})
5803+
)
5804+
.rejects.toThrow(BrowserAuthError)
5805+
.catch((error) => {
5806+
expect(error.errorCode).toBe(
5807+
BrowserAuthErrorCodes.nativeConnectionNotEstablished
5808+
);
5809+
});
5810+
expect(silentCacheSpy).toHaveBeenCalledTimes(0);
5811+
expect(silentRefreshSpy).toHaveBeenCalledTimes(0);
5812+
expect(silentIframeSpy).toHaveBeenCalledTimes(0);
5813+
expect(nativeAcquireTokenSpy).toHaveBeenCalledTimes(0);
5814+
5815+
nativeAcquireTokenSpy.mockRestore();
5816+
isPlatformBrokerAvailableSpy.mockRestore();
5817+
});
5818+
5819+
it("Calls SilentRefreshClient.acquireToken, and does not call SilentCacheClient.acquireToken or SilentIframeClient.acquireToken if refresh token is expired when CacheLookupPolicy is set to RefreshToken", async () => {
5820+
const silentCacheSpy = jest
5821+
.spyOn(SilentCacheClient.prototype, "acquireToken")
5822+
.mockImplementation();
57805823
const silentRefreshSpy: jest.SpyInstance = jest
57815824
.spyOn(SilentRefreshClient.prototype, "acquireToken")
57825825
.mockRejectedValue(refreshRequiredServerError);
@@ -5788,15 +5831,57 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
57885831
pca.acquireTokenSilent({
57895832
scopes: ["openid"],
57905833
account: testAccount,
5791-
cacheLookupPolicy:
5792-
CacheLookupPolicy.AccessTokenAndRefreshToken,
5834+
cacheLookupPolicy: CacheLookupPolicy.RefreshToken,
57935835
})
57945836
).rejects.toThrow(refreshRequiredServerError);
5795-
expect(silentCacheSpy).toHaveBeenCalledTimes(1);
5837+
expect(silentCacheSpy).toHaveBeenCalledTimes(0);
57965838
expect(silentRefreshSpy).toHaveBeenCalledTimes(1);
57975839
expect(silentIframeSpy).toHaveBeenCalledTimes(0);
57985840
});
57995841

5842+
it("Calls NativeInteractionClient.acquireToken when CacheLookupPolicy is set to AccessTokenAndRefreshToken", async () => {
5843+
const silentCacheSpy: jest.SpyInstance = jest
5844+
.spyOn(SilentCacheClient.prototype, "acquireToken")
5845+
.mockRejectedValue(refreshRequiredCacheError);
5846+
const silentRefreshSpy: jest.SpyInstance = jest
5847+
.spyOn(SilentRefreshClient.prototype, "acquireToken")
5848+
.mockRejectedValue(refreshRequiredServerError);
5849+
const silentIframeSpy = jest
5850+
.spyOn(SilentIframeClient.prototype, "acquireToken")
5851+
.mockImplementation();
5852+
const nativeAcquireTokenSpy: jest.SpyInstance = jest
5853+
.spyOn(NativeInteractionClient.prototype, "acquireToken")
5854+
.mockImplementation();
5855+
5856+
const cacheAccount = testAccount;
5857+
cacheAccount.nativeAccountId = "nativeAccountId";
5858+
const isPlatformBrokerAvailableSpy = jest
5859+
.spyOn(NativeMessageHandler, "isPlatformBrokerAvailable")
5860+
.mockReturnValue(true);
5861+
testAccount.nativeAccountId = "nativeAccountId";
5862+
5863+
await expect(
5864+
pca.acquireTokenSilent({
5865+
scopes: ["openid"],
5866+
account: cacheAccount,
5867+
cacheLookupPolicy:
5868+
CacheLookupPolicy.AccessTokenAndRefreshToken,
5869+
})
5870+
)
5871+
.rejects.toThrow(BrowserAuthError)
5872+
.catch((error) => {
5873+
expect(error.errorCode).toBe(
5874+
BrowserAuthErrorCodes.nativeConnectionNotEstablished
5875+
);
5876+
});
5877+
expect(silentCacheSpy).toHaveBeenCalledTimes(0);
5878+
expect(silentRefreshSpy).toHaveBeenCalledTimes(0);
5879+
expect(silentIframeSpy).toHaveBeenCalledTimes(0);
5880+
expect(nativeAcquireTokenSpy).toHaveBeenCalledTimes(0);
5881+
nativeAcquireTokenSpy.mockRestore();
5882+
isPlatformBrokerAvailableSpy.mockRestore();
5883+
});
5884+
58005885
it("Calls SilentRefreshClient.acquireToken, and does not call SilentCacheClient.acquireToken or SilentIframeClient.acquireToken if refresh token is expired when CacheLookupPolicy is set to RefreshToken", async () => {
58015886
const silentCacheSpy = jest
58025887
.spyOn(SilentCacheClient.prototype, "acquireToken")

0 commit comments

Comments
 (0)