How to Fix AWS Permission Denied: IAM Troubleshooting
On this page
How to Fix AWS Permission Denied: IAM Troubleshooting
Few messages stop an AWS workflow faster than AccessDenied: User is not authorized to perform this action. It shows up in the CLI, in CloudFormation stack events, in Lambda logs, and in the console as a polite red banner. The frustrating part is that the message is usually correct but rarely complete — AWS deliberately keeps denial reasons vague to avoid leaking information. This guide walks through a repeatable method for finding the actual cause and fixing it without resorting to the dangerous shortcut of attaching AdministratorAccess to everything.
Read the Error Message Carefully First
Before changing any policy, parse the error. A typical denial looks like this:
An error occurred (AccessDenied) when calling the PutObject operation:
User: arn:aws:iam::123456789012:user/deploy-bot is not authorized to
perform: s3:PutObject on resource:
"arn:aws:s3:::my-bucket/uploads/file.txt"
because no identity-based policy allows the s3:PutObject action
That single line tells you four critical facts:
- The principal:
deploy-bot— the identity making the call. - The action:
s3:PutObject— the exact API permission required. - The resource: the specific object ARN, including the
uploads/prefix. - The reason: "no identity-based policy allows" — meaning nothing granted the permission (as opposed to something explicitly denying it).
Note the difference between "no policy allows" and "an explicit deny exists." The first means you need to add a permission. The second means a Deny statement is overriding your Allow, and you must find and remove or scope that deny. These require completely different fixes, so always read which one you have.
Understand How IAM Evaluates Permissions
AWS evaluates every request through a fixed decision flow. Internalizing it eliminates most guesswork:
- Default deny — every request starts denied.
- Explicit deny — if any applicable policy has a
Deny, the request is denied. Full stop. No allow can override it. - Explicit allow — if an
Allowexists in an identity-based policy, resource-based policy, or other applicable policy, and no deny applies, the request is allowed. - Otherwise — implicit deny.
The order matters: deny always wins. The policies considered include identity-based policies (on the user/role), resource-based policies (on the S3 bucket, SQS queue, etc.), permissions boundaries, Service Control Policies (SCPs) from AWS Organizations, and session policies. A request must pass all of them. This is why a user with AdministratorAccess can still get denied — an SCP or a permissions boundary above them blocks it.
Decode the Authorization Failure Message
For many API calls, AWS returns an encoded authorization message — a long Base64 blob. Decode it to see exactly which policy type caused the denial:
aws sts decode-authorization-message \
--encoded-message "<the-long-encoded-string>" \
--query DecodedMessage --output text | jq .
The decoded JSON shows the allowed boolean, the matchedStatements (what allowed or denied it), and crucially whether the failure came from an SCP, a boundary, or a missing allow. The principal running the decode command needs the sts:DecodeAuthorizationMessage permission, which is harmless to grant broadly.
Use the IAM Policy Simulator and --dry-run
You don't have to guess whether a policy works. The IAM Policy Simulator (console or CLI) tests a principal against an action and resource without making a real call:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/deploy-bot \
--action-names s3:PutObject \
--resource-arns "arn:aws:s3:::my-bucket/uploads/file.txt"
The output reports allowed or explicitDeny and names the matched statement. The simulator also evaluates permissions boundaries and SCPs, making it the fastest way to pinpoint which layer is blocking you. Many AWS services additionally support --dry-run (notably EC2), which performs the authorization check and returns DryRunOperation on success or UnauthorizedOperation on failure without changing anything.
Common Causes and Their Fixes
Most permission-denied errors trace back to a handful of patterns.
Missing or Misspelled Action
The action in your policy must match exactly, and wildcards must be intentional. s3:PutObject does not include s3:PutObjectAcl. If your code sets an ACL while uploading, you need both. Check the service's action reference and grant every action your call chain touches.
Resource ARN Mismatch
A policy granting arn:aws:s3:::my-bucket (the bucket) does not grant access to arn:aws:s3:::my-bucket/* (the objects). Bucket-level and object-level actions need different ARNs. List operations target the bucket; get/put operations target objects. Granting both is common:
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::my-bucket"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}
A Condition Block That Doesn't Match
Conditions silently fail requests when context keys don't match. A policy that allows access only from a specific VPC, IP range, or with aws:MultiFactorAuthPresent true will deny everything that doesn't meet it. Inspect every Condition and verify your request actually satisfies it — MFA conditions in particular break CLI sessions that authenticated without an MFA token.
Service Control Policies (Organizations)
If you're in an AWS Organization, an SCP at the account or OU level can block actions regardless of IAM. SCPs are a permission ceiling, not a grant. If the simulator or decoded message points to an SCP, you'll need an Organizations administrator to adjust it — you cannot fix it from inside the member account.
Permissions Boundaries
A permissions boundary attached to a role caps what that role can do even if its identity policy allows more. The effective permission is the intersection of the identity policy and the boundary. If a boundary exists and doesn't include your action, the call is denied.
Cross-Account and Role Assumption Failures
AccessDenied on sts:AssumeRole usually means the target role's trust policy doesn't list your principal, or your principal lacks sts:AssumeRole on the target. Both sides must agree: the trust policy (resource side) names who may assume, and the identity policy (caller side) allows assuming. For cross-account S3 or KMS, remember the resource-based policy and the identity policy must both allow the action.
KMS Encryption Denials
An S3 PutObject or GetObject can fail with AccessDenied when the object is encrypted with a customer-managed KMS key. You need kms:GenerateDataKey (for writes) and kms:Decrypt (for reads) on that key, granted via both the key policy and your identity policy.
Check CloudTrail for the Real Story
When the immediate error isn't enough, CloudTrail records every API call with its full authorization context. Search Event history for the failed action, filter by your principal, and open the event JSON. The errorCode, errorMessage, and userIdentity fields confirm exactly who called what and why it failed — invaluable when the failure happens inside a service that assumes a role on your behalf, like CodeBuild or a Lambda execution role.
Apply Least Privilege, Then Iterate
The temptation under deadline pressure is to attach AdministratorAccess and move on. Resist it. Instead, grant the specific actions the error names, test, and add the next missing action when the next error appears. This iterative tightening produces a policy that is both functional and minimal. AWS IAM Access Analyzer can even generate a policy from CloudTrail history, giving you a least-privilege starting point based on what the principal actually used.
A Quick Troubleshooting Checklist
- Identify the principal, action, and resource from the error text.
- Determine whether it's "no allow" or "explicit deny."
- Run
simulate-principal-policyto find the blocking layer. - Decode any encoded authorization message.
- Verify resource ARNs (bucket vs. object, region, account ID).
- Inspect every condition key and whether MFA is required.
- Check for SCPs and permissions boundaries above the identity.
- For role assumption, confirm both trust and identity policies.
- For encrypted resources, confirm KMS key permissions.
- Review CloudTrail for the authoritative failure record.
Frequently Asked Questions
Why does my IAM user have a policy that allows the action but still gets denied?
Something with higher precedence is overriding it — most often an explicit Deny in another policy, an SCP, or a permissions boundary. Deny always beats allow, and a request must pass every policy layer, not just the identity policy.
What's the difference between AccessDenied and UnauthorizedOperation?
They mean the same thing functionally — the request was not authorized. UnauthorizedOperation is the EC2-style code (often paired with an encoded message you can decode), while AccessDenied is used by most other services. Treat both as "find the missing allow or the blocking deny."
How do I fix AccessDenied when assuming a role?
Check both sides. The target role's trust policy must name your principal in its Principal element, and your calling identity must have sts:AssumeRole permission for that role's ARN. Mismatched account IDs or external-ID conditions are common culprits.
Can a user with AdministratorAccess be denied? Yes. An SCP from AWS Organizations or a permissions boundary sits above identity policies and can block actions regardless of how permissive the identity policy is. The IAM Policy Simulator will reveal this.
Is it safe to just use a wildcard "Action": "*" to make it work?
It will usually resolve the error, but it violates least privilege and creates real security risk. Use a wildcard only to confirm that permissions are the cause, then replace it with the specific actions from the error messages.
Why does S3 access fail even though my bucket policy allows it?
Object encryption with a customer-managed KMS key is the usual reason. You need kms:Decrypt for reads and kms:GenerateDataKey for writes on that key, in addition to the S3 permissions.
Permission-denied errors feel opaque, but IAM is deterministic: every denial has a specific, discoverable cause. Read the message, simulate the policy, decode the context, and fix the exact gap — that disciplined loop beats guesswork and keeps your permissions tight.