We operate a Bitbucket Cloud OAuth consumer (classic consumer, authProfile
"oauth.bitbucket.legacyOAuthPrivate") used by a hosting integration. It works
correctly for all users except one, where every access token issued for that
account is rejected by the Bitbucket REST API within seconds of being issued.
WHAT HAPPENS
1. We complete an OAuth exchange against
https://bitbucket.org/site/oauth2/access_token
2. The token endpoint returns 200 with a valid access_token
(expires_in: 7200)
3. We call GET https://api.bitbucket.org/2.0/user/workspaces with
"Authorization: Bearer <that token>"
4. Bitbucket returns 401:
{"type":"error","error":{"message":"Token is invalid, expired, or not
supported for this endpoint."}}
The fastest reproduction had the token rejected 155 ms after it was issued.
GET /2.0/user also returns 401 for the same token.
WHAT WE HAVE RULED OUT
- Grant type. Reproduced with both grant_type=authorization_code (fresh
consent, token rejected 20 seconds after issue) and grant_type=refresh_token
(token rejected 155 ms after issue).
- Token expiry. Decoded iat/exp confirm a 2-hour validity window; rejection
occurs milliseconds to seconds after iat.
- Scopes. The token carries read:account:bitbucket-legacy,
read:repository:bitbucket-legacy, admin:repository:bitbucket-legacy,
admin:webhook:bitbucket-legacy, read:project:bitbucket-legacy,
read:email:bitbucket-legacy, offline_access. read:account:bitbucket-legacy
is documented as sufficient for /2.0/user and /2.0/user/workspaces.
- Audience. aud contains both our client_id and the matching
ari:cloud:bitbucket::oauth-client/... value.
- Request shape. Identical code path succeeds for other users.
- Stale authorization. Reproduced after a full uninstall, reinstall and fresh
consent, on two separate installations.
- Repository, project or workspace permissions. /2.0/user/workspaces
resolves identity and workspace membership; it does not evaluate
repository-level permissions. A user with no accessible workspaces would
receive 200 with an empty values array, not 401.
- Account class. The same integration works for other accounts, including
accounts owned and managed by an Atlassian organization.
DETAILS THAT MAY BE RELEVANT
- All 401 responses carry an identical ETag
("6d3e37891d3d2149bb4ff557646bd02b"), which suggests a canned response
produced by a common pre-handler rather than per-request endpoint logic.
- All 401 responses carry x-asap-succeeded: True, so Atlassian Edge accepted
and forwarded the request.
- www-authenticate: BitbucketCustom realm=Bitbucket.org HTTP
- x-dc-location: Micros-3, x-version: ef78fb922ffb
- The affected token's systemAccountId claim differs from its sub claim. We do
not know whether that is expected for a 3LO token on this auth profile.
- All grants for this user were authorized from the same Atlassian browser
session (same session_id claim across every reproduction), including across
the uninstall/reinstall. Each grant otherwise had a distinct jti, rti and
refresh_chain_id.
- Reproduced across two Bitbucket API-facing deployments in different regions.
- It previously worked for this user; it now fails consistently.
QUESTIONS
1. Under what conditions will api.bitbucket.org reject an access token that
auth.atlassian.com issued seconds earlier with a valid exp, correct aud,
and read:account:bitbucket-legacy present?
2. Is there account-level state that would cause Bitbucket to refuse to
authenticate a principal even with a structurally valid token? For example
missing Bitbucket product access, an in-flight identity or organization
migration, or a per-account restriction on the legacy OAuth profile.
3. Would an organization-level policy restricting third-party OAuth apps
surface as 401 on /2.0/user/workspaces rather than 403? If so, where does
an org admin inspect and change that?
4. Is a difference between the sub and systemAccountId claims expected for a
3LO token on authProfile oauth.bitbucket.legacyOAuthPrivate?
5. Is there any known issue affecting classic OAuth consumers using the
*:bitbucket-legacy granular scopes for a subset of accounts?
We have Atlassian request IDs, trace IDs, the affected account ID and the
decoded token claims for four separate reproductions. We have not posted them
here because this forum is public. Happy to provide them privately or via a
support ticket.