Skip to content

Fix broken 401 retry logic and wire forceRefresh through default auth provider#187

Open
jethibau-msft wants to merge 1 commit intomicrosoft:masterfrom
jethibau-msft:fix/token-refresh-and-401-retry
Open

Fix broken 401 retry logic and wire forceRefresh through default auth provider#187
jethibau-msft wants to merge 1 commit intomicrosoft:masterfrom
jethibau-msft:fix/token-refresh-and-401-retry

Conversation

@jethibau-msft
Copy link
Copy Markdown
Contributor

@jethibau-msft jethibau-msft commented Mar 27, 2026

Problem

Two bugs prevent ADO web extensions from recovering when the host returns a near-expiry access token that gets rejected by ADO services.

Bug 1: 401 retry in issueRequest() never actually retries (Fetch.ts)

The retry logic uses do {} while(false) with continue. In JavaScript, continue inside do-while(false) jumps to the condition check (false), which exits the loop immediately. The intended retry never executes.

// Before - continue exits the loop, no retry ever happens
do {
    // ... auth + fetch ...
    if (response.status === 401 && !refreshToken && ...) {
        refreshToken = true;
        continue; // → jumps to while(false) → exits
    }
} while (false);

Bug 2: Default auth provider ignores forceRefresh (Client.ts)

The IAuthorizationTokenProvider interface defines getAuthorizationHeader(forceRefresh?: boolean), but the default provider created by getClient() declares the function with no parameters, so forceRefresh is silently ignored.

// Before - forceRefresh parameter is not accepted
getAuthorizationHeader: (): Promise<string> => {
    return getAccessToken().then(token => ...);
}

Fix

Fetch.ts

  • Replace do {} while(false) with a proper while(true) loop that breaks after one retry
  • Use headers.set() instead of headers.append() to avoid duplicate Authorization headers on retry

Client.ts

  • Accept and forward forceRefresh in the default auth provider
  • Cast getAccessToken to accept the optional parameter (forwards harmlessly until the SDK adds support via companion issue)

Impact

Every ADO web extension using getClient() REST clients is affected — no extension can recover from a 401 despite the code clearly intending to support retry. Long-running extension sessions hit intermittent failures when tokens approach expiry with no recovery path.

Companion PR

The azure-devops-extension-sdk also needs getAccessToken(forceRefresh?) to complete the chain: https://github.com/microsoft/azure-devops-extension-sdk (issue/PR to follow).

Two bugs fixed:

1. Fetch.ts: The 401 retry loop used do {} while(false) with continue,
   which in JavaScript exits the loop immediately (continue in do-while
   jumps to the condition check, which is false, so the loop ends).
   This meant no REST client ever retried on 401 despite the code
   clearly intending to. Fixed by replacing with a proper while(true)
   loop that breaks after one retry.

   Also changed headers.append() to headers.set() to avoid duplicate
   Authorization headers on retry.

2. Client.ts: The default authTokenProvider created by getClient()
   ignored the forceRefresh parameter defined by the
   IAuthorizationTokenProvider interface. The parameter is now forwarded
   to SDK.getAccessToken(), enabling token refresh when the host
   supports it.

Background: ADO services reject access tokens within ~5 minutes of
expiry. The IAuthorizationTokenProvider interface defines forceRefresh
for exactly this scenario, but it was never wired through. Combined
with the broken retry loop, extensions had no way to recover from
near-expiry token rejections.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jethibau-msft
Copy link
Copy Markdown
Contributor Author

jethibau-msft commented Mar 27, 2026

@microsoft-github-policy-service agree company="Microsoft"

@microsoft-github-policy-service
Copy link
Copy Markdown
Contributor

@jethibau-msft the command you issued was incorrect. Please try again.

Examples are:

@microsoft-github-policy-service agree

and

@microsoft-github-policy-service agree company="your company"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant