Skip to content

Recover OAuth credentials marked invalid instead of requiring a manual re-login - #1907

Open
NSExceptional wants to merge 2 commits into
atlassian:mainfrom
NSExceptional:fix/recover-invalidated-oauth-credentials
Open

Recover OAuth credentials marked invalid instead of requiring a manual re-login#1907
NSExceptional wants to merge 2 commits into
atlassian:mainfrom
NSExceptional:fix/recover-invalidated-oauth-credentials

Conversation

@NSExceptional

Copy link
Copy Markdown

What Is This Change?

Fixes #1827 (likely also #1818, and part of #1635).

Symptom: "There was an error connecting to Bitbucket Cloud. Please log in again." appears in every new VS Code window, even though the user is signed in and their stored refresh token is still valid. Bitbucket features silently stop working while the auth UI continues to show the user as logged in.

Root cause: once refreshAccessToken persists AuthInfoState.Invalid to secret storage, the credential can never recover:

Meanwhile ClientManager.createClient shows the modal once per ClientManager instance (hasWarnedOfFailure), and a new instance is created per window — hence "every new window", indefinitely.

The Invalid flag is easy to acquire spuriously: OAuthRefesher.getNewTokens classifies any 401 from the token endpoint as permanent (shouldInvalidate), including transient gateway/proxy 401s and RFC 6749 invalid_client responses that say nothing about the user's refresh token. (Until #1747 any 403 did too.) On an affected install the credential had been stuck Invalid since March; replaying the stored refresh token against https://bitbucket.org/site/oauth2/access_token returned HTTP 200 with fresh tokens — the invalidation was bogus, but the extension refused to retry for months.

The change: treat persisted Invalid as "needs re-validation" rather than "dead forever":

  • softRefreshOAuth no longer skips Invalid OAuth credentials; it proceeds to attempt a refresh.
  • refreshAccessToken no longer short-circuits on Invalid; the existing _failedRefreshCache backoff still bounds retries (a refresh that comes back shouldInvalidate marks permanentFailure, so a genuinely dead token is attempted only once per session).
  • A successful refresh now explicitly sets state = AuthInfoState.Valid before saving, so a spuriously-invalidated credential self-heals on the next getAuthInfo call — typically at window startup, before the modal would have been shown.

Behavior for genuinely dead refresh tokens is unchanged: the recovery attempt fails, the credential stays Invalid, and the existing modal still appears.

How Has This Been Tested?

  • Updated the two unit tests that asserted the old skip-on-Invalid behavior, and added coverage for: recovery success (state persisted back to Valid), permanent failure (stays Invalid + permanentFailure cached), and no same-session retry after a permanent failure. authStore.test.ts passes (53/53); the rest of the unit suite is unaffected.
  • Confirmed the bug empirically on an affected Cloud install: decrypted the stored credential (state: 1) and replayed its refresh token against the Bitbucket token endpoint — HTTP 200, still valid — establishing that the persisted Invalid was spurious.
  • Built the patched extension and installed it locally against that same install for manual verification that the credential re-validates on startup and the modal no longer appears.

Basic checks:

  • npm run lint
  • npm run test (unit)

Advanced checks:

  • If Atlassian employee & Bitbucket changes: did you test with DC in mind? — n/a (external contributor; change is OAuth-cloud-path only, DC/basic/PAT auth is untouched)

Recommendations:

  • Update the CHANGELOG if making a user facing change

…ing a manual re-login

Once refreshAccessToken persists AuthInfoState.Invalid, softRefreshOAuth and refreshAccessToken both skip all future refresh attempts, so a credential invalidated by a single transient 401 from the token endpoint stays broken forever and ClientManager.createClient shows the 'There was an error connecting to... Please log in again.' modal in every new window — even though the stored refresh token still works. Fixes the roach motel by attempting a refresh for invalid OAuth credentials (still bounded by the existing failed-refresh backoff) and marking them Valid again when the refresh succeeds.
@atlassian-cla-bot

Copy link
Copy Markdown

Thank you for your submission! Like many open source projects, we ask that you sign our CLA (Contributor License Agreement) before we can accept your contribution.
If your email is listed below, please ensure that you sign the CLA with the same email address.

The following users still need to sign our CLA:
❌NSExceptional

Already signed the CLA? To re-check, try refreshing the page.

@atlassian

atlassian Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

To enable Rovo Dev code reviews, link your GitHub account to your Atlassian account.

This is a one-time task that takes less than a minute. Once your account is linked, resubmit the pull request to trigger a code review.

@twocs

twocs commented Jul 27, 2026

Copy link
Copy Markdown

I agree with the solution to stop considering AuthInfoState.Invalid as a final failed state.

I note that OAuth 2 should return error responses with an HTTP 400 status code along with error and error_description parameters.

https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/

If the Atlassian OAuth 2 server provides these parameters, the auth state can potentially be refined to contain more than simply Valid or Invalid. Unfortunately, the Atlassian documentation does not contain enough information about the various error/error_description that may be returned in their implementation so I agree that Invalid could be retried.
https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/#use-a-refresh-token-to-get-another-access-token-and-refresh-token-pair

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.

Authentication needed every time a new folder is opened. this message appears "There was an error connecting to Bitbucket Cloud. Please log in again."

2 participants