Detach authority/tenant from the PublicClientApp (#242719)

everything
This commit is contained in:
Tyler James Leonhardt
2025-03-05 10:55:10 -08:00
committed by GitHub
parent c790f5e777
commit 95ab795ff0
7 changed files with 251 additions and 111 deletions
@@ -51,12 +51,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
private readonly _env: Environment = Environment.AzureCloud
) {
this._disposables = _context.subscriptions;
this._publicClientManager = new CachedPublicClientApplicationManager(
_context.globalState,
_context.secrets,
this._logger,
this._env.name
);
this._publicClientManager = new CachedPublicClientApplicationManager(_context.secrets, this._logger, this._env.name);
const accountChangeEvent = this._eventBufferer.wrapEvent(
this._publicClientManager.onDidAccountsChange,
(last, newEvent) => {
@@ -109,8 +104,8 @@ export class MsalAuthProvider implements AuthenticationProvider {
clientTenantMap.get(key)!.refreshTokens.push(session.refreshToken);
}
for (const { clientId, tenant, refreshTokens } of clientTenantMap.values()) {
await this.getOrCreatePublicClientApplication(clientId, tenant, refreshTokens);
for (const { clientId, refreshTokens } of clientTenantMap.values()) {
await this._publicClientManager.getOrCreate(clientId, refreshTokens);
}
}
@@ -173,8 +168,8 @@ export class MsalAuthProvider implements AuthenticationProvider {
return allSessions;
}
const cachedPca = await this.getOrCreatePublicClientApplication(scopeData.clientId, scopeData.tenant);
const sessions = await this.getAllSessionsForPca(cachedPca, scopeData.originalScopes, scopeData.scopesToSend, options?.account);
const cachedPca = await this._publicClientManager.getOrCreate(scopeData.clientId);
const sessions = await this.getAllSessionsForPca(cachedPca, scopeData, options?.account);
this._logger.info(`[getSessions] [${scopeData.scopeStr}] returned ${sessions.length} session(s)`);
return sessions;
@@ -185,7 +180,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
// Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead.
this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'starting');
const cachedPca = await this.getOrCreatePublicClientApplication(scopeData.clientId, scopeData.tenant);
const cachedPca = await this._publicClientManager.getOrCreate(scopeData.clientId);
// Used for showing a friendlier message to the user when the explicitly cancel a flow.
let userCancelled: boolean | undefined;
@@ -211,6 +206,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
: ExtensionHost.WebWorker,
});
const authority = new URL(scopeData.tenant, this._env.activeDirectoryEndpointUrl).toString();
let lastError: Error | undefined;
for (const flow of flows) {
if (flow !== flows[0]) {
@@ -223,6 +219,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
try {
const result = await flow.trigger({
cachedPca,
authority,
scopes: scopeData.scopesToSend,
loginHint: options.account?.label,
windowHandle: window.nativeHandle ? Buffer.from(window.nativeHandle) : undefined,
@@ -260,7 +257,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
if (account.homeAccountId === sessionId) {
this._telemetryReporter.sendLogoutEvent();
promises.push(cachedPca.removeAccount(account));
this._logger.info(`[removeSession] [${sessionId}] [${cachedPca.clientId}] [${cachedPca.authority}] removing session...`);
this._logger.info(`[removeSession] [${sessionId}] [${cachedPca.clientId}] removing session...`);
}
}
}
@@ -281,26 +278,69 @@ export class MsalAuthProvider implements AuthenticationProvider {
//#endregion
private async getOrCreatePublicClientApplication(clientId: string, tenant: string, refreshTokensToMigrate?: string[]): Promise<ICachedPublicClientApplication> {
const authority = new URL(tenant, this._env.activeDirectoryEndpointUrl).toString();
return await this._publicClientManager.getOrCreate(clientId, authority, refreshTokensToMigrate);
}
private async getAllSessionsForPca(
cachedPca: ICachedPublicClientApplication,
originalScopes: readonly string[],
scopesToSend: string[],
scopeData: ScopeData,
accountFilter?: AuthenticationSessionAccountInformation
): Promise<AuthenticationSession[]> {
const accounts = accountFilter
let filteredAccounts = accountFilter
? cachedPca.accounts.filter(a => a.homeAccountId === accountFilter.id)
: cachedPca.accounts;
// Group accounts by homeAccountId
const accountGroups = new Map<string, AccountInfo[]>();
for (const account of filteredAccounts) {
const existing = accountGroups.get(account.homeAccountId) || [];
existing.push(account);
accountGroups.set(account.homeAccountId, existing);
}
// Filter to one account per homeAccountId
filteredAccounts = Array.from(accountGroups.values()).map(accounts => {
if (accounts.length === 1) {
return accounts[0];
}
// If we have a specific tenant to target, prefer that one
if (scopeData.tenantId) {
const matchingTenant = accounts.find(a => a.tenantId === scopeData.tenantId);
if (matchingTenant) {
return matchingTenant;
}
}
// Otherwise prefer the home tenant
return accounts.find(a => a.tenantId === a.idTokenClaims?.tid) || accounts[0];
});
const authority = new URL(scopeData.tenant, this._env.activeDirectoryEndpointUrl).toString();
const sessions: AuthenticationSession[] = [];
return this._eventBufferer.bufferEventsAsync(async () => {
for (const account of accounts) {
for (const account of filteredAccounts) {
try {
const result = await cachedPca.acquireTokenSilent({ account, scopes: scopesToSend, redirectUri });
sessions.push(this.sessionFromAuthenticationResult(result, originalScopes));
let forceRefresh: true | undefined;
if (scopeData.tenantId) {
// If the tenants do not match, then we need to skip the cache
// to get a new token for the new tenant
if (account.tenantId !== scopeData.tenantId) {
forceRefresh = true;
}
} else {
// If we are requesting the home tenant and we don't yet have
// a token for the home tenant, we need to skip the cache
// to get a new token for the home tenant
if (account.tenantId !== account.idTokenClaims?.tid) {
forceRefresh = true;
}
}
const result = await cachedPca.acquireTokenSilent({
account,
authority,
scopes: scopeData.scopesToSend,
redirectUri,
forceRefresh
});
sessions.push(this.sessionFromAuthenticationResult(result, scopeData.originalScopes));
} catch (e) {
// If we can't get a token silently, the account is probably in a bad state so we should skip it
// MSAL will log this already, so we don't need to log it again