Hi everyone,
I'm running into an issue with OAuth 2.0 (3LO) authentication in our E2E tests and I'm wondering how others have solved it.
Our Playwright E2E tests use our app's REST API, which authenticates via Atlassian OAuth 2.0 (3LO). The authentication flow relies on a refresh token to obtain a new access token. As expected, every refresh request returns a new refresh token, so the token needs to be persisted for the next execution.
This works fine locally, but I'm struggling with running the tests in Bitbucket Pipelines.
The problem is that after each pipeline run I need to persist the newly returned refresh token somewhere. Bitbucket repository variables are read-only from the pipeline's perspective (at least they don't seem intended to be used as mutable storage), so I can't simply update the stored refresh token for the next pipeline run.
Has anyone faced a similar problem?
Some questions I have:
How do you handle rotating refresh tokens in CI/CD?
Is there a recommended approach for Atlassian 3LO authentication in automated pipelines?
Do you use an external secret store (AWS Secrets Manager, Vault, etc.), or is there another approach I'm missing?
Is there a better authentication mechanism for automated E2E tests that avoids this problem altogether?
I'd really appreciate hearing how others have implemented this.
Thanks!
hi @Konrad
I think the root cause is that 3LO isn’t really designed for unattended CI/CD workloads it’s built around delegated user authentication, so refresh token rotation is expected.
A few approaches you can consider to work well:
One thing I’d avoid is trying to use Bitbucket repository variables as mutable storage. They’re intended for configuration rather than runtime state, and updating them from pipelines can become problematic, if multiple pipeline runs overlap and race to update the latest refresh token.
Out of curiosity, are your playwright tests validating the OAuth flow itself, or are they primarily testing your app after authentication? That distinction would influence the recommended approach. If it’s the latter, I’d lean towards abstracting authentication away from the tests and letting the pipeline consume a valid access token rather than owning the refresh token lifecycle.
The documentation below clearly explains how rotating refresh tokens work and that applications must always persist the newly returned refresh token, but it doesn’t describe a recommended CI/CD strategy or where that token should be stored.
https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/?utm_source
Thanks for the explanation - that makes sense.
In our case we're not testing the OAuth flow itself. We only use 3LO because our backend API requires it when preparing test data before running Playwright tests.
I understand that an external secret manager or token broker would solve the problem, but I was wondering whether Atlassian has an officially recommended approach for CI/CD scenarios like this but it seems there is no such recommendation.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Glad it helps, yes lets see what's in for Atlassian's strategy.
On this note, Atlassian close competitors have well-defined CI/CD security recommendations, and both strongly discourage storing tokens directly in repositories, pipeline files, or source code.
The preferred approach is to use platform’s built-in secret management (ex: Bitbucket Repository/Workspace/Deployment Variables) and for enterprise and regulated environments, integrate with an external secrets manager. Where supported, using OIDC/workload identity with short-lived credentials is considered the most secure pattern over long-lived API tokens.
If your organization uses Atlassian Atlassian Guard, you can also:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Konrad, welcome to the community. For E2E tests that just hit the REST API as a user, drop 3LO in CI and the rotating-token problem disappears. Use Basic auth with an Atlassian API token — `Authorization: Basic base64("email:api_token")` — it doesn't rotate, so it sits fine in a secured Bitbucket repository variable. One caveat: API tokens now carry a max 1-year expiry, so you swap it yearly, not per run. If you truly need 3LO (per-user scoped tokens, or testing the flow itself), your instinct's right: each refresh returns a new single-use token and a pipeline can't rewrite its own repo variable, so an external store like Secrets Manager is the move. Basic auth for REST APIs
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Unfortunately, in our case we're not calling Jira's REST API directly. The Playwright tests call our application's backend API, which validates an Atlassian 3LO access token and uses it to act on behalf of the authenticated user.
So replacing 3LO with Basic auth isn't something we can do without changing our application's authentication model.
It sounds like, if we continue using 3LO, an external secrets manager (or a token broker) is indeed the recommended approach.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Yeah, if your backend expects an Atlassian 3LO access token then Basic auth is off the table without touching your auth model, so external store it is. Two things that bit me when I wired this up. The rotation is single-use — the moment you refresh, the old refresh token is dead and you get a new one valid for another 90 days, so the pipeline has to write that new token straight back to the store or the next run fails with invalid_grant. And watch concurrent runs: two pipelines grabbing the same refresh token at once will race, one wins and the other's token is already invalidated. A small token broker sidesteps that, since it holds the refresh token itself and just hands each run a short-lived access token. Also keep in mind the refresh token dies after 90 days of no use, so a repo that goes quiet over a long break will need a fresh authorize before pipelines run green again.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.