From fcb56da720259a5da6c672e640f70a442d0565d9 Mon Sep 17 00:00:00 2001 From: Cheena Malhotra <13396919+cheenamalhotra@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:48:10 -0700 Subject: [PATCH] Handle multiple matching tokens error (#23441) Surfaces when fetching subscriptions --- .../src/account-provider/auths/azureAuth.ts | 92 ++++++++++--------- .../services/subscriptionService.ts | 9 +- extensions/azurecore/src/constants.ts | 8 ++ 3 files changed, 66 insertions(+), 43 deletions(-) diff --git a/extensions/azurecore/src/account-provider/auths/azureAuth.ts b/extensions/azurecore/src/account-provider/auths/azureAuth.ts index e91a71111c..2888abbc03 100644 --- a/extensions/azurecore/src/account-provider/auths/azureAuth.ts +++ b/extensions/azurecore/src/account-provider/auths/azureAuth.ts @@ -332,46 +332,52 @@ export abstract class AzureAuth implements vscode.Disposable { // Resource endpoint must end with '/' to form a valid scope for MSAL token request. const endpoint = resource.endpoint.endsWith('/') ? resource.endpoint : resource.endpoint + '/'; - - let account: AccountInfo | null = await this.getAccountFromMsalCache(accountId); - if (!account) { - Logger.error('Error: Could not fetch account when acquiring token'); - throw new Error(localize('msal.accountNotFoundError', `Unable to find account info when acquiring token.`)); - } + let account: AccountInfo | null; let newScope; - if (resource.azureResourceId === azdata.AzureResource.ResourceManagement) { - newScope = [`${endpoint}user_impersonation`]; - } else { - newScope = [`${endpoint}.default`]; - } - // construct request - // forceRefresh needs to be set true here in order to fetch the correct token, due to this issue - // https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/3687 - // Even for full tenants, access token is often received expired - force refresh is necessary when token expires. - const tokenRequest = { - account: account, - authority: `${this.loginEndpointUrl}${tenantId}`, - scopes: newScope, - forceRefresh: true - }; try { - return await this.clientApplication.acquireTokenSilent(tokenRequest); - } catch (e) { - Logger.error('Failed to acquireTokenSilent', e); - if (e instanceof AuthError && this.accountNeedsRefresh(e)) { - // build refresh token request - const tenant: Tenant = { - id: tenantId, - displayName: '' - }; - return this.handleInteractionRequiredMsal(tenant, resource); - } else { - if (e.name === 'ClientAuthError') { - Logger.verbose('[ClientAuthError] Failed to silently acquire token'); - } - return errorToPromptFailedResult(e); + account = await this.getAccountFromMsalCache(accountId); + if (!account) { + Logger.error('Error: Could not fetch account when acquiring token'); + throw new Error(localize('msal.accountNotFoundError', `Unable to find account info when acquiring token, please remove account and add again.`)); } + if (resource.azureResourceId === azdata.AzureResource.ResourceManagement) { + newScope = [`${endpoint}user_impersonation`]; + } else { + newScope = [`${endpoint}.default`]; + } + + // construct request + // forceRefresh needs to be set true here in order to fetch the correct token, due to this issue + // https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/3687 + // Even for full tenants, access token is often received expired - force refresh is necessary when token expires. + const tokenRequest = { + account: account, + authority: `${this.loginEndpointUrl}${tenantId}`, + scopes: newScope, + forceRefresh: true + }; + try { + return await this.clientApplication.acquireTokenSilent(tokenRequest); + } catch (e) { + Logger.error('Failed to acquireTokenSilent', e); + if (e instanceof AuthError && this.accountNeedsRefresh(e)) { + // build refresh token request + const tenant: Tenant = { + id: tenantId, + displayName: '' + }; + return this.handleInteractionRequiredMsal(tenant, resource); + } else { + if (e.name === 'ClientAuthError') { + Logger.verbose('[ClientAuthError] Failed to silently acquire token'); + } + return errorToPromptFailedResult(e); + } + } + } catch (error) { + Logger.error(`[ClientAuthError] Failed to find account: ${error}`); + return errorToPromptFailedResult(error); } } @@ -904,12 +910,16 @@ export abstract class AzureAuth implements vscode.Disposable { private async deleteAccountCacheMsal(accountKey: azdata.AccountKey): Promise { const tokenCache = this.clientApplication.getTokenCache(); - let msalAccount: AccountInfo | null = await this.getAccountFromMsalCache(accountKey.accountId); - if (!msalAccount) { - Logger.error(`MSAL: Unable to find account ${accountKey.accountId} for removal`); - throw Error(`Unable to find account ${accountKey.accountId}`); + try { + let msalAccount: AccountInfo | null = await this.getAccountFromMsalCache(accountKey.accountId); + if (!msalAccount) { + Logger.error(`MSAL: Unable to find account ${accountKey.accountId} for removal`); + throw Error(`Unable to find account ${accountKey.accountId}`); + } + await tokenCache.removeAccount(msalAccount); + } catch (error) { + Logger.error(`[ClientAuthError] Failed to find account: ${error}`); } - await tokenCache.removeAccount(msalAccount); await this.msalCacheProvider.clearAccountFromLocalCache(accountKey.accountId); } diff --git a/extensions/azurecore/src/azureResource/services/subscriptionService.ts b/extensions/azurecore/src/azureResource/services/subscriptionService.ts index 37bbf3da68..97dc26c326 100644 --- a/extensions/azurecore/src/azureResource/services/subscriptionService.ts +++ b/extensions/azurecore/src/azureResource/services/subscriptionService.ts @@ -15,6 +15,7 @@ import { Logger } from '../../utils/Logger'; import * as nls from 'vscode-nls'; import { TenantIgnoredError } from '../../utils/TenantIgnoredError'; +import { multiple_matching_tokens_error } from '../../constants'; const localize = nls.loadMessageBundle(); export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService { @@ -52,8 +53,12 @@ export class AzureResourceSubscriptionService implements IAzureResourceSubscript } } catch (error) { if (!account.isStale && !(error instanceof TenantIgnoredError)) { - const errorMsg = localize('azure.resource.tenantSubscriptionsError', "Failed to get subscriptions for account {0} (tenant '{1}'). {2}", account.displayInfo.displayName, tenantId, AzureResourceErrorMessageUtil.getErrorMessage(error)); - Logger.error(`Failed to get subscriptions for account ${account.displayInfo.displayName} (tenant '${tenantId}'). ${AzureResourceErrorMessageUtil.getErrorMessage(error)}`); + const msg = AzureResourceErrorMessageUtil.getErrorMessage(error); + let errorMsg = localize('azure.resource.tenantSubscriptionsError', "Failed to get subscriptions for account {0} (tenant '{1}'). {2}", account.displayInfo.displayName, tenantId, msg); + if (msg.includes(multiple_matching_tokens_error)) { + errorMsg = errorMsg.concat(` To resolve this error, please clear token cache, and refresh account credentials.`); + } + Logger.error(`Failed to get subscriptions for account ${account.displayInfo.displayName} (tenant '${tenantId}'). ${msg}`); errors.push(error); void vscode.window.showWarningMessage(errorMsg); } diff --git a/extensions/azurecore/src/constants.ts b/extensions/azurecore/src/constants.ts index edb387028b..51bf8d4852 100644 --- a/extensions/azurecore/src/constants.ts +++ b/extensions/azurecore/src/constants.ts @@ -93,6 +93,14 @@ export const AADSTS70043 = 'AADSTS70043'; */ export const AADSTS50173 = 'AADSTS50173'; +/** + * multiple_matching_tokens error can occur in scenarios when users try to run ADS as different users, reference issue: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/5134 + * Error message: multiple_matching_tokens The cache contains multiple tokens satisfying the requirements. + * Call AcquireToken again providing more requirements such as authority or account. + */ +export const multiple_matching_tokens_error = 'multiple_matching_tokens'; + export enum BuiltInCommands { SetContext = 'setContext' }