Skip to content

Commit 214bf83

Browse files
3 Changes to MSAL auth (microsoft#226580)
* Remove access token refreshing logic. The new calling pattern for an extension is that they should just always call `getSession` before doing something with it. The session that returns will be valid because MSAL will refresh any access tokens that are close to expiry using the refresh tokens that it has * NOTE: access tokens expire after 1hr. Refresh tokens expire after like... many days. * Have `createSession` fire an `onDidChangeSession` event so that the badge goes away * Improved logging messages
1 parent 8a625dc commit 214bf83

File tree

2 files changed

+22
-57
lines changed

2 files changed

+22
-57
lines changed

extensions/microsoft-authentication/src/node/authProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export class MsalAuthProvider implements AuthenticationProvider {
172172
const session = this.toAuthenticationSession(result, scopeData.originalScopes);
173173
this._telemetryReporter.sendLoginEvent(session.scopes);
174174
this._logger.info('[createSession]', scopeData.scopeStr, 'returned session');
175+
this._onDidChangeSessionsEmitter.fire({ added: [session], changed: [], removed: [] });
175176
return session;
176177
}
177178

extensions/microsoft-authentication/src/node/publicClientCache.ts

Lines changed: 21 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { SecretStorageCachePlugin } from '../common/cachePlugin';
88
import { SecretStorage, LogOutputChannel, Disposable, SecretStorageChangeEvent, EventEmitter, Memento, window, ProgressLocation, l10n } from 'vscode';
99
import { MsalLoggerOptions } from '../common/loggerOptions';
1010
import { ICachedPublicClientApplication, ICachedPublicClientApplicationManager } from '../common/publicClientCache';
11-
import { Delayer, raceCancellationAndTimeoutError } from '../common/async';
11+
import { raceCancellationAndTimeoutError } from '../common/async';
1212

1313
export interface IPublicClientApplicationInfo {
1414
clientId: string;
@@ -34,7 +34,7 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient
3434
}
3535

3636
async initialize() {
37-
this._logger.debug('Initializing PublicClientApplicationManager');
37+
this._logger.debug('[initialize] Initializing PublicClientApplicationManager');
3838
const keys = await this._secretStorage.get('publicClientApplications');
3939
if (!keys) {
4040
this._initialized = true;
@@ -54,13 +54,13 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient
5454
}
5555
} catch (e) {
5656
// data is corrupted
57-
this._logger.error('Error initializing PublicClientApplicationManager:', e);
57+
this._logger.error('[initialize] Error initializing PublicClientApplicationManager:', e);
5858
await this._secretStorage.delete('publicClientApplications');
5959
}
6060

6161
// TODO: should we do anything for when this fails?
6262
await Promise.allSettled(promises);
63-
this._logger.debug('PublicClientApplicationManager initialized');
63+
this._logger.debug('[initialize] PublicClientApplicationManager initialized');
6464
this._initialized = true;
6565
}
6666

@@ -78,16 +78,16 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient
7878
const pcasKey = JSON.stringify({ clientId, authority });
7979
let pca = this._pcas.get(pcasKey);
8080
if (pca) {
81-
this._logger.debug(clientId, authority, 'PublicClientApplicationManager cache hit');
81+
this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager cache hit`);
8282
return pca;
8383
}
8484

85-
this._logger.debug(clientId, authority, 'PublicClientApplicationManager cache miss, creating new PCA...');
85+
this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager cache miss, creating new PCA...`);
8686
pca = new CachedPublicClientApplication(clientId, authority, this._globalMemento, this._secretStorage, this._accountChangeHandler, this._logger);
8787
this._pcas.set(pcasKey, pca);
8888
await pca.initialize();
8989
await this._storePublicClientApplications();
90-
this._logger.debug(clientId, authority, 'PublicClientApplicationManager PCA created');
90+
this._logger.debug(`[getOrCreate] [${clientId}] [${authority}] PublicClientApplicationManager PCA created`);
9191
return pca;
9292
}
9393

@@ -103,24 +103,24 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient
103103
return;
104104
}
105105

106-
this._logger.debug('PublicClientApplicationManager secret storage change:', e.key);
106+
this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager secret storage change: ${e.key}`);
107107
const result = await this._secretStorage.get(e.key);
108108
const pcasKey = e.key.split(_keyPrefix)[1];
109109

110110
// If the cache was deleted, or the PCA has zero accounts left, remove the PCA
111111
if (!result || this._pcas.get(pcasKey)?.accounts.length === 0) {
112-
this._logger.debug('PublicClientApplicationManager removing PCA:', pcasKey);
112+
this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager removing PCA: ${pcasKey}`);
113113
this._pcas.delete(pcasKey);
114114
await this._storePublicClientApplications();
115-
this._logger.debug('PublicClientApplicationManager PCA removed:', pcasKey);
115+
this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager PCA removed: ${pcasKey}`);
116116
return;
117117
}
118118

119119
// Load the PCA in memory if it's not already loaded
120120
const { clientId, authority } = JSON.parse(pcasKey) as IPublicClientApplicationInfo;
121-
this._logger.debug('PublicClientApplicationManager loading PCA:', pcasKey);
121+
this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager loading PCA: ${pcasKey}`);
122122
await this.getOrCreate(clientId, authority);
123-
this._logger.debug('PublicClientApplicationManager PCA loaded:', pcasKey);
123+
this._logger.debug(`[handleSecretStorageChange] PublicClientApplicationManager PCA loaded: ${pcasKey}`);
124124
}
125125

126126
private async _storePublicClientApplications() {
@@ -134,8 +134,6 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient
134134
class CachedPublicClientApplication implements ICachedPublicClientApplication {
135135
private _pca: PublicClientApplication;
136136

137-
private readonly _refreshDelayer = new DelayerByKey<any>();
138-
139137
private _accounts: AccountInfo[] = [];
140138
private readonly _disposable: Disposable;
141139

@@ -149,6 +147,7 @@ class CachedPublicClientApplication implements ICachedPublicClientApplication {
149147
auth: { clientId: this._clientId, authority: this._authority },
150148
system: {
151149
loggerOptions: {
150+
correlationId: `${this._clientId}] [${this._authority}`,
152151
loggerCallback: (level, message, containsPii) => this._loggerOptions.loggerCallback(level, message, containsPii),
153152
}
154153
},
@@ -191,27 +190,24 @@ class CachedPublicClientApplication implements ICachedPublicClientApplication {
191190
}
192191

193192
async acquireTokenSilent(request: SilentFlowRequest): Promise<AuthenticationResult> {
193+
this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}]`);
194194
const result = await this._pca.acquireTokenSilent(request);
195-
this._setupRefresh(result);
196195
if (result.account && !result.fromCache) {
197196
this._accountChangeHandler({ added: [], changed: [result.account], deleted: [] });
198197
}
199198
return result;
200199
}
201200

202201
async acquireTokenInteractive(request: InteractiveRequest): Promise<AuthenticationResult> {
202+
this._logger.debug(`[acquireTokenInteractive] [${this._clientId}] [${this._authority}] [${request.scopes?.join(' ')}] loopbackClientOverride: ${request.loopbackClient ? 'true' : 'false'}`);
203203
return await window.withProgress(
204204
{
205205
location: ProgressLocation.Notification,
206206
cancellable: true,
207207
title: l10n.t('Signing in to Microsoft...')
208208
},
209209
(_process, token) => raceCancellationAndTimeoutError(
210-
(async () => {
211-
const result = await this._pca.acquireTokenInteractive(request);
212-
this._setupRefresh(result);
213-
return result;
214-
})(),
210+
this._pca.acquireTokenInteractive(request),
215211
token,
216212
1000 * 60 * 5
217213
), // 5 minutes
@@ -227,38 +223,20 @@ class CachedPublicClientApplication implements ICachedPublicClientApplication {
227223
return this._secretStorageCachePlugin.onDidChange(() => this._update());
228224
}
229225

230-
private _setupRefresh(result: AuthenticationResult) {
231-
const on = result.refreshOn || result.expiresOn;
232-
if (!result.account || !on) {
233-
return;
234-
}
235-
236-
const account = result.account;
237-
const scopes = result.scopes;
238-
const timeToRefresh = on.getTime() - Date.now() - 5 * 60 * 1000; // 5 minutes before expiry
239-
const key = JSON.stringify({ accountId: account.homeAccountId, scopes });
240-
this._refreshDelayer.trigger(
241-
key,
242-
// This may need the redirectUri when we switch to the broker
243-
() => this.acquireTokenSilent({ account, scopes, redirectUri: undefined, forceRefresh: true }),
244-
timeToRefresh > 0 ? timeToRefresh : 0
245-
);
246-
}
247-
248226
private async _update() {
249227
const before = this._accounts;
250-
this._logger.debug(this._clientId, this._authority, 'CachedPublicClientApplication update before:', before.length);
228+
this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update before: ${before.length}`);
251229
// Dates are stored as strings in the memento
252230
const lastRemovalDate = this._globalMemento.get<string>(`lastRemoval:${this._clientId}:${this._authority}`);
253231
if (lastRemovalDate && this._lastCreated && Date.parse(lastRemovalDate) > this._lastCreated.getTime()) {
254-
this._logger.debug(this._clientId, this._authority, 'CachedPublicClientApplication removal detected... recreating PCA...');
232+
this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication removal detected... recreating PCA...`);
255233
this._pca = new PublicClientApplication(this._config);
256234
this._lastCreated = new Date();
257235
}
258236

259237
const after = await this._pca.getAllAccounts();
260238
this._accounts = after;
261-
this._logger.debug(this._clientId, this._authority, 'CachedPublicClientApplication update after:', after.length);
239+
this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update after: ${after.length}`);
262240

263241
const beforeSet = new Set(before.map(b => b.homeAccountId));
264242
const afterSet = new Set(after.map(a => a.homeAccountId));
@@ -267,22 +245,8 @@ class CachedPublicClientApplication implements ICachedPublicClientApplication {
267245
const deleted = before.filter(b => !afterSet.has(b.homeAccountId));
268246
if (added.length > 0 || deleted.length > 0) {
269247
this._accountChangeHandler({ added, changed: [], deleted });
270-
this._logger.debug(this._clientId, this._authority, 'CachedPublicClientApplication accounts changed. added, deleted:', added.length, deleted.length);
248+
this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication accounts changed. added: ${added.length}, deleted: ${deleted.length}`);
271249
}
272-
this._logger.debug(this._clientId, this._authority, 'CachedPublicClientApplication update complete');
273-
}
274-
}
275-
276-
class DelayerByKey<T> {
277-
private _delayers = new Map<string, Delayer<T>>();
278-
279-
trigger(key: string, fn: () => Promise<T>, delay: number): Promise<T> {
280-
let delayer = this._delayers.get(key);
281-
if (!delayer) {
282-
delayer = new Delayer<T>(delay);
283-
this._delayers.set(key, delayer);
284-
}
285-
286-
return delayer.trigger(fn, delay);
250+
this._logger.debug(`[update] [${this._clientId}] [${this._authority}] CachedPublicClientApplication update complete`);
287251
}
288252
}

0 commit comments

Comments
 (0)