Tired of managing AWS access keys in your Bitbucket Pipelines? Bitbucket OIDC eliminates credential rotation headaches and reduces your attack surface by using temporary credentials that automatically expire.
Long-lived AWS credentials in CI/CD pipelines create security risks and operational overhead. Every access key is a potential breach waiting to happen, and rotating them across dozens of pipelines becomes a full-time job. Bitbucket Pipelines OIDC authentication solves this by exchanging short-lived JWT tokens for temporary AWS credentials.
In this guide, you'll learn how to configure Bitbucket OIDC with AWS CDK, including trust policy conditions, pipeline setup, and troubleshooting common errors. Everything you build here is available as a working example on my GitHub repository.
If you're looking to configure OIDC for GitHub instead, check out my GitHub OIDC guide.
What is Bitbucket OIDC?
Bitbucket OIDC is an identity federation mechanism that uses the OpenID Connect protocol to establish trust between your AWS account and Bitbucket Pipelines. Instead of storing long-term AWS credentials, your pipeline authenticates using a JWT token issued by Bitbucket.
The benefits are significant:
- No credential rotation: Temporary credentials expire automatically after each pipeline run
- Reduced attack surface: No access keys to leak or compromise
- Better auditability: Each pipeline run creates a unique session traceable in CloudTrail
- Simplified management: No secrets to store, rotate, or distribute across repositories
When you compare this to traditional access keys that require manual rotation every 90 days (or more frequently for compliance), OIDC removes an entire category of operational burden and security risk.
How Bitbucket OIDC Authentication Works
Before diving into implementation, understanding the authentication flow helps you troubleshoot issues when they arise. Here's what happens when your Bitbucket Pipeline authenticates with AWS.
The Authentication Flow
The flow works like this:
- Bitbucket generates a JWT token containing claims about your workspace and repository
- Your pipeline sends this token to AWS STS via
AssumeRoleWithWebIdentity - AWS validates the token against your IAM OIDC provider configuration
- AWS checks the trust policy conditions (audience and subject claims)
- If everything matches, STS returns temporary credentials
- Your pipeline uses these credentials to interact with AWS services
The JWT token contains required claims including aud (audience), iat (issued at), iss (issuer), and sub (subject). AWS validates these claims against your OIDC provider and trust policy conditions.
Token Expiration and Session Duration
A common question is how long these credentials last. There are two timeframes to understand:
JWT Token Lifetime: The $BITBUCKET_STEP_OIDC_TOKEN issued by Bitbucket is short-lived. IAM provides a 5-minute grace period beyond the token's expiration time to accommodate clock skew between systems.
AWS Session Duration: The temporary credentials returned by STS have configurable duration:
- Default: 3,600 seconds (1 hour)
- Minimum: 900 seconds (15 minutes)
- Maximum: 43,200 seconds (12 hours)
For most pipeline jobs, the default 1-hour session is sufficient. If you have long-running deployments (like database migrations or large infrastructure changes), you can extend this up to 12 hours using the maxSessionDuration property on your IAM role.
Prerequisites
Before implementing Bitbucket OIDC, ensure you have:
- Bitbucket workspace with Pipelines enabled: Your repository must have Bitbucket Pipelines activated
- AWS CDK installed: Version 2.x or later
- AWS account with IAM permissions: You need permissions to create OIDC providers and IAM roles
- Bitbucket repository settings access: To retrieve the OIDC provider URL and audience value
To find your OIDC configuration values, navigate to your Bitbucket repository settings and look for the OpenID Connect tab. You'll need the Provider URL and Audience values from this page.
Configure Bitbucket OIDC with AWS CDK
Now let's build the infrastructure. We'll use CDK constructs to create the OIDC provider, IAM role, and trust policy conditions.
Step 1: Create the OIDC Provider
AWS CDK provides two constructs for creating OIDC providers:
OpenIdConnectProvider: The legacy construct using Lambda-backed custom resourcesOidcProviderNative(recommended): Uses native CloudFormation resources with no Lambda dependencies
I recommend OidcProviderNative for new implementations. Here's how to create the provider:
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export class BitbucketOidcStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const bitbucketDomain =
'api.bitbucket.org/2.0/workspaces/<WORKSPACE>/pipelines-config/identity/oidc';
const bitbucketAudience =
'ari:cloud:bitbucket::workspace/<YOUR-WORKSPACE-UUID>';
const bitbucketProvider = new iam.OidcProviderNative(
this,
'BitbucketProvider',
{
url: `https://${bitbucketDomain}`,
clientIds: [bitbucketAudience],
// Thumbprints are optional - AWS auto-retrieves them for trusted CAs
}
);
}
}
Important configuration notes:
url: Replace<WORKSPACE>with your Bitbucket workspace name. This URL must begin withhttps://and cannot contain port numbers.clientIds: Replace<YOUR-WORKSPACE-UUID>with your actual audience value from Bitbucket's OpenID Connect settings.thumbprints: This property is now optional. AWS automatically retrieves the certificate thumbprint for OIDC providers using certificates signed by trusted CAs.
You can find both values in your Bitbucket repository settings under the OpenID Connect tab. For details, see the official Bitbucket OIDC documentation.
Step 2: Create an IAM Role with Trust Policy
Next, create an IAM role that trusts the Bitbucket OIDC provider. This role is what your pipeline assumes to gain AWS permissions.
const conditions: iam.Conditions = {
StringEquals: {
[`${bitbucketDomain}:aud`]: bitbucketAudience,
},
};
const bitbucketDeployRole = new iam.Role(this, 'BitbucketDeployRole', {
assumedBy: new iam.WebIdentityPrincipal(
bitbucketProvider.openIdConnectProviderArn,
conditions
),
roleName: 'BitbucketPipelineDeployRole',
description: 'IAM role for Bitbucket Pipelines to deploy via OIDC',
maxSessionDuration: cdk.Duration.hours(2),
});
The WebIdentityPrincipal creates a trust policy that:
- Allows the
sts:AssumeRoleWithWebIdentityaction - Restricts access to tokens from your specific OIDC provider
- Validates the audience claim matches your workspace
The maxSessionDuration controls how long the temporary credentials remain valid. Set this based on your longest-running pipeline job, plus some buffer time.
Step 3: Configure Trust Policy Conditions
Trust policy conditions are critical for security. They determine which Bitbucket entities can assume your IAM role. Without proper conditions, any repository in your workspace could potentially access your AWS resources.
The basic audience condition validates the token comes from your workspace:
const conditions: iam.Conditions = {
StringEquals: {
[`${bitbucketDomain}:aud`]: bitbucketAudience,
},
};
For tighter security, add a subject condition to restrict access to specific repositories or branches:
const conditions: iam.Conditions = {
StringEquals: {
[`${bitbucketDomain}:aud`]: bitbucketAudience,
},
StringLike: {
[`${bitbucketDomain}:sub`]: '{WORKSPACE-UUID}:{REPOSITORY-UUID}:*',
},
};
The subject claim format typically includes your workspace UUID, repository UUID, and branch information. Use StringEquals for exact matches or StringLike with wildcards for pattern matching.
When you synthesize this CDK code, the trust policy looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/api.bitbucket.org/2.0/workspaces/<WORKSPACE>/pipelines-config/identity/oidc"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"api.bitbucket.org/2.0/workspaces/<WORKSPACE>/pipelines-config/identity/oidc:aud": "ari:cloud:bitbucket::workspace/<YOUR-WORKSPACE-UUID>"
}
}
}
]
}
Step 4: Add IAM Permissions for Your Workload
Never use AdministratorAccess in production pipelines. Grant only the permissions your deployment actually needs.
Here's an example for a CDK deployment pipeline:
// Permissions for CDK deployments
bitbucketDeployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'cloudformation:*',
'sts:AssumeRole',
],
resources: ['*'],
})
);
// CDK needs to pass roles to CloudFormation
bitbucketDeployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['iam:PassRole'],
resources: ['arn:aws:iam::*:role/cdk-*'],
})
);
// S3 access for CDK assets
bitbucketDeployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['s3:*'],
resources: [
'arn:aws:s3:::cdk-*',
'arn:aws:s3:::cdk-*/*',
],
})
);
For a simpler S3 deployment, permissions might look like this:
bitbucketDeployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
's3:PutObject',
's3:GetObject',
's3:ListBucket',
's3:DeleteObject',
],
resources: [
'arn:aws:s3:::your-deployment-bucket',
'arn:aws:s3:::your-deployment-bucket/*',
],
})
);
The principle is simple: grant exactly what your pipeline needs and nothing more. If you're unsure what permissions are required, run your pipeline with broader permissions first, then use CloudTrail to identify exactly which API calls are made.
Configure Your Bitbucket Pipeline
With the AWS infrastructure in place, configure your Bitbucket Pipeline to use OIDC authentication.
Pipeline YAML Configuration
Create or update your bitbucket-pipelines.yml at the repository root:
image: node:22
pipelines:
branches:
main:
- step:
name: Deploy to AWS using OIDC
oidc: true
script:
- export AWS_REGION=eu-west-1
- export AWS_ROLE_ARN=arn:aws:iam::123456789012:role/BitbucketPipelineDeployRole
- export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
- echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
- npm ci
- npx cdk deploy --all --require-approval never
Critical settings to update:
image: Use a current Node.js LTS version (Node 20 or 22). Older versions like Node 16 are no longer supported.oidc: true: This flag tells Bitbucket to generate the$BITBUCKET_STEP_OIDC_TOKENfor this step.AWS_ROLE_ARN: Replace with your actual IAM role ARN from the CDK stack.AWS_REGION: Set to your target deployment region.
The pipeline writes the OIDC token to a file that the AWS SDK reads automatically. This is the standard web identity token file approach used by AWS SDKs.
Testing the OIDC Connection
Before running complex deployments, verify your OIDC setup with a simple test:
image: amazon/aws-cli:latest
pipelines:
branches:
main:
- step:
name: Test OIDC Authentication
oidc: true
script:
- export AWS_REGION=eu-west-1
- export AWS_ROLE_ARN=arn:aws:iam::123456789012:role/BitbucketPipelineDeployRole
- export AWS_WEB_IDENTITY_TOKEN_FILE=$(pwd)/web-identity-token
- echo $BITBUCKET_STEP_OIDC_TOKEN > $(pwd)/web-identity-token
- aws sts get-caller-identity
- aws s3 ls
If aws sts get-caller-identity succeeds and shows your assumed role ARN, your OIDC configuration is working. Check CloudTrail for AssumeRoleWithWebIdentity events to see the complete authentication details.
For more details, see the official Bitbucket documentation on OIDC deployment.
Troubleshooting Bitbucket OIDC Errors
When OIDC authentication fails, error messages can be cryptic. Here's how to diagnose and fix the most common issues.
AccessDenied Errors
Error: AccessDenied when calling AssumeRoleWithWebIdentity
This is the most common OIDC error with several potential causes:
1. Incorrect IAM Role ARN IAM role ARNs are case-sensitive. Double-check every character:
arn:aws:iam::123456789012:role/BitbucketPipelineDeployRole
2. Trust Policy Condition Mismatch The audience or subject claims in your JWT don't match the trust policy conditions. Verify:
- The
audcondition matches your Bitbucket audience value exactly - The
subcondition (if used) matches the workspace/repository format
3. Session Duration Too Long
If you request a DurationSeconds value exceeding the role's maxSessionDuration, the request fails. The default maximum is 1 hour unless you explicitly set it higher.
4. Service Control Policies (SCPs) Blocking
If your AWS account is part of an organization, SCPs might deny sts:AssumeRoleWithWebIdentity. Check with your organization administrator.
5. Missing sts:TagSession Permission
If your setup uses session tags, add sts:TagSession to the trust policy's allowed actions.
InvalidIdentityToken Errors
Error: InvalidIdentityToken: Incorrect token audience
The JWT's aud claim doesn't match the client ID configured in your IAM OIDC provider. To verify:
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/api.bitbucket.org/...
Check that the ClientIDList contains your Bitbucket audience value. If using both aud and azp claims in the token, note that AWS STS uses the azp (authorized party) claim as the audience.
Error: InvalidIdentityToken: Token audience contains more than one audience while authorized party is not present
AWS doesn't support JWTs with multiple audiences. Each application should be registered with a separate OIDC provider if needed.
No OpenIDConnect Provider Found
Error: InvalidIdentityToken: No OpenIDConnect provider found in your account
This typically means:
- Wrong OIDC Provider ARN: The ARN in your
AssumeRoleWithWebIdentitycall doesn't match any provider in your account - Trust Policy Mismatch: The IAM role's trust policy doesn't reference the correct OIDC provider
- URL Mismatch: The OIDC provider URL in AWS doesn't exactly match the issuer URL in the JWT
- Cross-Account Issue: The OIDC provider and IAM role must be in the same AWS account
Note: AWS CLI automatically adds a trailing slash to OIDC provider URLs, but SDKs don't. This inconsistency can cause intermittent failures if your URLs aren't consistent.
Using CloudTrail for Debugging
When error messages aren't enough, CloudTrail provides the detailed picture. You can view events for the past 90 days without any setup.
- Navigate to CloudTrail in the AWS Console
- Go to Event History
- Filter by Event name:
AssumeRoleWithWebIdentity - Examine the request and response details
Look at the PrincipalId value in failed requests. It shows exactly what claims AWS received, which you can compare against your trust policy conditions. The format looks like:
arn:aws:iam::123456789012:oidc-provider/api.bitbucket.org/...:sts.amazonaws.com:{workspace-uuid}:{repo-uuid}:...
Important: InvalidIdentityToken errors don't appear in CloudTrail because the failure happens before AWS logs the event. For these errors, focus on validating your OIDC provider configuration and JWT token structure.
Security Best Practices
Beyond basic implementation, these practices harden your Bitbucket OIDC setup for production use.
Restricting Access by Repository
Workspace-level conditions are a starting point, but production environments need repository-level restrictions. Use the sub claim to limit which repositories can assume IAM roles:
const conditions: iam.Conditions = {
StringEquals: {
[`${bitbucketDomain}:aud`]: bitbucketAudience,
},
StringLike: {
// Restrict to specific repository and main branch only
[`${bitbucketDomain}:sub`]: '{WORKSPACE-UUID}:{PRODUCTION-REPO-UUID}:*:main',
},
};
For multiple repositories, create separate IAM roles with specific conditions rather than one permissive role. This limits blast radius if a single repository is compromised.
Test your conditions with CloudTrail before deploying to production. Run the pipeline, check the AssumeRoleWithWebIdentity event, and verify the PrincipalId matches your expected format.
Session Duration Considerations
Shorter sessions reduce risk if credentials are somehow exposed during a pipeline run:
- Short jobs (< 30 minutes): Use the minimum 15-minute session
- Standard deployments: The default 1-hour session works well
- Long-running jobs: Extend to 2-4 hours, but consider breaking into smaller steps
- Maximum 12 hours: Only for exceptional cases like large data migrations
const bitbucketDeployRole = new iam.Role(this, 'BitbucketDeployRole', {
// ...
maxSessionDuration: cdk.Duration.minutes(30), // Tighter for production
});
If you have jobs that legitimately take many hours, consider splitting them into multiple steps where each step gets fresh credentials.
Conclusion
You've successfully configured Bitbucket OIDC with AWS CDK, eliminating the need for long-lived AWS credentials in your pipelines. Let's recap the key points:
- OIDC eliminates credential management: No more access key rotation or secret storage
- Trust policy conditions are critical: Always restrict by audience, and add subject conditions for production
- Session duration requires planning: Match the duration to your pipeline job requirements
- Test before deploying workloads: Verify authentication with simple AWS CLI commands
Everything from this guide is available as a working example in my aws-cdk-examples repository.
For teams using multiple Git providers, you might also want to set up GitHub OIDC with AWS CDK for a consistent credential-free CI/CD experience across platforms.
Build Scalable CDK Apps That Are Easy to Maintain
Transform your complex CDK codebase into a structured, reusable architecture. Get real-world expertise from someone who's built production CDK at scale.

