AccessDeniedException: User: arn:aws:sts::111122223333:assumed-role/orders-fn-role/orders-fn is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:us-east-1:111122223333:table/orders

Short answer

IAM refused the call. The message names three things — who asked, what they asked to do, and which resource — and those three are enough to fix it, because the denial is almost always a missing action or a resource ARN that does not match the policy's.

What it means

Every AWS call a Lambda function makes is signed with credentials from its execution role, and every one of those calls is evaluated against IAM before it does anything. AccessDeniedException is that evaluation returning no. It is the most mechanical error in this index, and also the most generously documented at the moment it occurs: unlike most failures, the message contains everything needed to fix it.

Three fields carry the whole diagnosis. The User: ARN is the assumed-role form — arn:aws:sts::<account>:assumed-role/<role-name>/<session> — where the session name is your function's name. That confirms which role was actually used, which matters whenever code assumes a second role for cross-account work. The action after to perform: is the IAM action name, which frequently differs from the SDK method you called. The on resource: ARN is what was touched, and comparing it against the policy is where most of these are solved.

The final clause is the part most often skipped and the one that changes the fix. "because no identity-based policy allows the action" means nothing granted the permission, and adding it will work. "with an explicit deny" — in an identity policy, a resource policy, a permissions boundary, or a service control policy — means something is actively refusing, and an explicit deny always beats an allow. In that second case, adding the permission to the role accomplishes nothing at all, and knowing which of the two you have before editing a policy saves a deploy cycle.

Two ARN mismatches account for a striking share of real occurrences. arn:aws:s3:::bucket and arn:aws:s3:::bucket/* are different resources: the first is the bucket, the second is the objects in it, and a policy granting one does not grant the other. Similarly a DynamoDB table's ARN does not cover its global secondary indexes, which need ${TableArn}/index/* listed separately. Both produce a denial whose ARN looks, at a glance, exactly like the one in the policy.

Depends

CloudWatch’s Errors metric: Whether CloudWatch’s Errors metric counts this depends on whether the error escaped your handler, which the log line alone does not settle. An AccessDeniedException that escapes the handler fails the invocation and is counted; one your code catches and turns into a 403 is not. LogStitch classified the example below from its log level rather than an extracted type, so it reports the failure as caught — check whether your own code actually handles it.

What it looks like in CloudWatch

This is the shape the failure arrives in: the lines of one invocation scattered among everything else the log group received at the same moment.

CloudWatch Logs Before

5 raw lines, in the order CloudWatch delivered them.

2026-08-29T14:26:03.118Z START RequestId: 9a17c3e5-0b48-4d61-8c29-4f7e1a5b8d30 Version: $LATEST
2026-08-29T14:26:03.122Z 9a17c3e5-0b48-4d61-8c29-4f7e1a5b8d30 INFO Order accepted order_id=ord_44718
2026-08-29T14:26:03.288Z 9a17c3e5-0b48-4d61-8c29-4f7e1a5b8d30 ERROR AccessDeniedException: User: arn:aws:sts::111122223333:assumed-role/orders-fn-role/orders-fn is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:us-east-1:111122223333:table/orders because no identity-based policy allows the dynamodb:PutItem action
2026-08-29T14:26:03.402Z END RequestId: 9a17c3e5-0b48-4d61-8c29-4f7e1a5b8d30
2026-08-29T14:26:03.402Z REPORT RequestId: 9a17c3e5-0b48-4d61-8c29-4f7e1a5b8d30 Duration: 284.11 ms Billed Duration: 285 ms Memory Size: 256 MB Max Memory Used: 91 MB Status: error

LogStitch After

The same lines, grouped into the invocation they belong to.

Error9a17c3e5-0b48-4d61-8c29-4f7e1a5b8d30dur 284msbilled 285msmem 91/256MBlogs 5
  1. 14:26:03.118PLAT
    START RequestId: 9a17c3e5-0b48-4d61-8c29-4f7e1a5b8d30 Version: $LATEST
  2. 14:26:03.122INFO
    INFO	Order accepted order_id=ord_44718
  3. 14:26:03.288ERROR
    ERROR	AccessDeniedException: User: arn:aws:sts::111122223333:assumed-role/orders-fn-role/orders-fn is not authorized to perform: dynamodb:PutItem on resource: arn:aws:dynamodb:us-east-1:111122223333:table/orders because no identity-based policy allows the dynamodb:PutItem action
  4. 14:26:03.402PLAT
    END RequestId: 9a17c3e5-0b48-4d61-8c29-4f7e1a5b8d30
  5. 14:26:03.402PLAT
    REPORT RequestId: 9a17c3e5-0b48-4d61-8c29-4f7e1a5b8d30	Duration: 284.11 ms	Billed Duration: 285 ms	Memory Size: 256 MB	Max Memory Used: 91 MB	Status: error
Error
Status
284ms
Duration
285ms
Billed
91/256MB
Memory
64%
Headroom
No
Cold start
LogLevelError
Error type

The panel on the right is generated by running the excerpt on the left through the same parser that powers the free web stitcher — it is what the tool actually produces for this input, not an illustration of it.

How to confirm it from the logs

Read the three fields the message gives you. The User: is the assumed-role ARN of your function's execution role, in the form arn:aws:sts::<account>:assumed-role/<role>/<function>. The action after to perform: is the exact IAM action name, which is often not the API call name. The on resource: ARN is what the request touched.

The tail of the message matters as much as the head. "because no identity-based policy allows the action" means the permission is simply absent. "with an explicit deny in an identity-based policy" or "in a resource-based policy" means something is actively denying it — a permissions boundary or an SCP — and adding the permission to the role will change nothing.

Causes, most likely first

1

The action is missing from the execution role

How to confirm

Compare the action in the message against the role's policies. The action name is often not what you would guess from the SDK method — writing to DynamoDB with a document client still requires dynamodb:PutItem, and an S3 upload may need s3:PutObject plus s3:PutObjectAcl.

2

The resource ARN in the policy does not cover the one being accessed

How to confirm

Compare the ARN in the message character by character against the policy's Resource. A bucket ARN and the objects inside it are two different ARNs — arn:aws:s3:::bucket does not grant access to arn:aws:s3:::bucket/*. A DynamoDB index needs its own ARN alongside the table's.

3

A resource-based policy or KMS key policy is the real blocker

How to confirm

Check whether the resource has a policy of its own. A cross-account bucket, an SQS queue, or a KMS-encrypted resource must permit your role from its side as well; the identity policy granting the action is necessary but not sufficient.

4

A permissions boundary or SCP denies it regardless of the role

How to confirm

Read the reason at the end of the message. An explicit deny cannot be overridden by adding an allow, so if the wording mentions a boundary or an organizational policy, the fix is at that level rather than on the function's role.

5

The function assumed a different role than you expect

How to confirm

Check the role name inside the User: ARN against the function's configured execution role. If the code calls sts:AssumeRole for cross-account work, the denial may concern the assumed role rather than the execution role.

Fixes

Fix 1

Grant exactly the action named, on exactly the resource named

Copy the action and the ARN out of the error message rather than guessing at them. Note that a bucket and its contents are separate ARNs, and that list operations act on the bucket while object operations act on the objects.

yamlPolicies:
  - Version: "2012-10-17"
    Statement:
      - Effect: Allow
        Action:
          - dynamodb:PutItem
          - dynamodb:GetItem
        Resource:
          - !GetAtt OrdersTable.Arn
          # An index needs its own ARN.
          - !Sub "${OrdersTable.Arn}/index/*"
Fix 2

Reproduce the denial with the policy simulator before redeploying

The simulator answers "would this role be allowed?" without a deploy cycle, and it reports which statement decided the outcome — which is the fastest way to find an explicit deny.

bashaws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::111122223333:role/orders-fn-role \
  --action-names dynamodb:PutItem \
  --resource-arns arn:aws:dynamodb:us-east-1:111122223333:table/orders
Fix 3

Check the resource's own policy for cross-account access

Where the resource lives in another account or is encrypted with a customer-managed key, both sides must allow the call. Read the resource policy as well as the role.

bashaws s3api get-bucket-policy --bucket partner-exports
aws kms get-key-policy --key-id alias/orders --policy-name default

Also seen as

The same underlying failure, worded differently by a different runtime, SDK version, or logging layer. All of these land here — there is no separate page for each phrasing.

AccessDenied: Access DeniedUser is not authorized to perform: s3:GetObjectAn error occurred (AccessDeniedException) when calling the PutItem operationbecause no identity-based policy allows the actionwith an explicit deny in an identity-based policy

Errors that show up alongside this one, or that people mistake for it.

References

LogStitch finds this automatically, across every invocation in your account.

Paste a log excerpt into the free web stitcher and see it grouped, classified, and measured in your browser — nothing is uploaded. Or run the Mac app against your own AWS profiles and get the same view over every function you own.