Fix concurrent token initialization

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Alex Yang (DevDiv)
2026-07-20 14:49:48 -07:00
parent 463a876284
commit b9705e9439
2 changed files with 70 additions and 23 deletions
+29 -23
View File
@@ -231,31 +231,35 @@ export class InteractiveAuthBroker {
if (hasUsableToken(this.accessToken, this.now())) return this.accessToken.token; if (hasUsableToken(this.accessToken, this.now())) return this.accessToken.token;
if (this.tokenInFlight) return this.tokenInFlight; if (this.tokenInFlight) return this.tokenInFlight;
let credential = this.credential; const request = (async () => {
if (!credential) { let credential = this.credential;
try { if (!credential) {
const authenticationRecord = await this.loadAuthRecord(); try {
credential = this.createInteractiveCredential(authenticationRecord); const authenticationRecord = await this.loadAuthRecord();
this.credential = credential; if (hasUsableToken(this.accessToken, this.now())) return this.accessToken.token;
} catch (error) { credential = this.credential;
if (isAuthenticationRequiredError(error)) { if (!credential) {
throw new ConnectorAuthenticationRequiredError( credential = this.createInteractiveCredential(authenticationRecord);
"Sign in to Azure to continue.", this.credential = credential;
{ cause: error }, }
); } catch (error) {
if (isAuthenticationRequiredError(error)) {
throw new ConnectorAuthenticationRequiredError(
"Sign in to Azure to continue.",
{ cause: error },
);
}
throw error;
} }
throw error;
} }
} try {
const request = credential.getToken(this.scope) const accessToken = await credential.getToken(this.scope);
.then((accessToken) => {
if (!accessToken?.token || !Number.isFinite(accessToken.expiresOnTimestamp)) { if (!accessToken?.token || !Number.isFinite(accessToken.expiresOnTimestamp)) {
throw new Error("Azure identity returned an incomplete ARM access token."); throw new Error("Azure identity returned an incomplete ARM access token.");
} }
this.accessToken = accessToken; this.accessToken = accessToken;
return accessToken.token; return accessToken.token;
}) } catch (error) {
.catch((error) => {
if (!isAuthenticationRequiredError(error)) throw error; if (!isAuthenticationRequiredError(error)) throw error;
if (this.credential === credential) { if (this.credential === credential) {
this.credential = null; this.credential = null;
@@ -265,12 +269,14 @@ export class InteractiveAuthBroker {
"Sign in to Azure to continue.", "Sign in to Azure to continue.",
{ cause: error }, { cause: error },
); );
}) }
.finally(() => { })();
if (this.tokenInFlight === request) this.tokenInFlight = null;
});
this.tokenInFlight = request; this.tokenInFlight = request;
return request; try {
return await request;
} finally {
if (this.tokenInFlight === request) this.tokenInFlight = null;
}
} }
} }
@@ -162,6 +162,47 @@ test("a new broker restores the persisted credential without reopening the brows
assert.equal(authenticateCalls, 1); assert.equal(authenticateCalls, 1);
}); });
test("concurrent first-time token requests share credential initialization and acquisition", async () => {
let releaseAuthenticationRecord;
const authenticationRecordReady = new Promise((resolve) => {
releaseAuthenticationRecord = resolve;
});
let loadAuthRecordCalls = 0;
let createCredentialCalls = 0;
let tokenCalls = 0;
const broker = new InteractiveAuthBroker({
async loadAuthRecord() {
loadAuthRecordCalls++;
await authenticationRecordReady;
return authenticationRecord();
},
createCredential: () => {
createCredentialCalls++;
return {
async getToken() {
tokenCalls++;
return accessToken("shared-token");
},
};
},
});
const firstRequest = broker.getToken();
const secondRequest = broker.getToken();
await new Promise((resolve) => setImmediate(resolve));
const loadsBeforeRelease = loadAuthRecordCalls;
releaseAuthenticationRecord();
assert.deepEqual(
await Promise.all([firstRequest, secondRequest]),
["shared-token", "shared-token"],
);
assert.equal(loadsBeforeRelease, 1);
assert.equal(loadAuthRecordCalls, 1);
assert.equal(createCredentialCalls, 1);
assert.equal(tokenCalls, 1);
});
test("cancelling sign-in aborts the credential request", async () => { test("cancelling sign-in aborts the credential request", async () => {
let abortSignal; let abortSignal;
const credential = { const credential = {