/*! @azure/msal-browser v3.21.0 2024-08-13 */ 'use strict'; import { CacheManager, CredentialType, CacheHelpers, AccountEntity, createClientAuthError, ClientAuthErrorCodes, Constants, PersistentCacheKeys, PerformanceEvents, StringUtils, ProtocolUtils, AuthToken, CacheError, DEFAULT_CRYPTO_IMPLEMENTATION, CcsCredentialType } from '@azure/msal-common'; import { createBrowserAuthError } from '../error/BrowserAuthError.mjs'; import { BrowserCacheLocation, StaticCacheKeys, InMemoryCacheKeys, TemporaryCacheKeys } from '../utils/BrowserConstants.mjs'; import { BrowserStorage } from './BrowserStorage.mjs'; import { MemoryStorage } from './MemoryStorage.mjs'; import { extractBrowserRequestState } from '../utils/BrowserProtocolUtils.mjs'; import { base64Decode } from '../encode/Base64Decode.mjs'; import { base64Encode } from '../encode/Base64Encode.mjs'; import { noTokenRequestCacheError, unableToParseTokenRequestCacheError, noCachedAuthorityError, interactionInProgress } from '../error/BrowserAuthErrorCodes.mjs'; /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ /** * This class implements the cache storage interface for MSAL through browser local or session storage. * Cookies are only used if storeAuthStateInCookie is true, and are only used for * parameters such as state and nonce, generally. */ class BrowserCacheManager extends CacheManager { constructor(clientId, cacheConfig, cryptoImpl, logger, staticAuthorityOptions, performanceClient) { super(clientId, cryptoImpl, logger, staticAuthorityOptions); // Cookie life calculation (hours * minutes * seconds * ms) this.COOKIE_LIFE_MULTIPLIER = 24 * 60 * 60 * 1000; this.cacheConfig = cacheConfig; this.logger = logger; this.internalStorage = new MemoryStorage(); this.browserStorage = this.setupBrowserStorage(this.cacheConfig.cacheLocation); this.temporaryCacheStorage = this.setupTemporaryCacheStorage(this.cacheConfig.temporaryCacheLocation, this.cacheConfig.cacheLocation); // Migrate cache entries from older versions of MSAL. if (cacheConfig.cacheMigrationEnabled) { this.migrateCacheEntries(); this.createKeyMaps(); } this.performanceClient = performanceClient; } /** * Returns a window storage class implementing the IWindowStorage interface that corresponds to the configured cacheLocation. * @param cacheLocation */ setupBrowserStorage(cacheLocation) { switch (cacheLocation) { case BrowserCacheLocation.LocalStorage: case BrowserCacheLocation.SessionStorage: try { return new BrowserStorage(cacheLocation); } catch (e) { this.logger.verbose(e); break; } } this.cacheConfig.cacheLocation = BrowserCacheLocation.MemoryStorage; return new MemoryStorage(); } /** * Returns a window storage class implementing the IWindowStorage interface that corresponds to the configured temporaryCacheLocation. * @param temporaryCacheLocation * @param cacheLocation */ setupTemporaryCacheStorage(temporaryCacheLocation, cacheLocation) { switch (cacheLocation) { case BrowserCacheLocation.LocalStorage: case BrowserCacheLocation.SessionStorage: try { // Temporary cache items will always be stored in session storage to mitigate problems caused by multiple tabs return new BrowserStorage(temporaryCacheLocation || BrowserCacheLocation.SessionStorage); } catch (e) { this.logger.verbose(e); return this.internalStorage; } case BrowserCacheLocation.MemoryStorage: default: return this.internalStorage; } } /** * Migrate all old cache entries to new schema. No rollback supported. * @param storeAuthStateInCookie */ migrateCacheEntries() { const idTokenKey = `${Constants.CACHE_PREFIX}.${PersistentCacheKeys.ID_TOKEN}`; const clientInfoKey = `${Constants.CACHE_PREFIX}.${PersistentCacheKeys.CLIENT_INFO}`; const errorKey = `${Constants.CACHE_PREFIX}.${PersistentCacheKeys.ERROR}`; const errorDescKey = `${Constants.CACHE_PREFIX}.${PersistentCacheKeys.ERROR_DESC}`; const idTokenValue = this.browserStorage.getItem(idTokenKey); const clientInfoValue = this.browserStorage.getItem(clientInfoKey); const errorValue = this.browserStorage.getItem(errorKey); const errorDescValue = this.browserStorage.getItem(errorDescKey); const values = [ idTokenValue, clientInfoValue, errorValue, errorDescValue, ]; const keysToMigrate = [ PersistentCacheKeys.ID_TOKEN, PersistentCacheKeys.CLIENT_INFO, PersistentCacheKeys.ERROR, PersistentCacheKeys.ERROR_DESC, ]; keysToMigrate.forEach((cacheKey, index) => { const value = values[index]; if (value) { this.setTemporaryCache(cacheKey, value, true); } }); } /** * Searches all cache entries for MSAL accounts and creates the account key map * This is used to migrate users from older versions of MSAL which did not create the map. * @returns */ createKeyMaps() { this.logger.trace("BrowserCacheManager - createKeyMaps called."); const accountKeys = this.getItem(StaticCacheKeys.ACCOUNT_KEYS); const tokenKeys = this.getItem(`${StaticCacheKeys.TOKEN_KEYS}.${this.clientId}`); if (accountKeys && tokenKeys) { this.logger.verbose("BrowserCacheManager:createKeyMaps - account and token key maps already exist, skipping migration."); // Key maps already exist, no need to iterate through cache return; } const allKeys = this.browserStorage.getKeys(); allKeys.forEach((key) => { if (this.isCredentialKey(key)) { // Get item, parse, validate and write key to map const value = this.getItem(key); if (value) { const credObj = this.validateAndParseJson(value); if (credObj && credObj.hasOwnProperty("credentialType")) { switch (credObj["credentialType"]) { case CredentialType.ID_TOKEN: if (CacheHelpers.isIdTokenEntity(credObj)) { this.logger.trace("BrowserCacheManager:createKeyMaps - idToken found, saving key to token key map"); this.logger.tracePii(`BrowserCacheManager:createKeyMaps - idToken with key: ${key} found, saving key to token key map`); const idTokenEntity = credObj; const newKey = this.updateCredentialCacheKey(key, idTokenEntity); this.addTokenKey(newKey, CredentialType.ID_TOKEN); return; } else { this.logger.trace("BrowserCacheManager:createKeyMaps - key found matching idToken schema with value containing idToken credentialType field but value failed IdTokenEntity validation, skipping."); this.logger.tracePii(`BrowserCacheManager:createKeyMaps - failed idToken validation on key: ${key}`); } break; case CredentialType.ACCESS_TOKEN: case CredentialType.ACCESS_TOKEN_WITH_AUTH_SCHEME: if (CacheHelpers.isAccessTokenEntity(credObj)) { this.logger.trace("BrowserCacheManager:createKeyMaps - accessToken found, saving key to token key map"); this.logger.tracePii(`BrowserCacheManager:createKeyMaps - accessToken with key: ${key} found, saving key to token key map`); const accessTokenEntity = credObj; const newKey = this.updateCredentialCacheKey(key, accessTokenEntity); this.addTokenKey(newKey, CredentialType.ACCESS_TOKEN); return; } else { this.logger.trace("BrowserCacheManager:createKeyMaps - key found matching accessToken schema with value containing accessToken credentialType field but value failed AccessTokenEntity validation, skipping."); this.logger.tracePii(`BrowserCacheManager:createKeyMaps - failed accessToken validation on key: ${key}`); } break; case CredentialType.REFRESH_TOKEN: if (CacheHelpers.isRefreshTokenEntity(credObj)) { this.logger.trace("BrowserCacheManager:createKeyMaps - refreshToken found, saving key to token key map"); this.logger.tracePii(`BrowserCacheManager:createKeyMaps - refreshToken with key: ${key} found, saving key to token key map`); const refreshTokenEntity = credObj; const newKey = this.updateCredentialCacheKey(key, refreshTokenEntity); this.addTokenKey(newKey, CredentialType.REFRESH_TOKEN); return; } else { this.logger.trace("BrowserCacheManager:createKeyMaps - key found matching refreshToken schema with value containing refreshToken credentialType field but value failed RefreshTokenEntity validation, skipping."); this.logger.tracePii(`BrowserCacheManager:createKeyMaps - failed refreshToken validation on key: ${key}`); } break; // If credentialType isn't one of our predefined ones, it may not be an MSAL cache value. Ignore. } } } } if (this.isAccountKey(key)) { const value = this.getItem(key); if (value) { const accountObj = this.validateAndParseJson(value); if (accountObj && AccountEntity.isAccountEntity(accountObj)) { this.logger.trace("BrowserCacheManager:createKeyMaps - account found, saving key to account key map"); this.logger.tracePii(`BrowserCacheManager:createKeyMaps - account with key: ${key} found, saving key to account key map`); this.addAccountKeyToMap(key); } } } }); } /** * Parses passed value as JSON object, JSON.parse() will throw an error. * @param input */ validateAndParseJson(jsonValue) { try { const parsedJson = JSON.parse(jsonValue); /** * There are edge cases in which JSON.parse will successfully parse a non-valid JSON object * (e.g. JSON.parse will parse an escaped string into an unescaped string), so adding a type check * of the parsed value is necessary in order to be certain that the string represents a valid JSON object. * */ return parsedJson && typeof parsedJson === "object" ? parsedJson : null; } catch (error) { return null; } } /** * fetches the entry from the browser storage based off the key * @param key */ getItem(key) { return this.browserStorage.getItem(key); } /** * sets the entry in the browser storage * @param key * @param value */ setItem(key, value) { this.browserStorage.setItem(key, value); } /** * fetch the account entity from the platform cache * @param accountKey */ getAccount(accountKey, logger) { this.logger.trace("BrowserCacheManager.getAccount called"); const accountEntity = this.getCachedAccountEntity(accountKey); return this.updateOutdatedCachedAccount(accountKey, accountEntity, logger); } /** * Reads account from cache, deserializes it into an account entity and returns it. * If account is not found from the key, returns null and removes key from map. * @param accountKey * @returns */ getCachedAccountEntity(accountKey) { const serializedAccount = this.getItem(accountKey); if (!serializedAccount) { this.removeAccountKeyFromMap(accountKey); return null; } const parsedAccount = this.validateAndParseJson(serializedAccount); if (!parsedAccount || !AccountEntity.isAccountEntity(parsedAccount)) { this.removeAccountKeyFromMap(accountKey); return null; } return CacheManager.toObject(new AccountEntity(), parsedAccount); } /** * set account entity in the platform cache * @param account */ setAccount(account) { this.logger.trace("BrowserCacheManager.setAccount called"); const key = account.generateAccountKey(); this.setItem(key, JSON.stringify(account)); this.addAccountKeyToMap(key); } /** * Returns the array of account keys currently cached * @returns */ getAccountKeys() { this.logger.trace("BrowserCacheManager.getAccountKeys called"); const accountKeys = this.getItem(StaticCacheKeys.ACCOUNT_KEYS); if (accountKeys) { return JSON.parse(accountKeys); } this.logger.verbose("BrowserCacheManager.getAccountKeys - No account keys found"); return []; } /** * Add a new account to the key map * @param key */ addAccountKeyToMap(key) { this.logger.trace("BrowserCacheManager.addAccountKeyToMap called"); this.logger.tracePii(`BrowserCacheManager.addAccountKeyToMap called with key: ${key}`); const accountKeys = this.getAccountKeys(); if (accountKeys.indexOf(key) === -1) { // Only add key if it does not already exist in the map accountKeys.push(key); this.setItem(StaticCacheKeys.ACCOUNT_KEYS, JSON.stringify(accountKeys)); this.logger.verbose("BrowserCacheManager.addAccountKeyToMap account key added"); } else { this.logger.verbose("BrowserCacheManager.addAccountKeyToMap account key already exists in map"); } } /** * Remove an account from the key map * @param key */ removeAccountKeyFromMap(key) { this.logger.trace("BrowserCacheManager.removeAccountKeyFromMap called"); this.logger.tracePii(`BrowserCacheManager.removeAccountKeyFromMap called with key: ${key}`); const accountKeys = this.getAccountKeys(); const removalIndex = accountKeys.indexOf(key); if (removalIndex > -1) { accountKeys.splice(removalIndex, 1); this.setItem(StaticCacheKeys.ACCOUNT_KEYS, JSON.stringify(accountKeys)); this.logger.trace("BrowserCacheManager.removeAccountKeyFromMap account key removed"); } else { this.logger.trace("BrowserCacheManager.removeAccountKeyFromMap key not found in existing map"); } } /** * Extends inherited removeAccount function to include removal of the account key from the map * @param key */ async removeAccount(key) { void super.removeAccount(key); this.removeAccountKeyFromMap(key); } /** * Remove account entity from the platform cache if it's outdated * @param accountKey */ removeOutdatedAccount(accountKey) { this.removeItem(accountKey); this.removeAccountKeyFromMap(accountKey); } /** * Removes given idToken from the cache and from the key map * @param key */ removeIdToken(key) { super.removeIdToken(key); this.removeTokenKey(key, CredentialType.ID_TOKEN); } /** * Removes given accessToken from the cache and from the key map * @param key */ async removeAccessToken(key) { void super.removeAccessToken(key); this.removeTokenKey(key, CredentialType.ACCESS_TOKEN); } /** * Removes given refreshToken from the cache and from the key map * @param key */ removeRefreshToken(key) { super.removeRefreshToken(key); this.removeTokenKey(key, CredentialType.REFRESH_TOKEN); } /** * Gets the keys for the cached tokens associated with this clientId * @returns */ getTokenKeys() { this.logger.trace("BrowserCacheManager.getTokenKeys called"); const item = this.getItem(`${StaticCacheKeys.TOKEN_KEYS}.${this.clientId}`); if (item) { const tokenKeys = this.validateAndParseJson(item); if (tokenKeys && tokenKeys.hasOwnProperty("idToken") && tokenKeys.hasOwnProperty("accessToken") && tokenKeys.hasOwnProperty("refreshToken")) { return tokenKeys; } else { this.logger.error("BrowserCacheManager.getTokenKeys - Token keys found but in an unknown format. Returning empty key map."); } } else { this.logger.verbose("BrowserCacheManager.getTokenKeys - No token keys found"); } return { idToken: [], accessToken: [], refreshToken: [], }; } /** * Adds the given key to the token key map * @param key * @param type */ addTokenKey(key, type) { this.logger.trace("BrowserCacheManager addTokenKey called"); const tokenKeys = this.getTokenKeys(); switch (type) { case CredentialType.ID_TOKEN: if (tokenKeys.idToken.indexOf(key) === -1) { this.logger.info("BrowserCacheManager: addTokenKey - idToken added to map"); tokenKeys.idToken.push(key); } break; case CredentialType.ACCESS_TOKEN: if (tokenKeys.accessToken.indexOf(key) === -1) { this.logger.info("BrowserCacheManager: addTokenKey - accessToken added to map"); tokenKeys.accessToken.push(key); } break; case CredentialType.REFRESH_TOKEN: if (tokenKeys.refreshToken.indexOf(key) === -1) { this.logger.info("BrowserCacheManager: addTokenKey - refreshToken added to map"); tokenKeys.refreshToken.push(key); } break; default: this.logger.error(`BrowserCacheManager:addTokenKey - CredentialType provided invalid. CredentialType: ${type}`); throw createClientAuthError(ClientAuthErrorCodes.unexpectedCredentialType); } this.setItem(`${StaticCacheKeys.TOKEN_KEYS}.${this.clientId}`, JSON.stringify(tokenKeys)); } /** * Removes the given key from the token key map * @param key * @param type */ removeTokenKey(key, type) { this.logger.trace("BrowserCacheManager removeTokenKey called"); const tokenKeys = this.getTokenKeys(); switch (type) { case CredentialType.ID_TOKEN: this.logger.infoPii(`BrowserCacheManager: removeTokenKey - attempting to remove idToken with key: ${key} from map`); const idRemoval = tokenKeys.idToken.indexOf(key); if (idRemoval > -1) { this.logger.info("BrowserCacheManager: removeTokenKey - idToken removed from map"); tokenKeys.idToken.splice(idRemoval, 1); } else { this.logger.info("BrowserCacheManager: removeTokenKey - idToken does not exist in map. Either it was previously removed or it was never added."); } break; case CredentialType.ACCESS_TOKEN: this.logger.infoPii(`BrowserCacheManager: removeTokenKey - attempting to remove accessToken with key: ${key} from map`); const accessRemoval = tokenKeys.accessToken.indexOf(key); if (accessRemoval > -1) { this.logger.info("BrowserCacheManager: removeTokenKey - accessToken removed from map"); tokenKeys.accessToken.splice(accessRemoval, 1); } else { this.logger.info("BrowserCacheManager: removeTokenKey - accessToken does not exist in map. Either it was previously removed or it was never added."); } break; case CredentialType.REFRESH_TOKEN: this.logger.infoPii(`BrowserCacheManager: removeTokenKey - attempting to remove refreshToken with key: ${key} from map`); const refreshRemoval = tokenKeys.refreshToken.indexOf(key); if (refreshRemoval > -1) { this.logger.info("BrowserCacheManager: removeTokenKey - refreshToken removed from map"); tokenKeys.refreshToken.splice(refreshRemoval, 1); } else { this.logger.info("BrowserCacheManager: removeTokenKey - refreshToken does not exist in map. Either it was previously removed or it was never added."); } break; default: this.logger.error(`BrowserCacheManager:removeTokenKey - CredentialType provided invalid. CredentialType: ${type}`); throw createClientAuthError(ClientAuthErrorCodes.unexpectedCredentialType); } this.setItem(`${StaticCacheKeys.TOKEN_KEYS}.${this.clientId}`, JSON.stringify(tokenKeys)); } /** * generates idToken entity from a string * @param idTokenKey */ getIdTokenCredential(idTokenKey) { const value = this.getItem(idTokenKey); if (!value) { this.logger.trace("BrowserCacheManager.getIdTokenCredential: called, no cache hit"); this.removeTokenKey(idTokenKey, CredentialType.ID_TOKEN); return null; } const parsedIdToken = this.validateAndParseJson(value); if (!parsedIdToken || !CacheHelpers.isIdTokenEntity(parsedIdToken)) { this.logger.trace("BrowserCacheManager.getIdTokenCredential: called, no cache hit"); this.removeTokenKey(idTokenKey, CredentialType.ID_TOKEN); return null; } this.logger.trace("BrowserCacheManager.getIdTokenCredential: cache hit"); return parsedIdToken; } /** * set IdToken credential to the platform cache * @param idToken */ setIdTokenCredential(idToken) { this.logger.trace("BrowserCacheManager.setIdTokenCredential called"); const idTokenKey = CacheHelpers.generateCredentialKey(idToken); this.setItem(idTokenKey, JSON.stringify(idToken)); this.addTokenKey(idTokenKey, CredentialType.ID_TOKEN); } /** * generates accessToken entity from a string * @param key */ getAccessTokenCredential(accessTokenKey) { const value = this.getItem(accessTokenKey); if (!value) { this.logger.trace("BrowserCacheManager.getAccessTokenCredential: called, no cache hit"); this.removeTokenKey(accessTokenKey, CredentialType.ACCESS_TOKEN); return null; } const parsedAccessToken = this.validateAndParseJson(value); if (!parsedAccessToken || !CacheHelpers.isAccessTokenEntity(parsedAccessToken)) { this.logger.trace("BrowserCacheManager.getAccessTokenCredential: called, no cache hit"); this.removeTokenKey(accessTokenKey, CredentialType.ACCESS_TOKEN); return null; } this.logger.trace("BrowserCacheManager.getAccessTokenCredential: cache hit"); return parsedAccessToken; } /** * set accessToken credential to the platform cache * @param accessToken */ setAccessTokenCredential(accessToken) { this.logger.trace("BrowserCacheManager.setAccessTokenCredential called"); const accessTokenKey = CacheHelpers.generateCredentialKey(accessToken); this.setItem(accessTokenKey, JSON.stringify(accessToken)); this.addTokenKey(accessTokenKey, CredentialType.ACCESS_TOKEN); } /** * generates refreshToken entity from a string * @param refreshTokenKey */ getRefreshTokenCredential(refreshTokenKey) { const value = this.getItem(refreshTokenKey); if (!value) { this.logger.trace("BrowserCacheManager.getRefreshTokenCredential: called, no cache hit"); this.removeTokenKey(refreshTokenKey, CredentialType.REFRESH_TOKEN); return null; } const parsedRefreshToken = this.validateAndParseJson(value); if (!parsedRefreshToken || !CacheHelpers.isRefreshTokenEntity(parsedRefreshToken)) { this.logger.trace("BrowserCacheManager.getRefreshTokenCredential: called, no cache hit"); this.removeTokenKey(refreshTokenKey, CredentialType.REFRESH_TOKEN); return null; } this.logger.trace("BrowserCacheManager.getRefreshTokenCredential: cache hit"); return parsedRefreshToken; } /** * set refreshToken credential to the platform cache * @param refreshToken */ setRefreshTokenCredential(refreshToken) { this.logger.trace("BrowserCacheManager.setRefreshTokenCredential called"); const refreshTokenKey = CacheHelpers.generateCredentialKey(refreshToken); this.setItem(refreshTokenKey, JSON.stringify(refreshToken)); this.addTokenKey(refreshTokenKey, CredentialType.REFRESH_TOKEN); } /** * fetch appMetadata entity from the platform cache * @param appMetadataKey */ getAppMetadata(appMetadataKey) { const value = this.getItem(appMetadataKey); if (!value) { this.logger.trace("BrowserCacheManager.getAppMetadata: called, no cache hit"); return null; } const parsedMetadata = this.validateAndParseJson(value); if (!parsedMetadata || !CacheHelpers.isAppMetadataEntity(appMetadataKey, parsedMetadata)) { this.logger.trace("BrowserCacheManager.getAppMetadata: called, no cache hit"); return null; } this.logger.trace("BrowserCacheManager.getAppMetadata: cache hit"); return parsedMetadata; } /** * set appMetadata entity to the platform cache * @param appMetadata */ setAppMetadata(appMetadata) { this.logger.trace("BrowserCacheManager.setAppMetadata called"); const appMetadataKey = CacheHelpers.generateAppMetadataKey(appMetadata); this.setItem(appMetadataKey, JSON.stringify(appMetadata)); } /** * fetch server telemetry entity from the platform cache * @param serverTelemetryKey */ getServerTelemetry(serverTelemetryKey) { const value = this.getItem(serverTelemetryKey); if (!value) { this.logger.trace("BrowserCacheManager.getServerTelemetry: called, no cache hit"); return null; } const parsedEntity = this.validateAndParseJson(value); if (!parsedEntity || !CacheHelpers.isServerTelemetryEntity(serverTelemetryKey, parsedEntity)) { this.logger.trace("BrowserCacheManager.getServerTelemetry: called, no cache hit"); return null; } this.logger.trace("BrowserCacheManager.getServerTelemetry: cache hit"); return parsedEntity; } /** * set server telemetry entity to the platform cache * @param serverTelemetryKey * @param serverTelemetry */ setServerTelemetry(serverTelemetryKey, serverTelemetry) { this.logger.trace("BrowserCacheManager.setServerTelemetry called"); this.setItem(serverTelemetryKey, JSON.stringify(serverTelemetry)); } /** * */ getAuthorityMetadata(key) { const value = this.internalStorage.getItem(key); if (!value) { this.logger.trace("BrowserCacheManager.getAuthorityMetadata: called, no cache hit"); return null; } const parsedMetadata = this.validateAndParseJson(value); if (parsedMetadata && CacheHelpers.isAuthorityMetadataEntity(key, parsedMetadata)) { this.logger.trace("BrowserCacheManager.getAuthorityMetadata: cache hit"); return parsedMetadata; } return null; } /** * */ getAuthorityMetadataKeys() { const allKeys = this.internalStorage.getKeys(); return allKeys.filter((key) => { return this.isAuthorityMetadata(key); }); } /** * Sets wrapper metadata in memory * @param wrapperSKU * @param wrapperVersion */ setWrapperMetadata(wrapperSKU, wrapperVersion) { this.internalStorage.setItem(InMemoryCacheKeys.WRAPPER_SKU, wrapperSKU); this.internalStorage.setItem(InMemoryCacheKeys.WRAPPER_VER, wrapperVersion); } /** * Returns wrapper metadata from in-memory storage */ getWrapperMetadata() { const sku = this.internalStorage.getItem(InMemoryCacheKeys.WRAPPER_SKU) || Constants.EMPTY_STRING; const version = this.internalStorage.getItem(InMemoryCacheKeys.WRAPPER_VER) || Constants.EMPTY_STRING; return [sku, version]; } /** * * @param entity */ setAuthorityMetadata(key, entity) { this.logger.trace("BrowserCacheManager.setAuthorityMetadata called"); this.internalStorage.setItem(key, JSON.stringify(entity)); } /** * Gets the active account */ getActiveAccount() { const activeAccountKeyFilters = this.generateCacheKey(PersistentCacheKeys.ACTIVE_ACCOUNT_FILTERS); const activeAccountValueFilters = this.getItem(activeAccountKeyFilters); if (!activeAccountValueFilters) { // if new active account cache type isn't found, it's an old version, so look for that instead this.logger.trace("BrowserCacheManager.getActiveAccount: No active account filters cache schema found, looking for legacy schema"); const activeAccountKeyLocal = this.generateCacheKey(PersistentCacheKeys.ACTIVE_ACCOUNT); const activeAccountValueLocal = this.getItem(activeAccountKeyLocal); if (!activeAccountValueLocal) { this.logger.trace("BrowserCacheManager.getActiveAccount: No active account found"); return null; } const activeAccount = this.getAccountInfoFilteredBy({ localAccountId: activeAccountValueLocal, }); if (activeAccount) { this.logger.trace("BrowserCacheManager.getActiveAccount: Legacy active account cache schema found"); this.logger.trace("BrowserCacheManager.getActiveAccount: Adding active account filters cache schema"); this.setActiveAccount(activeAccount); return activeAccount; } return null; } const activeAccountValueObj = this.validateAndParseJson(activeAccountValueFilters); if (activeAccountValueObj) { this.logger.trace("BrowserCacheManager.getActiveAccount: Active account filters schema found"); return this.getAccountInfoFilteredBy({ homeAccountId: activeAccountValueObj.homeAccountId, localAccountId: activeAccountValueObj.localAccountId, tenantId: activeAccountValueObj.tenantId, }); } this.logger.trace("BrowserCacheManager.getActiveAccount: No active account found"); return null; } /** * Sets the active account's localAccountId in cache * @param account */ setActiveAccount(account) { const activeAccountKey = this.generateCacheKey(PersistentCacheKeys.ACTIVE_ACCOUNT_FILTERS); const activeAccountKeyLocal = this.generateCacheKey(PersistentCacheKeys.ACTIVE_ACCOUNT); if (account) { this.logger.verbose("setActiveAccount: Active account set"); const activeAccountValue = { homeAccountId: account.homeAccountId, localAccountId: account.localAccountId, tenantId: account.tenantId, }; this.browserStorage.setItem(activeAccountKey, JSON.stringify(activeAccountValue)); this.browserStorage.setItem(activeAccountKeyLocal, account.localAccountId); } else { this.logger.verbose("setActiveAccount: No account passed, active account not set"); this.browserStorage.removeItem(activeAccountKey); this.browserStorage.removeItem(activeAccountKeyLocal); } } /** * fetch throttling entity from the platform cache * @param throttlingCacheKey */ getThrottlingCache(throttlingCacheKey) { const value = this.getItem(throttlingCacheKey); if (!value) { this.logger.trace("BrowserCacheManager.getThrottlingCache: called, no cache hit"); return null; } const parsedThrottlingCache = this.validateAndParseJson(value); if (!parsedThrottlingCache || !CacheHelpers.isThrottlingEntity(throttlingCacheKey, parsedThrottlingCache)) { this.logger.trace("BrowserCacheManager.getThrottlingCache: called, no cache hit"); return null; } this.logger.trace("BrowserCacheManager.getThrottlingCache: cache hit"); return parsedThrottlingCache; } /** * set throttling entity to the platform cache * @param throttlingCacheKey * @param throttlingCache */ setThrottlingCache(throttlingCacheKey, throttlingCache) { this.logger.trace("BrowserCacheManager.setThrottlingCache called"); this.setItem(throttlingCacheKey, JSON.stringify(throttlingCache)); } /** * Gets cache item with given key. * Will retrieve from cookies if storeAuthStateInCookie is set to true. * @param key */ getTemporaryCache(cacheKey, generateKey) { const key = generateKey ? this.generateCacheKey(cacheKey) : cacheKey; if (this.cacheConfig.storeAuthStateInCookie) { const itemCookie = this.getItemCookie(key); if (itemCookie) { this.logger.trace("BrowserCacheManager.getTemporaryCache: storeAuthStateInCookies set to true, retrieving from cookies"); return itemCookie; } } const value = this.temporaryCacheStorage.getItem(key); if (!value) { // If temp cache item not found in session/memory, check local storage for items set by old versions if (this.cacheConfig.cacheLocation === BrowserCacheLocation.LocalStorage) { const item = this.browserStorage.getItem(key); if (item) { this.logger.trace("BrowserCacheManager.getTemporaryCache: Temporary cache item found in local storage"); return item; } } this.logger.trace("BrowserCacheManager.getTemporaryCache: No cache item found in local storage"); return null; } this.logger.trace("BrowserCacheManager.getTemporaryCache: Temporary cache item returned"); return value; } /** * Sets the cache item with the key and value given. * Stores in cookie if storeAuthStateInCookie is set to true. * This can cause cookie overflow if used incorrectly. * @param key * @param value */ setTemporaryCache(cacheKey, value, generateKey) { const key = generateKey ? this.generateCacheKey(cacheKey) : cacheKey; this.temporaryCacheStorage.setItem(key, value); if (this.cacheConfig.storeAuthStateInCookie) { this.logger.trace("BrowserCacheManager.setTemporaryCache: storeAuthStateInCookie set to true, setting item cookie"); this.setItemCookie(key, value); } } /** * Removes the cache item with the given key. * @param key */ removeItem(key) { this.browserStorage.removeItem(key); } /** * Removes the temporary cache item with the given key. * Will also clear the cookie item if storeAuthStateInCookie is set to true. * @param key */ removeTemporaryItem(key) { this.temporaryCacheStorage.removeItem(key); if (this.cacheConfig.storeAuthStateInCookie) { this.logger.trace("BrowserCacheManager.removeItem: storeAuthStateInCookie is true, clearing item cookie"); this.clearItemCookie(key); } } /** * Gets all keys in window. */ getKeys() { return this.browserStorage.getKeys(); } /** * Clears all cache entries created by MSAL. */ async clear() { // Removes all accounts and their credentials await this.removeAllAccounts(); this.removeAppMetadata(); // Remove temp storage first to make sure any cookies are cleared this.temporaryCacheStorage.getKeys().forEach((cacheKey) => { if (cacheKey.indexOf(Constants.CACHE_PREFIX) !== -1 || cacheKey.indexOf(this.clientId) !== -1) { this.removeTemporaryItem(cacheKey); } }); // Removes all remaining MSAL cache items this.browserStorage.getKeys().forEach((cacheKey) => { if (cacheKey.indexOf(Constants.CACHE_PREFIX) !== -1 || cacheKey.indexOf(this.clientId) !== -1) { this.browserStorage.removeItem(cacheKey); } }); this.internalStorage.clear(); } /** * Clears all access tokes that have claims prior to saving the current one * @param performanceClient {IPerformanceClient} * @param correlationId {string} correlation id * @returns */ async clearTokensAndKeysWithClaims(performanceClient, correlationId) { performanceClient.addQueueMeasurement(PerformanceEvents.ClearTokensAndKeysWithClaims, correlationId); const tokenKeys = this.getTokenKeys(); const removedAccessTokens = []; tokenKeys.accessToken.forEach((key) => { // if the access token has claims in its key, remove the token key and the token const credential = this.getAccessTokenCredential(key); if (credential?.requestedClaimsHash && key.includes(credential.requestedClaimsHash.toLowerCase())) { removedAccessTokens.push(this.removeAccessToken(key)); } }); await Promise.all(removedAccessTokens); // warn if any access tokens are removed if (removedAccessTokens.length > 0) { this.logger.warning(`${removedAccessTokens.length} access tokens with claims in the cache keys have been removed from the cache.`); } } /** * Add value to cookies * @param cookieName * @param cookieValue * @param expires * @deprecated */ setItemCookie(cookieName, cookieValue, expires) { let cookieStr = `${encodeURIComponent(cookieName)}=${encodeURIComponent(cookieValue)};path=/;SameSite=Lax;`; if (expires) { const expireTime = this.getCookieExpirationTime(expires); cookieStr += `expires=${expireTime};`; } if (this.cacheConfig.secureCookies) { cookieStr += "Secure;"; } document.cookie = cookieStr; } /** * Get one item by key from cookies * @param cookieName * @deprecated */ getItemCookie(cookieName) { const name = `${encodeURIComponent(cookieName)}=`; const cookieList = document.cookie.split(";"); for (let i = 0; i < cookieList.length; i++) { let cookie = cookieList[i]; while (cookie.charAt(0) === " ") { cookie = cookie.substring(1); } if (cookie.indexOf(name) === 0) { return decodeURIComponent(cookie.substring(name.length, cookie.length)); } } return Constants.EMPTY_STRING; } /** * Clear all msal-related cookies currently set in the browser. Should only be used to clear temporary cache items. * @deprecated */ clearMsalCookies() { const cookiePrefix = `${Constants.CACHE_PREFIX}.${this.clientId}`; const cookieList = document.cookie.split(";"); cookieList.forEach((cookie) => { while (cookie.charAt(0) === " ") { // eslint-disable-next-line no-param-reassign cookie = cookie.substring(1); } if (cookie.indexOf(cookiePrefix) === 0) { const cookieKey = cookie.split("=")[0]; this.clearItemCookie(cookieKey); } }); } /** * Clear an item in the cookies by key * @param cookieName * @deprecated */ clearItemCookie(cookieName) { this.setItemCookie(cookieName, Constants.EMPTY_STRING, -1); } /** * Get cookie expiration time * @param cookieLifeDays * @deprecated */ getCookieExpirationTime(cookieLifeDays) { const today = new Date(); const expr = new Date(today.getTime() + cookieLifeDays * this.COOKIE_LIFE_MULTIPLIER); return expr.toUTCString(); } /** * Prepend msal. to each key; Skip for any JSON object as Key (defined schemas do not need the key appended: AccessToken Keys or the upcoming schema) * @param key * @param addInstanceId */ generateCacheKey(key) { const generatedKey = this.validateAndParseJson(key); if (!generatedKey) { if (StringUtils.startsWith(key, Constants.CACHE_PREFIX) || StringUtils.startsWith(key, PersistentCacheKeys.ADAL_ID_TOKEN)) { return key; } return `${Constants.CACHE_PREFIX}.${this.clientId}.${key}`; } return JSON.stringify(key); } /** * Create authorityKey to cache authority * @param state */ generateAuthorityKey(stateString) { const { libraryState: { id: stateId }, } = ProtocolUtils.parseRequestState(this.cryptoImpl, stateString); return this.generateCacheKey(`${TemporaryCacheKeys.AUTHORITY}.${stateId}`); } /** * Create Nonce key to cache nonce * @param state */ generateNonceKey(stateString) { const { libraryState: { id: stateId }, } = ProtocolUtils.parseRequestState(this.cryptoImpl, stateString); return this.generateCacheKey(`${TemporaryCacheKeys.NONCE_IDTOKEN}.${stateId}`); } /** * Creates full cache key for the request state * @param stateString State string for the request */ generateStateKey(stateString) { // Use the library state id to key temp storage for uniqueness for multiple concurrent requests const { libraryState: { id: stateId }, } = ProtocolUtils.parseRequestState(this.cryptoImpl, stateString); return this.generateCacheKey(`${TemporaryCacheKeys.REQUEST_STATE}.${stateId}`); } /** * Gets the cached authority based on the cached state. Returns empty if no cached state found. */ getCachedAuthority(cachedState) { const stateCacheKey = this.generateStateKey(cachedState); const state = this.getTemporaryCache(stateCacheKey); if (!state) { return null; } const authorityCacheKey = this.generateAuthorityKey(state); return this.getTemporaryCache(authorityCacheKey); } /** * Updates account, authority, and state in cache * @param serverAuthenticationRequest * @param account */ updateCacheEntries(state, nonce, authorityInstance, loginHint, account) { this.logger.trace("BrowserCacheManager.updateCacheEntries called"); // Cache the request state const stateCacheKey = this.generateStateKey(state); this.setTemporaryCache(stateCacheKey, state, false); // Cache the nonce const nonceCacheKey = this.generateNonceKey(state); this.setTemporaryCache(nonceCacheKey, nonce, false); // Cache authorityKey const authorityCacheKey = this.generateAuthorityKey(state); this.setTemporaryCache(authorityCacheKey, authorityInstance, false); if (account) { const ccsCredential = { credential: account.homeAccountId, type: CcsCredentialType.HOME_ACCOUNT_ID, }; this.setTemporaryCache(TemporaryCacheKeys.CCS_CREDENTIAL, JSON.stringify(ccsCredential), true); } else if (loginHint) { const ccsCredential = { credential: loginHint, type: CcsCredentialType.UPN, }; this.setTemporaryCache(TemporaryCacheKeys.CCS_CREDENTIAL, JSON.stringify(ccsCredential), true); } } /** * Reset all temporary cache items * @param state */ resetRequestCache(state) { this.logger.trace("BrowserCacheManager.resetRequestCache called"); // check state and remove associated cache items if (state) { this.temporaryCacheStorage.getKeys().forEach((key) => { if (key.indexOf(state) !== -1) { this.removeTemporaryItem(key); } }); // delete generic interactive request parameters this.removeTemporaryItem(this.generateStateKey(state)); this.removeTemporaryItem(this.generateNonceKey(state)); this.removeTemporaryItem(this.generateAuthorityKey(state)); } this.removeTemporaryItem(this.generateCacheKey(TemporaryCacheKeys.REQUEST_PARAMS)); this.removeTemporaryItem(this.generateCacheKey(TemporaryCacheKeys.ORIGIN_URI)); this.removeTemporaryItem(this.generateCacheKey(TemporaryCacheKeys.URL_HASH)); this.removeTemporaryItem(this.generateCacheKey(TemporaryCacheKeys.CORRELATION_ID)); this.removeTemporaryItem(this.generateCacheKey(TemporaryCacheKeys.CCS_CREDENTIAL)); this.removeTemporaryItem(this.generateCacheKey(TemporaryCacheKeys.NATIVE_REQUEST)); this.removeTemporaryItem(this.generateCacheKey(TemporaryCacheKeys.REDIRECT_REQUEST)); this.setInteractionInProgress(false); } /** * Removes temporary cache for the provided state * @param stateString */ cleanRequestByState(stateString) { this.logger.trace("BrowserCacheManager.cleanRequestByState called"); // Interaction is completed - remove interaction status. if (stateString) { const stateKey = this.generateStateKey(stateString); const cachedState = this.temporaryCacheStorage.getItem(stateKey); this.logger.infoPii(`BrowserCacheManager.cleanRequestByState: Removing temporary cache items for state: ${cachedState}`); this.resetRequestCache(cachedState || Constants.EMPTY_STRING); } this.clearMsalCookies(); } /** * Looks in temporary cache for any state values with the provided interactionType and removes all temporary cache items for that state * Used in scenarios where temp cache needs to be cleaned but state is not known, such as clicking browser back button. * @param interactionType */ cleanRequestByInteractionType(interactionType) { this.logger.trace("BrowserCacheManager.cleanRequestByInteractionType called"); // Loop through all keys to find state key this.temporaryCacheStorage.getKeys().forEach((key) => { // If this key is not the state key, move on if (key.indexOf(TemporaryCacheKeys.REQUEST_STATE) === -1) { return; } // Retrieve state value, return if not a valid value const stateValue = this.temporaryCacheStorage.getItem(key); if (!stateValue) { return; } // Extract state and ensure it matches given InteractionType, then clean request cache const parsedState = extractBrowserRequestState(this.cryptoImpl, stateValue); if (parsedState && parsedState.interactionType === interactionType) { this.logger.infoPii(`BrowserCacheManager.cleanRequestByInteractionType: Removing temporary cache items for state: ${stateValue}`); this.resetRequestCache(stateValue); } }); this.clearMsalCookies(); this.setInteractionInProgress(false); } /** * Create request retry key to cache retry status */ generateRequestRetriedKey() { return `${Constants.CACHE_PREFIX}.${TemporaryCacheKeys.REQUEST_RETRY}.${this.clientId}`; } /** * Gets the request retry value from the cache */ getRequestRetried() { const requestRetriedKey = this.generateRequestRetriedKey(); const cachedRetryNumber = this.getTemporaryCache(requestRetriedKey); if (!cachedRetryNumber) { return null; } return parseInt(cachedRetryNumber); } /** * Sets the request retry value to "retried" in the cache */ setRequestRetried() { this.logger.trace("BrowserCacheManager.setRequestRetried called"); const requestRetriedKey = this.generateRequestRetriedKey(); this.setTemporaryCache(requestRetriedKey, "1", false); } /** * Removes all request retry values in the cache */ removeRequestRetried() { const requestRetriedKey = this.generateRequestRetriedKey(); this.removeTemporaryItem(requestRetriedKey); } /** * Caches the redirectRequest in the cache * @param redirectRequest */ cacheRedirectRequest(redirectRequest) { this.logger.trace("BrowserCacheManager.cacheRedirectRequest called"); const { ...restParams } = redirectRequest; delete restParams.onRedirectNavigate; const encodedValue = JSON.stringify(restParams); this.setTemporaryCache(TemporaryCacheKeys.REDIRECT_REQUEST, encodedValue, true); } /** * Gets redirect request from the cache. Logs an error and returns undefined if nothing is found. */ getCachedRedirectRequest() { this.logger.trace("BrowserCacheManager.getCachedRedirectRequest called"); const cachedRedirectRequest = this.getTemporaryCache(TemporaryCacheKeys.REDIRECT_REQUEST, true); if (!cachedRedirectRequest) { this.logger.error(`No cached redirect request found.`); } else { this.removeTemporaryItem(this.generateCacheKey(TemporaryCacheKeys.REDIRECT_REQUEST)); let parsedRequest; try { parsedRequest = JSON.parse(cachedRedirectRequest); } catch (e) { this.logger.errorPii(`Attempted to parse: ${cachedRedirectRequest}`); this.logger.error(`Parsing cached redirect request threw with error: ${e}`); return; } if (parsedRequest) { return parsedRequest; } } return; } cacheCodeRequest(authCodeRequest) { this.logger.trace("BrowserCacheManager.cacheCodeRequest called"); const encodedValue = base64Encode(JSON.stringify(authCodeRequest)); this.setTemporaryCache(TemporaryCacheKeys.REQUEST_PARAMS, encodedValue, true); } /** * Gets the token exchange parameters from the cache. Throws an error if nothing is found. */ getCachedRequest(state) { this.logger.trace("BrowserCacheManager.getCachedRequest called"); // Get token request from cache and parse as TokenExchangeParameters. const encodedTokenRequest = this.getTemporaryCache(TemporaryCacheKeys.REQUEST_PARAMS, true); if (!encodedTokenRequest) { throw createBrowserAuthError(noTokenRequestCacheError); } let parsedRequest; try { parsedRequest = JSON.parse(base64Decode(encodedTokenRequest)); } catch (e) { this.logger.errorPii(`Attempted to parse: ${encodedTokenRequest}`); this.logger.error(`Parsing cached token request threw with error: ${e}`); throw createBrowserAuthError(unableToParseTokenRequestCacheError); } this.removeTemporaryItem(this.generateCacheKey(TemporaryCacheKeys.REQUEST_PARAMS)); // Get cached authority and use if no authority is cached with request. if (!parsedRequest.authority) { const authorityCacheKey = this.generateAuthorityKey(state); const cachedAuthority = this.getTemporaryCache(authorityCacheKey); if (!cachedAuthority) { throw createBrowserAuthError(noCachedAuthorityError); } parsedRequest.authority = cachedAuthority; } return parsedRequest; } /** * Gets cached native request for redirect flows */ getCachedNativeRequest() { this.logger.trace("BrowserCacheManager.getCachedNativeRequest called"); const cachedRequest = this.getTemporaryCache(TemporaryCacheKeys.NATIVE_REQUEST, true); if (!cachedRequest) { this.logger.trace("BrowserCacheManager.getCachedNativeRequest: No cached native request found"); return null; } const parsedRequest = this.validateAndParseJson(cachedRequest); if (!parsedRequest) { this.logger.error("BrowserCacheManager.getCachedNativeRequest: Unable to parse native request"); return null; } return parsedRequest; } isInteractionInProgress(matchClientId) { const clientId = this.getInteractionInProgress(); if (matchClientId) { return clientId === this.clientId; } else { return !!clientId; } } getInteractionInProgress() { const key = `${Constants.CACHE_PREFIX}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`; return this.getTemporaryCache(key, false); } setInteractionInProgress(inProgress) { // Ensure we don't overwrite interaction in progress for a different clientId const key = `${Constants.CACHE_PREFIX}.${TemporaryCacheKeys.INTERACTION_STATUS_KEY}`; if (inProgress) { if (this.getInteractionInProgress()) { throw createBrowserAuthError(interactionInProgress); } else { // No interaction is in progress this.setTemporaryCache(key, this.clientId, false); } } else if (!inProgress && this.getInteractionInProgress() === this.clientId) { this.removeTemporaryItem(key); } } /** * Returns username retrieved from ADAL or MSAL v1 idToken * @deprecated */ getLegacyLoginHint() { // Only check for adal/msal token if no SSO params are being used const adalIdTokenString = this.getTemporaryCache(PersistentCacheKeys.ADAL_ID_TOKEN); if (adalIdTokenString) { this.browserStorage.removeItem(PersistentCacheKeys.ADAL_ID_TOKEN); this.logger.verbose("Cached ADAL id token retrieved."); } // Check for cached MSAL v1 id token const msalIdTokenString = this.getTemporaryCache(PersistentCacheKeys.ID_TOKEN, true); if (msalIdTokenString) { this.browserStorage.removeItem(this.generateCacheKey(PersistentCacheKeys.ID_TOKEN)); this.logger.verbose("Cached MSAL.js v1 id token retrieved"); } const cachedIdTokenString = msalIdTokenString || adalIdTokenString; if (cachedIdTokenString) { const idTokenClaims = AuthToken.extractTokenClaims(cachedIdTokenString, base64Decode); if (idTokenClaims.preferred_username) { this.logger.verbose("No SSO params used and ADAL/MSAL v1 token retrieved, setting ADAL/MSAL v1 preferred_username as loginHint"); return idTokenClaims.preferred_username; } else if (idTokenClaims.upn) { this.logger.verbose("No SSO params used and ADAL/MSAL v1 token retrieved, setting ADAL/MSAL v1 upn as loginHint"); return idTokenClaims.upn; } else { this.logger.verbose("No SSO params used and ADAL/MSAL v1 token retrieved, however, no account hint claim found. Enable preferred_username or upn id token claim to get SSO."); } } return null; } /** * Updates a credential's cache key if the current cache key is outdated */ updateCredentialCacheKey(currentCacheKey, credential) { const updatedCacheKey = CacheHelpers.generateCredentialKey(credential); if (currentCacheKey !== updatedCacheKey) { const cacheItem = this.getItem(currentCacheKey); if (cacheItem) { this.browserStorage.removeItem(currentCacheKey); this.setItem(updatedCacheKey, cacheItem); this.logger.verbose(`Updated an outdated ${credential.credentialType} cache key`); return updatedCacheKey; } else { this.logger.error(`Attempted to update an outdated ${credential.credentialType} cache key but no item matching the outdated key was found in storage`); } } return currentCacheKey; } /** * Builds credential entities from AuthenticationResult object and saves the resulting credentials to the cache * @param result * @param request */ async hydrateCache(result, request) { const idTokenEntity = CacheHelpers.createIdTokenEntity(result.account?.homeAccountId, result.account?.environment, result.idToken, this.clientId, result.tenantId); let claimsHash; if (request.claims) { claimsHash = await this.cryptoImpl.hashString(request.claims); } /** * meta data for cache stores time in seconds from epoch * AuthenticationResult returns expiresOn and extExpiresOn in milliseconds (as a Date object which is in ms) * We need to map these for the cache when building tokens from AuthenticationResult * * The next MSAL VFuture should map these both to same value if possible */ const accessTokenEntity = CacheHelpers.createAccessTokenEntity(result.account?.homeAccountId, result.account.environment, result.accessToken, this.clientId, result.tenantId, result.scopes.join(" "), result.expiresOn ? result.expiresOn.getTime() / 1000 : 0, result.extExpiresOn ? result.extExpiresOn.getTime() / 1000 : 0, base64Decode, undefined, // refreshOn result.tokenType, undefined, // userAssertionHash request.sshKid, request.claims, claimsHash); const cacheRecord = { idToken: idTokenEntity, accessToken: accessTokenEntity, }; return this.saveCacheRecord(cacheRecord); } /** * saves a cache record * @param cacheRecord {CacheRecord} * @param storeInCache {?StoreInCache} * @param correlationId {?string} correlation id */ async saveCacheRecord(cacheRecord, storeInCache, correlationId) { try { await super.saveCacheRecord(cacheRecord, storeInCache, correlationId); } catch (e) { if (e instanceof CacheError && this.performanceClient && correlationId) { try { const tokenKeys = this.getTokenKeys(); this.performanceClient.addFields({ cacheRtCount: tokenKeys.refreshToken.length, cacheIdCount: tokenKeys.idToken.length, cacheAtCount: tokenKeys.accessToken.length, }, correlationId); } catch (e) { } } throw e; } } } const DEFAULT_BROWSER_CACHE_MANAGER = (clientId, logger) => { const cacheOptions = { cacheLocation: BrowserCacheLocation.MemoryStorage, temporaryCacheLocation: BrowserCacheLocation.MemoryStorage, storeAuthStateInCookie: false, secureCookies: false, cacheMigrationEnabled: false, claimsBasedCachingEnabled: false, }; return new BrowserCacheManager(clientId, cacheOptions, DEFAULT_CRYPTO_IMPLEMENTATION, logger); }; export { BrowserCacheManager, DEFAULT_BROWSER_CACHE_MANAGER }; //# sourceMappingURL=BrowserCacheManager.mjs.map