DEV Community

Morgan Wowk
Morgan Wowk

Posted on

The Misleading "User is not authorized to access connection" Error in AWS CodeBuild — and Why Your IAM Policy Looks Fine

If you've wired up an AWS CodeBuild project to pull source from GitHub
via CodeConnections (formerly CodeStar Connections), you may have hit
this error at the moment you call UpdateProject or trigger your first
build:

OAuthProviderException: User is not authorized to access connection
arn:aws:codestar-connections:us-east-1:123456789012:connection/...
Enter fullscreen mode Exit fullscreen mode

The error wording is wrong in two different ways at the same time, which
is what makes it such a brutal debugging session. This post walks
through what the message actually means, two distinct root causes that
both produce it, and the IAM policy shape that resolves both.

"User" doesn't mean what you think it means

The first thing to know: the User in the error message is almost
never the IAM principal making the API call. It's the CodeBuild
service role
— the role CodeBuild will eventually assume to clone
your repo at build time.

When you call UpdateProject with a source.auth.type of
CODECONNECTIONS, AWS validates the service role's permissions to
use the connection. If those permissions are missing, the API call
fails with this error and blames "User," meaning the service role —
not you.

So the first thing to do when this error fires: stop checking the
permissions on whoever ran the command, and start checking the
permissions on the service role referenced by the CodeBuild
project.

Trap #1: GetConnectionToken is required but undocumented

The AWS docs for CodeConnections IAM say you need
codestar-connections:UseConnection on the connection ARN. That's
true but incomplete.

You also need codestar-connections:GetConnectionToken — and
this requirement is undocumented in the IAM examples page as of
this writing. Without it, UpdateProject returns
OAuthProviderException even though your policy "looks right."

This trap was first surfaced publicly in a comment on a Terraform
AWS Provider GitHub issue, and you'll find it referenced in the
SDK source if you go looking. The minimum service-role grant that
actually works is:

statement {
  effect = "Allow"
  actions = [
    "codestar-connections:UseConnection",
    "codestar-connections:GetConnectionToken",  # ← undocumented requirement
    "codeconnections:UseConnection",
    "codeconnections:GetConnectionToken",       # ← same
  ]
  resources = [aws_codestarconnections_connection.your_connection.arn]
}
Enter fullscreen mode Exit fullscreen mode

(More on why the snippet duplicates each action under both prefixes
in a moment.)

If you've been chasing this for an hour and your policy already has
UseConnection, adding GetConnectionToken is very likely the fix.

Trap #2: list actions don't accept ARN scoping

Now here's the meaner one — and the one that bites you the second
time, after you've added GetConnectionToken and your build still
fails with the same error.

CodeConnections has three actions that do not accept resource-level
permissions
at all:

  • ListConnections
  • ListInstallationTargets
  • ListTagsForResource

If you grant these with resources scoped to a specific connection
ARN, the statement silently never matches the action. AWS denies
the call without telling you which action was denied — and surfaces
the same misleading "User is not authorized to access connection"
error
as Trap #1.

AWS's internal flow for UpdateProject (and for the build's source-
resolution phase) calls list-level actions as part of validating the
connection binding. Your policy can grant codeconnections:* on the
connection ARN and STILL fail, because the wildcard doesn't help when
the action itself refuses resource-level scoping.

The fix: split your connections grant into two statements. List
actions get resources: ["*"]. Resource-level actions stay scoped to
your connection ARN.

# Statement 1: list-level actions that don't accept ARN scoping.
# These MUST use "*" or they'll be silently denied.
statement {
  sid    = "CodeConnectionsListLevel"
  effect = "Allow"
  actions = [
    "codestar-connections:ListConnections",
    "codestar-connections:ListInstallationTargets",
    "codestar-connections:ListTagsForResource",
    "codeconnections:ListConnections",
    "codeconnections:ListInstallationTargets",
    "codeconnections:ListTagsForResource",
  ]
  resources = ["*"]
}

# Statement 2: resource-level actions you can safely scope.
statement {
  sid    = "CodeConnectionsResourceLevel"
  effect = "Allow"
  actions = [
    "codestar-connections:GetConnection",
    "codestar-connections:GetConnectionToken",
    "codestar-connections:PassConnection",
    "codestar-connections:UseConnection",
    "codeconnections:GetConnection",
    "codeconnections:GetConnectionToken",
    "codeconnections:PassConnection",
    "codeconnections:UseConnection",
  ]
  resources = [aws_codestarconnections_connection.your_connection.arn]
}
Enter fullscreen mode Exit fullscreen mode

If you'd rather not enumerate every action, the loosest version
that's still narrowly enough scoped is Get*, List*, Pass*, Use* on
"*", with the understanding that the role can then enumerate every
connection in your account. For most CI workloads that's a reasonable
tradeoff.

Why both codestar-connections:* and codeconnections:*?

In March 2024 AWS renamed the service from CodeStar Connections to
CodeConnections. Both action prefixes still work, and AWS aliases
them internally — but different parts of the AWS SDK and AWS-internal
callers use different prefixes
, sometimes within the same API call.
ARNs created before the rename keep the legacy arn:aws:codestar-
connections:...
prefix; ARNs created after the rename keep it too,
because changing them would break thousands of existing integrations.

Granting both prefixes for every action you care about costs you
nothing and saves a future debugging session if AWS quietly shifts
its internal caller from one prefix to the other.

A note on diagnosis

When this error fires and your IAM simulator says the calling user
has the permissions, the simulator is testing the wrong principal.
The denial is on the service role, not the API caller. CloudTrail's
event for the failed UpdateProject shows the actual denied principal
in userIdentity, and sometimes references the target principal by
ARN in the error metadata. That's the surest way to ground-truth
what's failing.

aws iam simulate-principal-policy is still useful — just point it
at the service role's ARN, not your own:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::ACCOUNT:role/your-codebuild-service-role \
  --action-names \
    codestar-connections:UseConnection \
    codestar-connections:GetConnectionToken \
    codestar-connections:GetConnection \
    codeconnections:ListConnections \
    codeconnections:ListInstallationTargets \
  --resource-arns CONNECTION_ARN
Enter fullscreen mode Exit fullscreen mode

The list actions will report implicitDeny against an ARN-scoped
policy even when the resource-level ones are allowed. That's the
giveaway.

A note on IAM eventual consistency

One more wrinkle: even with a correct policy, terraform apply
(or whatever provisioning tool you use) sometimes fails on the first
apply and succeeds on the second, with zero changes between them.

That's IAM eventual consistency. When the policy and the
UpdateProject call land in the same plan, the policy attachment
may not have propagated by the time the API call is made. AWS
returns the same misleading error, because at the moment of the
check, the service role genuinely doesn't have the permission yet.

If you're seeing this race repeatedly, the simplest workaround is
to put the IAM resources in a separate stack/workspace from the
CodeBuild project itself, so the policy is fully applied before
the project resource is touched. A CDK project that takes exactly
this approach is [linked at the bottom of this post]; their stack
comment explicitly calls out the eventual-consistency hazard.

Summary

If you're stuck on OAuthProviderException: User is not authorized
to access connection
and your IAM looks correct:

  1. You're checking the wrong principal. It's the CodeBuild service role, not the caller.
  2. Add GetConnectionToken to the service role's grants — it's required but undocumented.
  3. Split list actions out to resources: ["*"]. They don't accept ARN scoping and will silently fail otherwise.
  4. Grant both codestar-connections:* and codeconnections:* action prefixes. The rename in 2024 left both still in use.
  5. If applies are flaky, separate the IAM policy from the CodeBuild project so policy attachments fully propagate first.

References worth bookmarking:

  • AWS docs: Permissions and examples for AWS CodeConnections
  • AWS docs: Troubleshooting connections
  • The Terraform AWS Provider GitHub issue where GetConnectionToken was first surfaced publicly (search the provider issues for OAuthProviderException)
  • fourTheorem's codebuild-gha-runners CDK repo (the connection- stack.ts file) for a working reference implementation that uses the wildcard-resource approach and addresses eventual consistency

Top comments (0)