Fix AWS STS AssumeRole AccessDenied

Package aws-cli/2.37.3
AWS Region ap-south-1 (Mumbai) for the examples; IAM and STS are global services, although the role ARN and API endpoint Region still appear in CLI output
Lab environment Workstation with AWS CLI v2 and configured credentials — AWS CLI tutorial
Install AWS CLI on Ubuntu · Install AWS CLI on Debian
Privilege IAM permissions to read and update identity policies and role trust policies on the caller and target role, or account administrator access for the lab steps
Scope Why sts:AssumeRole returns AccessDenied, how caller-side sts:AssumeRole permission and the target role trust policy work together (including same-account named principal vs account-root trust and cross-account rules), how to read the CLI error, check ExternalId and other trust conditions, and verify assumed-role credentials. Mentions permissions boundaries and organization SCPs only as extra deny layers. Does not cover credential provider selection — see Which credentials AWS CLI uses.
Related guides IAM AccessDenied despite AdministratorAccess
Which credentials AWS CLI uses
AWS CLI multiple profiles
Fix AWS CLI ExpiredToken
AWS Tutorial
Short on time?Beta Get the gist of this 11-minute read in seconds, then ask follow-up questions

sts:AssumeRole authorization involves the caller and the target role's trust policy:

  • Cross-account: the caller needs sts:AssumeRole and the target role must trust that caller
  • Same account, named principal in trust: direct trust can authorize the call without a separate identity-policy Allow
  • Any scenario: conditions such as ExternalId or MFA must also match

Because several different policy problems can produce a similar AccessDenied message, first identify the caller, then check the caller permission and role trust policy.

The walkthrough uses a caller IAM user and a target IAM role in one account. It covers:

  • Caller-side AccessDenied when the role trusts the account root but the user lacks identity permission
  • Trust-policy principal mismatch
  • ExternalId condition mismatch
  • Successful assume and get-caller-identity on temporary credentials

Quick diagnosis

Run these two commands before diving into policy JSON:

bash
aws sts get-caller-identity

Record the Arn — that is the principal STS evaluates on the request.

bash
aws iam get-role \
  --role-name glc-assumerole-lab-target \
  --query Role.AssumeRolePolicyDocument

After those commands:

  • Confirm the ARN actually making the request
  • Inspect the trust policy for that principal or its source account
  • For cross-account access, confirm the caller has an identity policy allowing sts:AssumeRole on the target role ARN

How AssumeRole authorization works

AssumeRole authorization checks whether the caller may assume the role and whether the trust policy allows that principal with any required conditions.

          Caller authorized to assume role?
          (identity policy or same-account
             direct trust permission)
                       ↓
              STS AssumeRole request
                       ↓
        Role trust policy allows caller
           and required conditions?
                       ↓
              ┌────────────┴────────────┐
             yes                       no
              │                         │
              ▼                         ▼
      Temporary role              AccessDenied
        credentials

Two policy layers apply on every call:

  • Caller authorization: an identity-based policy on the IAM user or role that runs the CLI must allow sts:AssumeRole on the target role ARN unless same-account direct trust applies (see the table in the next section)
  • Role trust: the trust policy names who may assume the role; Condition keys such as sts:ExternalId or aws:MultiFactorAuthPresent can block the call even when the principal matches
  • Cross-account: both sides are always required — trust in the target account plus caller identity policy on the role ARN

Confirm the caller identity

Before reading trust policies, confirm which principal is calling STS. A named profile or environment override can make the CLI use a different user than the one named in the trust policy.

Run get-caller-identity in the same shell where assume-role failed:

bash
aws sts get-caller-identity
output
{
    "UserId": "AIDAU7QAIUYJK66DKJOIP",
    "Account": "123456789012",
    "Arn": "arn:aws:iam::123456789012:user/glc-assumerole-lab-caller"
}

The Arn value is the principal STS evaluates. Compare it character by character to the Principal entry in the role trust policy. For cross-account access, the target role must trust the source account or a specific principal from that account. Trusting only principals from the target account does not authorize a caller from another account.


Check caller sts:AssumeRole permission

Same-account trust shapes matter

In the same AWS account, how the trust policy names the caller changes whether an identity policy is required:

Trust policy Principal Caller needs sts:AssumeRole in an identity policy?
Specific IAM user or role ARN in this account No — the trust policy alone can authorize the call
arn:aws:iam::ACCOUNT_ID:root (account principal) Yes — the caller needs an Allow for sts:AssumeRole on the role ARN
Cross-account user, role, or account Yes — always, in addition to trust

Many teams use account-root trust so only identities with an explicit sts:AssumeRole grant can switch roles. If the trust policy lists the caller by ARN instead, detaching the caller policy will not reproduce an AccessDenied from missing identity permission — check the trust policy shape first.

Reproduce caller-side AccessDenied

The example role trusts the account root. The caller user has no attached policy that allows sts:AssumeRole:

bash
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/glc-assumerole-lab-target \
  --role-session-name glc-assumerole-lab
output
aws: [ERROR]: An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::123456789012:user/glc-assumerole-lab-caller is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/glc-assumerole-lab-target

Confirm the caller has no identity policy granting the action:

bash
aws iam list-attached-user-policies --user-name glc-assumerole-lab-caller
output
--------------------------
|ListAttachedUserPolicies|
+------------------------+

The empty table matches the denial: no identity-based Allow for sts:AssumeRole.

Fix caller-side permission

Attach a policy that allows sts:AssumeRole on the role ARN (scoped to that ARN in production):

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::123456789012:role/glc-assumerole-lab-target"
    }
  ]
}

When the CLI error is unclear, simulate-principal-policy shows whether the caller has an implicit deny before you edit the trust policy:

bash
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/glc-assumerole-lab-caller \
  --action-names sts:AssumeRole \
  --resource-arns arn:aws:iam::123456789012:role/glc-assumerole-lab-target \
  --query 'EvaluationResults[0].{Action:EvalActionName,Decision:EvalDecision}'
output
{
    "Action": "sts:AssumeRole",
    "Decision": "implicitDeny"
}

In this example, the role trusts the account principal, so implicitDeny means the caller has no identity-policy Allow for sts:AssumeRole. Fix that caller permission before changing the trust policy.


Check the target trust policy

When the caller already has sts:AssumeRole, the next place to look is the role trust policy. STS returns the same AccessDenied text when the trust policy lists a different principal.

With caller permission restored, the trust policy allows only a different IAM user:

bash
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/glc-assumerole-lab-target \
  --role-session-name glc-assumerole-lab
output
aws: [ERROR]: An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::123456789012:user/glc-assumerole-lab-caller is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/glc-assumerole-lab-target

Read the trust policy document:

bash
aws iam get-role --role-name glc-assumerole-lab-target --query Role.AssumeRolePolicyDocument
output
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:user/glc-assumerole-lab-wrong"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

The caller ARN is glc-assumerole-lab-caller, but the trust policy names glc-assumerole-lab-wrong. To fix the principal:

  • Update Principal to the exact caller ARN
  • To trust an entire account, use the account principal and narrow access with appropriate conditions
  • Wildcards cannot match part of an IAM user or role ARN inside Principal
  • Do not widen trust to Principal: * unless a documented requirement explicitly calls for it

The IAM console trust relationship editor shows the same Principal and Action fields:

IAM role trust policy editor showing the Principal ARN and sts:AssumeRole action for the lab role


Check conditions, MFA, and External ID

Trust policy Condition blocks are a third common cause. They apply even when the Principal ARN matches.

ExternalId mismatch

Some cross-account roles — especially roles used by third parties — require an external ID. This happens when the role's trust policy contains a sts:ExternalId condition. In that case, the AssumeRole request must provide the matching value with --external-id. The example trust policy requires a specific external ID:

bash
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/glc-assumerole-lab-target \
  --role-session-name glc-assumerole-lab
output
aws: [ERROR]: An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::123456789012:user/glc-assumerole-lab-caller is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/glc-assumerole-lab-target

Omitting --external-id fails the condition. A wrong value fails the same way:

bash
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/glc-assumerole-lab-target \
  --role-session-name glc-assumerole-lab \
  --external-id wrong-value
output
aws: [ERROR]: An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::123456789012:user/glc-assumerole-lab-caller is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/glc-assumerole-lab-target

Pass the value that matches the trust policy StringEquals condition on sts:ExternalId.

MFA and other trust conditions

Common trust-policy conditions beyond sts:ExternalId:

  • aws:MultiFactorAuthPresent — caller must authenticate with MFA before assuming the role
  • aws:PrincipalArn — limits which principal ARN may assume the role

Those failures also surface as AccessDenied on AssumeRole. Read the full trust policy Condition block and match every key in the CLI call or session context.


Check boundary and SCP denies

These guardrails can deny sts:AssumeRole even when identity and trust policies look correct:

  • Permissions boundaries
  • Session policies
  • Organization SCPs

An identity with AdministratorAccess can still be blocked by a boundary or SCP — the same pattern as other API calls.

When identity and trust policies both Allow the operation but AssumeRole still fails, check those guardrail layers next. The walkthrough for explicit and implicit denies on administrator identities is in IAM AccessDenied despite AdministratorAccess.


Test AssumeRole again

After fixing caller permission and aligning the trust policy (principal plus conditions), call assume-role with every required flag:

bash
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/glc-assumerole-lab-target \
  --role-session-name glc-assumerole-lab \
  --external-id glc-assumerole-lab-external-id
output
{
    "Credentials": {
        "AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
        "SecretAccessKey": "EXAMPLE_SECRET_ACCESS_KEY",
        "SessionToken": "EXAMPLE_SESSION_TOKEN",
        "Expiration": "2026-09-28T05:18:29+00:00"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROAU7QAIUYJFKNJTB5MX:glc-assumerole-lab",
        "Arn": "arn:aws:sts::123456789012:assumed-role/glc-assumerole-lab-target/glc-assumerole-lab"
    },
    "PackedPolicySize": 18,
    "SessionTokenUtilization": 18,
    "SessionTokenSize": 740
}

A successful response includes:

  • Credentials with temporary access key, secret key, and session token
  • AssumedRoleUser with the assumed-role/ ARN for the session name

Both fields present means the authorization checks passed.

For repeated role use from the CLI, a profile with role_arn and source_profile can chain assume-role automatically once the underlying policies are correct.


Verify the assumed identity

Copy all three values from the Credentials block into the environment:

  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • AWS_SESSION_TOKEN (required — the access key and secret alone are not enough for temporary credentials)
bash
export AWS_ACCESS_KEY_ID='ASIAIOSFODNN7EXAMPLE'
export AWS_SECRET_ACCESS_KEY='EXAMPLE_SECRET_ACCESS_KEY'
export AWS_SESSION_TOKEN='EXAMPLE_SESSION_TOKEN'

With those variables set, confirm the active principal changed:

bash
aws sts get-caller-identity
output
{
    "UserId": "AROAU7QAIUYJFKNJTB5MX:glc-assumerole-lab",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/glc-assumerole-lab-target/glc-assumerole-lab"
}

The Arn prefix assumed-role/ and the role name in the path confirm the session is using the target role, not the original IAM user.

Temporary credentials expire. If later API calls return ExpiredToken, refresh the session — see Fix AWS CLI ExpiredToken.


Troubleshooting

Symptom Likely cause Fix
Same AccessDenied after attaching caller policy Trust policy lists wrong principal or account Update Principal in the role trust policy to the caller ARN
AccessDenied with correct principal Missing or wrong ExternalId, MFA, or other Condition Match trust policy conditions in the CLI call or caller session
Caller policy attached but still denied Trust uses account root; identity policy missing or wrong role ARN Add sts:AssumeRole on the exact role ARN to the caller identity
Named principal in trust; identity policy removed; call still works Same-account named-principal trust is sufficient alone Expected behavior — adjust trust to account root if identity policy should be required
Policies look correct; still denied Permissions boundary, SCP, or session policy deny Find the denying layer; see administrator AccessDenied guide
Wrong user in error message CLI using a different profile or env credentials Run get-caller-identity; fix profile or env vars

Summary

sts:AssumeRole fails when the caller is not allowed to call the API on the role ARN, when the role trust policy does not allow that principal, or when a trust condition such as ExternalId does not match. STS often uses the same AccessDenied text for all of these cases, so get-caller-identity, the caller identity policies, and get-role trust document must be read together.

In the same account, a trust policy that names a specific user or role ARN can authorize AssumeRole by itself. When the trust policy uses the account root principal, the caller still needs an identity policy with sts:AssumeRole on the role. Cross-account access always requires both sides.

After both gates allow the request, assume-role returns temporary credentials and get-caller-identity shows an assumed-role/ ARN. If API calls fail later with ExpiredToken, renew the session before debugging permissions again.


References


Frequently Asked Questions

Do both sides need permission for AssumeRole?

For cross-account access, yes — the caller needs sts:AssumeRole on the role ARN and the role trust policy must allow that principal. In the same account, a trust policy that names a specific IAM user or role ARN can be enough by itself. When the trust policy uses the account root principal, the caller still needs an identity policy that allows sts:AssumeRole.

What is the role trust policy?

The trust policy is the resource-based policy on an IAM role. It defines which principals may call sts:AssumeRole on that role and can add conditions such as sts:ExternalId or MFA requirements. It is separate from the permissions policy that defines what the role can do after it is assumed.

Why does AdministratorAccess not bypass trust?

AdministratorAccess is a broad Allow on the identity. It does not let a principal assume a role whose trust policy does not list them. Trust is evaluated on the role resource. An administrator can edit the trust policy, but AssumeRole still fails until the trust relationship allows the caller.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)