Bitbucket Pipelines OIDC: the step token explained
What oidc: true gives a Bitbucket step: the BITBUCKET_STEP_OIDC_TOKEN, the workspace issuer, audiences, the sub claim and how to pin a trust policy to it.
Crunu teamPublished 7 min read
On this page
Bitbucket Pipelines hands a step a signed JWT when that step has oidc: true. The token arrives in the variable $BITBUCKET_STEP_OIDC_TOKEN, is issued by an identity provider that belongs to your workspace, and names the repository, branch, step and, for deployment steps, the deployment environment. AWS, Google Cloud, Vault or Crunu check its signature against the workspace's public keys, compare its claims with a policy you wrote, and hand back short-lived access. No cloud key needs to sit in repository variables.
This post walks through the token itself: how to turn it on, which claims it carries, how audiences work, how to pin an AWS trust policy to the sub claim, what Bitbucket's third-party secret providers need, and how the same token binds a Crunu service token to one repository, branch and deployment. For GitHub Actions and GitLab CI, and for every cloud, see the guide to CI/CD secrets with OIDC.
Turning the token on
OIDC is a property of a step. This step follows Atlassian's AWS example (Atlassian: deploy on AWS using OIDC):
image: amazon/aws-cli
pipelines:
default:
- step:
oidc: true
script:
- export AWS_REGION=us-west-2
- export AWS_ROLE_ARN=arn:aws:iam::123456789012:role/oidc-demo
- export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
- echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
- aws s3 lsBitbucket generates a separate token for each step that asks for one. Other steps in the same pipeline don't get it unless they ask for it too. That is the first control you have: put oidc: true only on the steps that talk to a cloud or a vault.
The script writes the JWT to a file because the AWS CLI reads a web identity token from the path in AWS_WEB_IDENTITY_TOKEN_FILE. The file lives in the build directory for the rest of the step, so don't copy it into an artifact or a cache.
What the token says about the step
Atlassian's example payload lists these claims: sub, aud, iss, exp, iat, workspaceUuid, repositoryUuid, pipelineUuid, stepUuid, branchName and deploymentEnvironment. Three of them carry most of the weight in a policy.
repositoryUuididentifies the repository with its UUID, in braces, for example{1de489be-…}. A UUID survives a rename of the repository, unlike a slug.branchNameis the branch the pipeline runs on.subpacks the identity into one string:{REPOSITORY_UUID}[:{ENVIRONMENT_UUID}]:{STEP_UUID}. The environment part "shows up only if the step is assigned to a deployment environment."
That last detail matters. A deployment step to production has the production environment's UUID inside sub; an ordinary test step doesn't. Atlassian calls this "very important for crafting strict policies per environment."
The values you need for a policy are in Repository settings → Pipelines → OpenID Connect: the identity provider URL, the audience, and the UUIDs of the workspace, repository and deployment environments (Atlassian: integrate Pipelines with resource servers using OIDC).
One issuer per workspace
GitHub and GitLab.com each have one shared issuer by default. Bitbucket has one per workspace:
https://api.bitbucket.org/2.0/workspaces/<workspace>/pipelines-config/identity/oidcThe discovery document and public keys hang off that address. Two consequences follow. First, the issuer alone already limits a policy to your workspace, so a token from another company's workspace fails before any claim is compared. Second, every repository in your workspace shares that issuer, so a policy that checks only the issuer and the default audience accepts a token from any of your repositories. Atlassian's AWS page says so plainly: the basic setup lets "any repository under the workspace" assume the role.
Audiences: the default and your own
The default audience has the form ari:cloud:bitbucket::workspace/<workspace-uuid>. It is the same for every step in the workspace, and every service that trusts it accepts the same token. If one service logs incoming tokens or is compromised, a token it received is valid at the others until it expires.
Since February 2026, a step can declare several audiences of its own, per step or for the whole file, and one token carries all of them (Atlassian: multiple audiences):
pipelines:
default:
- step:
oidc:
audiences:
- https://app.crunu.com
script:
- ./deploy.shThe limits are 10 audiences per step or file and 150 characters per audience name. Atlassian also states the rule that makes audiences useful: resource servers "must check that their identifier appears in the aud claim" and refuse the token if it doesn't.
Notice the difference from GitLab, where a job declares one ID token per audience. In Bitbucket a step gets one token with every audience in it, so each service named there could replay it at the others. If a step needs AWS and another service, consider two steps with one audience each.
AWS: pin the trust policy to sub
Register the workspace's identity provider URL in IAM with your audience as the client ID, then create a role. Atlassian notes that AWS lets a trust policy use only a set of claims, which is why its examples match on sub with StringLike. To allow one repository:
"Condition": {
"StringLike": {
"api.bitbucket.org/2.0/workspaces/kiteline/pipelines-config/identity/oidc:sub": "{REPO_UUID}:*"
}
}To allow only deployment steps to one environment, include the environment's UUID:
"Condition": {
"StringLike": {
"api.bitbucket.org/2.0/workspaces/kiteline/pipelines-config/identity/oidc:sub": "{REPO_UUID}*:{ENVIRONMENT_UUID}:*"
}
}Kiteline is a fictional company; use your own workspace and UUIDs, braces included. The branch isn't part of sub, so for AWS the deployment environment is the practical way to keep a feature branch away from production. Atlassian also documents an IP-range condition limited to Bitbucket Pipelines' addresses as an extra layer.
The OIDC policy builder drafts trust policies like these, and what is a JWT explains the token format.
Third-party secret providers: what they need
Since December 2024, Bitbucket can inject variables from an outside secret store into a step without storing them in Bitbucket (Atlassian: third-party secrets in Pipelines). The flow is built on the same step token:
- You set a Secret provider endpoint for the workspace, or
SECRET_PROVIDER_URIon a self-hosted runner. - The step declares
oidc: true, with the comment "Required to get the token to pass to the Secret Provider". - At run time the runner calls your endpoint "including the OIDC token in the
Authorizationheader." - The endpoint validates the token against the workspace's keys and issuer, picks the secrets that the claims allow, and answers with "a flat JSON key-value map, where both keys and values are strings."
The part that's easy to miss: the endpoint is yours to build. Atlassian describes it as "an integration that serves as a bridge between your specific secret store and the runner" and points to a demo. On self-hosted runners the request goes from the runner, so values stay inside your network. Bitbucket says logs "automatically mask these variables", which helps with accidental echo but not with a value that's been encoded or split; masked CI variables still leak covers where masking stops.
So the provider gives you declarative injection, and in return you run and secure a service whose only job is to turn a Bitbucket JWT into secrets. It needs the same claim checks as an AWS trust policy, written in your own code.
Binding a Crunu service token to the step
Crunu reads the same token. A pipeline signs in with a service token stored in CRUNU_TOKEN; when that token has a CI binding (OIDC), the server accepts it only together with a Bitbucket JWT whose claims match the binding. The rule for Bitbucket takes the workspace name and UUID, the repository UUID, optionally a branch (main or a list of patterns), optionally the deployment environment's UUID, and the audience. The issuer is the workspace's own, and UUIDs are compared without braces and regardless of case (Crunu docs: OIDC binding).
Unlike AWS, the Crunu rule matches branchName directly, so a production token can require both main and the production deployment environment.
pipelines:
branches:
main:
- step:
name: Deploy
deployment: production
oidc:
audiences:
- https://app.crunu.com
script:
- curl -sSf "$CRUNU_INSTALL_URL" -o /tmp/crunu-install.sh
- echo "$CRUNU_INSTALL_SHA256 /tmp/crunu-install.sh" | sha256sum -c -
- sh /tmp/crunu-install.sh --version "$CRUNU_VERSION" --sha256 "$CRUNU_BINARY_SHA256" --user-install
- crunu run --env web-app/production -- npm run deploycrunu finds $BITBUCKET_STEP_OIDC_TOKEN by itself. Use the server address as the audience, https://app.crunu.com in the cloud or your own server's address, so the token is made for Crunu and nothing else. The default workspace audience also works, but then every service in the workspace receives a token Crunu would accept.
Two Bitbucket specifics are handled on the server side. Bitbucket tokens have no jti claim, so the server recognizes a replay by a fingerprint of the token's signed part and raises an alert; crunu therefore signs in once per step and keeps that session. And because the whole step shares one token, the binding is only as narrow as the step: a step that also calls AWS hands the same JWT to AWS. Setup details, including the crunu run pipe, are in the Bitbucket Pipelines docs and on the Bitbucket Pipelines integration page.
A CRUNU_TOKEN copied out of a log then fails from a laptop or another repository, because it needs a fresh JWT from this repository's step. Within the step, the decrypted values are still visible to the command and anything it runs, so grant the token only the environments that step needs.
Checklist
Sources
- Deploy on AWS using Bitbucket Pipelines OpenID Connect, Atlassian Support, checked October 30, 2026.
- Integrate Pipelines with resource servers using OIDC, Atlassian Support, checked October 30, 2026.
- Bitbucket Pipelines OIDC now supports multiple audiences, Atlassian blog, checked October 30, 2026.
- Third-party secrets are now supported in Bitbucket Pipelines, Atlassian blog, checked October 30, 2026.