Skip to content

Commit fa85c44

Browse files
authored
[v4] Remove access tokens synchronously (#7822)
Updates the remove access token cache logic to perform its steps synchronously. This is a pre-requisite for additional work to handle cache quota errors in the near future. This requires pushing the POP key removal (which is asynchronous) to the background, we will track failures around this in telemetry instead of throwing an error developers can't resolve anyway.
1 parent 75f6237 commit fa85c44

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+371
-774
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": "remove access tokens synchronously",
4+
"packageName": "@azure/msal-browser",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "remove access tokens synchronously",
4+
"packageName": "@azure/msal-common",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "remove access tokens synchronously",
4+
"packageName": "@azure/msal-node",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ export class BrowserCacheManager extends CacheManager {
8686
protected cookieStorage: CookieStorage;
8787
// Logger instance
8888
protected logger: Logger;
89-
// Telemetry perf client
90-
protected performanceClient: IPerformanceClient;
9189
// Event Handler
9290
private eventHandler: EventHandler;
9391

@@ -100,7 +98,13 @@ export class BrowserCacheManager extends CacheManager {
10098
eventHandler: EventHandler,
10199
staticAuthorityOptions?: StaticAuthorityOptions
102100
) {
103-
super(clientId, cryptoImpl, logger, staticAuthorityOptions);
101+
super(
102+
clientId,
103+
cryptoImpl,
104+
logger,
105+
performanceClient,
106+
staticAuthorityOptions
107+
);
104108
this.cacheConfig = cacheConfig;
105109
this.logger = logger;
106110
this.internalStorage = new MemoryStorage();
@@ -118,7 +122,6 @@ export class BrowserCacheManager extends CacheManager {
118122
);
119123
this.cookieStorage = new CookieStorage();
120124

121-
this.performanceClient = performanceClient;
122125
this.eventHandler = eventHandler;
123126
}
124127

@@ -298,17 +301,17 @@ export class BrowserCacheManager extends CacheManager {
298301
* Extends inherited removeAccount function to include removal of the account key from the map
299302
* @param key
300303
*/
301-
async removeAccount(key: string): Promise<void> {
302-
void super.removeAccount(key);
304+
removeAccount(key: string, correlationId: string): void {
305+
super.removeAccount(key, correlationId);
303306
this.removeAccountKeyFromMap(key);
304307
}
305308

306309
/**
307310
* Removes credentials associated with the provided account
308311
* @param account
309312
*/
310-
async removeAccountContext(account: AccountEntity): Promise<void> {
311-
await super.removeAccountContext(account);
313+
removeAccountContext(account: AccountEntity, correlationId: string): void {
314+
super.removeAccountContext(account, correlationId);
312315

313316
/**
314317
* @deprecated - Remove this in next major version in favor of more consistent LOGOUT event
@@ -337,8 +340,8 @@ export class BrowserCacheManager extends CacheManager {
337340
* Removes given accessToken from the cache and from the key map
338341
* @param key
339342
*/
340-
async removeAccessToken(key: string): Promise<void> {
341-
void super.removeAccessToken(key);
343+
removeAccessToken(key: string, correlationId: string): void {
344+
super.removeAccessToken(key, correlationId);
342345
this.removeTokenKey(key, CredentialType.ACCESS_TOKEN);
343346
}
344347

@@ -1012,9 +1015,9 @@ export class BrowserCacheManager extends CacheManager {
10121015
/**
10131016
* Clears all cache entries created by MSAL.
10141017
*/
1015-
async clear(): Promise<void> {
1018+
clear(correlationId: string): void {
10161019
// Removes all accounts and their credentials
1017-
await this.removeAllAccounts();
1020+
this.removeAllAccounts(correlationId);
10181021
this.removeAppMetadata();
10191022

10201023
// Remove temp storage first to make sure any cookies are cleared
@@ -1046,34 +1049,30 @@ export class BrowserCacheManager extends CacheManager {
10461049
* @param correlationId {string} correlation id
10471050
* @returns
10481051
*/
1049-
async clearTokensAndKeysWithClaims(
1050-
performanceClient: IPerformanceClient,
1051-
correlationId: string
1052-
): Promise<void> {
1053-
performanceClient.addQueueMeasurement(
1052+
clearTokensAndKeysWithClaims(correlationId: string): void {
1053+
this.performanceClient.addQueueMeasurement(
10541054
PerformanceEvents.ClearTokensAndKeysWithClaims,
10551055
correlationId
10561056
);
10571057

10581058
const tokenKeys = this.getTokenKeys();
1059-
1060-
const removedAccessTokens: Array<Promise<void>> = [];
1059+
let removedAccessTokens = 0;
10611060
tokenKeys.accessToken.forEach((key: string) => {
10621061
// if the access token has claims in its key, remove the token key and the token
10631062
const credential = this.getAccessTokenCredential(key);
10641063
if (
10651064
credential?.requestedClaimsHash &&
10661065
key.includes(credential.requestedClaimsHash.toLowerCase())
10671066
) {
1068-
removedAccessTokens.push(this.removeAccessToken(key));
1067+
this.removeAccessToken(key, correlationId);
1068+
removedAccessTokens++;
10691069
}
10701070
});
1071-
await Promise.all(removedAccessTokens);
10721071

10731072
// warn if any access tokens are removed
1074-
if (removedAccessTokens.length > 0) {
1073+
if (removedAccessTokens > 0) {
10751074
this.logger.warning(
1076-
`${removedAccessTokens.length} access tokens with claims in the cache keys have been removed from the cache.`
1075+
`${removedAccessTokens} access tokens with claims in the cache keys have been removed from the cache.`
10771076
);
10781077
}
10791078
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -505,9 +505,7 @@ export class NestedAppAuthController implements IController {
505505
currentAccount,
506506
authRequest,
507507
tokenKeys,
508-
currentAccount.tenantId,
509-
this.performanceClient,
510-
authRequest.correlationId
508+
currentAccount.tenantId
511509
);
512510

513511
// If there is no access token, log it and return null

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
getRequestThumbprint,
2424
AccountEntity,
2525
invokeAsync,
26+
invoke,
2627
createClientAuthError,
2728
ClientAuthErrorCodes,
2829
AccountFilter,
@@ -380,15 +381,15 @@ export class StandardController implements IController {
380381
"Claims-based caching is disabled. Clearing the previous cache with claims"
381382
);
382383

383-
await invokeAsync(
384+
invoke(
384385
this.browserStorage.clearTokensAndKeysWithClaims.bind(
385386
this.browserStorage
386387
),
387388
PerformanceEvents.ClearTokensAndKeysWithClaims,
388389
this.logger,
389390
this.performanceClient,
390391
initCorrelationId
391-
)(this.performanceClient, initCorrelationId);
392+
)(initCorrelationId);
392393
}
393394

394395
this.config.system.asyncPopups &&

lib/msal-browser/src/crypto/CryptoOps.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55

66
import {
7+
ClientAuthErrorCodes,
8+
createClientAuthError,
79
ICrypto,
810
IPerformanceClient,
911
JoseHeader,
@@ -168,10 +170,14 @@ export class CryptoOps implements ICrypto {
168170
* Removes cryptographic keypair from key store matching the keyId passed in
169171
* @param kid
170172
*/
171-
async removeTokenBindingKey(kid: string): Promise<boolean> {
173+
async removeTokenBindingKey(kid: string): Promise<void> {
172174
await this.cache.removeItem(kid);
173175
const keyFound = await this.cache.containsKey(kid);
174-
return !keyFound;
176+
if (keyFound) {
177+
throw createClientAuthError(
178+
ClientAuthErrorCodes.bindingKeyNotRemoved
179+
);
180+
}
175181
}
176182

177183
/**

lib/msal-browser/src/crypto/SignedHttpRequest.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import { CryptoOps } from "./CryptoOps.js";
77
import {
8+
ClientAuthError,
9+
ClientAuthErrorCodes,
810
Logger,
911
LoggerOptions,
1012
PopTokenGenerator,
@@ -71,6 +73,22 @@ export class SignedHttpRequest {
7173
* @returns If keys are properly deleted
7274
*/
7375
async removeKeys(publicKeyThumbprint: string): Promise<boolean> {
74-
return this.cryptoOps.removeTokenBindingKey(publicKeyThumbprint);
76+
return this.cryptoOps
77+
.removeTokenBindingKey(publicKeyThumbprint)
78+
.then(() => true)
79+
.catch((error) => {
80+
/*
81+
* @deprecated - To maintain public API signature, we return false if the error is due to the key still being present in indexedDB.
82+
*/
83+
if (
84+
error instanceof ClientAuthError &&
85+
error.errorCode ===
86+
ClientAuthErrorCodes.bindingKeyNotRemoved
87+
) {
88+
return false;
89+
}
90+
91+
throw error;
92+
});
7593
}
7694
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ export abstract class BaseInteractionClient {
102102
}
103103
// Clear given account.
104104
try {
105-
await this.browserStorage.removeAccount(
106-
AccountEntity.generateAccountCacheKey(account)
105+
this.browserStorage.removeAccount(
106+
AccountEntity.generateAccountCacheKey(account),
107+
this.correlationId
107108
);
108109
this.logger.verbose(
109110
"Cleared cache items belonging to the account provided in the logout request."
@@ -120,7 +121,7 @@ export abstract class BaseInteractionClient {
120121
this.correlationId
121122
);
122123
// Clear all accounts and tokens
123-
await this.browserStorage.clear();
124+
this.browserStorage.clear(this.correlationId);
124125
// Clear any stray keys from IndexedDB
125126
await this.browserCrypto.clearKeystore();
126127
} catch (e) {

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -714,11 +714,10 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient {
714714
await this.browserStorage.setAccount(accountEntity, this.correlationId);
715715

716716
// Remove any existing cached tokens for this account in browser storage
717-
this.browserStorage.removeAccountContext(accountEntity).catch((e) => {
718-
this.logger.error(
719-
`Error occurred while removing account context from browser storage. ${e}`
720-
);
721-
});
717+
this.browserStorage.removeAccountContext(
718+
accountEntity,
719+
this.correlationId
720+
);
722721
}
723722

724723
/**

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -503,8 +503,9 @@ export class PopupClient extends StandardInteractionClient {
503503
validRequest.postLogoutRedirectUri &&
504504
authClient.authority.protocolMode === ProtocolMode.OIDC
505505
) {
506-
void this.browserStorage.removeAccount(
507-
validRequest.account?.homeAccountId
506+
this.browserStorage.removeAccount(
507+
validRequest.account?.homeAccountId,
508+
this.correlationId
508509
);
509510

510511
this.eventHandler.emitEvent(

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,8 +704,9 @@ export class RedirectClient extends StandardInteractionClient {
704704
authClient.authority.endSessionEndpoint;
705705
} catch {
706706
if (validLogoutRequest.account?.homeAccountId) {
707-
void this.browserStorage.removeAccount(
708-
validLogoutRequest.account?.homeAccountId
707+
this.browserStorage.removeAccount(
708+
validLogoutRequest.account?.homeAccountId,
709+
this.correlationId
709710
);
710711

711712
this.eventHandler.emitEvent(

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7779,7 +7779,10 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
77797779
// Ensure account is present in the cache before removing it
77807780
const cacheKey =
77817781
AccountEntity.generateAccountCacheKey(accountInfo);
7782-
secondBrowserStorageInstance.removeAccount(cacheKey);
7782+
secondBrowserStorageInstance.removeAccount(
7783+
cacheKey,
7784+
RANDOM_TEST_GUID
7785+
);
77837786
});
77847787
});
77857788

0 commit comments

Comments
 (0)