AWS IAM explained
Who can do what in an AWS account: users, roles and policies, the order in which AWS checks them, and how to find the one that said no.
Checked against the AWS documentation · 20 October 2026
On this page · 15 sections
The short answer
AWS Identity and Access Management (IAM) decides, for every request made to AWS, whether the caller may do it. Callers are principals: IAM users, and sessions of IAM roles. What they may do is written in policies, JSON documents that allow or deny actions on resources.
Three rules explain most of IAM. Everything is denied unless a policy allows it. An explicit Deny anywhere beats every Allow. And several kinds of policy can apply to one request, so the one you are looking at is not always the one that decided.
An analogy, and where it breaks
Think of an office building with key cards. A user is an employee with a permanent card. A role is a visitor card at reception: whoever is on the list (the role’s trust policy) can pick it up, and it stops working after a set time. The doors each card opens are its permissions policy. Some doors also carry their own list of who may enter (a resource-based policy, such as an S3 bucket policy). And head office can lock whole floors in every building it owns (service control policies).
Where it breaks:
- A card opens nothing by default. A new user or role can do nothing at all until a policy allows it. The one exception is the account’s root user, which has full access.
- A “no entry” sign wins. An explicit Deny in any applicable policy overrides every Allow, wherever the Allow is.
- Head office never opens doors. An SCP grants nothing. It only sets the most that users and roles in an account can be allowed.
Users, groups, roles and the root user
- The root user is the identity the account was created with, and it has full access. AWS recommends using it only for the few tasks that require it, never for everyday work, and protecting it with MFA.
- An IAM user is an identity with long-term credentials: a console password, access keys, or both. AWS now recommends IAM users only for cases federation cannot cover; people should sign in through an identity provider and get temporary credentials instead.
- An IAM user group is a set of users that share policies. A group is not an identity: it cannot sign in, and it cannot be named as a principal in a resource-based policy.
- An IAM role is an identity with permissions but no password or access keys. Someone or something assumes it and receives temporary credentials for a role session. EC2 instances, Lambda functions, CI jobs and people from other accounts all use roles. Some AWS services create service-linked roles for themselves; you can view their permissions but not edit them.
Every identity has an ARN, and the ARN is what policies name:
arn:aws:iam::111122223333:user/exampleuser # an IAM user arn:aws:iam::111122223333:role/examplerole # an IAM role arn:aws:sts::111122223333:assumed-role/examplerole/examplerolesessionname # a session of that role
Policies
A policy is a JSON document of statements. Each statement has an Effect (Allow or Deny), the Actions it covers, the Resources it covers and, optionally, a Condition. Policies attached to a resource also have a Principal: who the statement is about. The Version is the policy language version, not a date you choose: use 2012-10-17.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOneBucket",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket1/*"
},
{
"Sid": "ListThatBucket",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket1"
}
]
}Note the two resources: listing a bucket is an action on the bucket, reading an object is an action on the object, so one needs the bucket ARN and the other the ARN ending in /*. Getting that wrong is a classic implicit deny.
Policies attached to identities come in three kinds:
| Kind | Who writes it | Editable | Attached to |
|---|---|---|---|
| AWS managed policy | AWS | No | Many identities |
| Customer managed policy | You | Yes | Many identities |
| Inline policy | You | Yes | Exactly one user, group or role |
The other policy types that can affect a request:
- Resource-based policies, attached to a resource: S3 bucket policies, SQS queue policies, KMS key policies, and a role’s trust policy.
- Permissions boundaries, a managed policy set as the most a user or role can ever be allowed. Used to let teams create roles without letting them create admins.
- Service control policies (SCPs) and resource control policies (RCPs), from AWS Organizations. SCPs cap what principals in member accounts can do, including their root users; RCPs cap what can be done to resources in member accounts, from anywhere. Neither affects the management account or service-linked roles.
- Session policies, passed when a role session is created, which can only narrow the role’s permissions for that session.
Roles and trust policies
A role has two policies that answer two different questions. Its trust policy says who may assume it. Its permissions policies say what a session of the role may do. Assuming a role needs both your own permission to call sts:AssumeRole on it and a trust policy that names you. A trust policy is one of the few resource-based policies that must always allow explicitly, even within one account.
A trust policy that lets the account’s own IAM policies decide who may assume the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111122223333:root" },
"Action": "sts:AssumeRole"
}
]
}Despite the :root, that principal is not the root user. Naming an account delegates to it: any user or role in 111122223333 whose own IAM policy allows sts:AssumeRole on this role can assume it. To trust one role or user instead, put its ARN there. When you save the policy, AWS stores that ARN as the principal’s unique ID, so a user or role deleted and recreated with the same name is not trusted any more.
How AWS decides: the order of checks
For a request inside one account, AWS goes through the applicable policies in this order:
- Explicit deny. If any applicable policy of any type has a Deny that matches, the answer is Deny.
- RCPs on the resource’s account must allow it. With RCPs enabled, the default
RCPFullAWSAccessis always attached and cannot be detached, so in practice this step only fails on a Deny. - SCPs on the principal’s account must allow it. If none allows it, the answer is Deny.
- Resource-based policy. Within one account, a resource policy that names the user or the role session directly is enough on its own. One that names the role’s ARN is still limited by the role’s permissions boundary and session policy. One that names the whole account only delegates to IAM policies.
- Identity-based policies must allow it, unless the resource policy already did.
- Permissions boundary, if set, must also allow it.
- Session policy, if one was passed, must also allow it.
Across accounts, AWS evaluates the request in both: the caller’s account (identity policies and what limits them) and the resource’s account (the resource’s policy and what limits it). Both must allow. That is why a bucket policy that names a role in another account is not enough by itself.
Explore a request
Pick who is asking and what each policy says, and the explorer walks the checks above for one request: reading an object from an S3 bucket. It shows where evaluation stops and the error S3 returns. It runs entirely in your browser.
The request: s3:GetObject on one object
Allowed.
- continueAny explicit Deny?No policy has a Deny statement that applies.
- not setResource control policiesNo RCP applies to the bucket's account.
- not setService control policiesNo SCP applies: the account is not in an organization, or it is the management account, which SCPs do not affect.
- continueResource-based policyNo bucket policy allows this principal. Within one account that is fine as long as an identity-based policy does.
- continueIdentity-based policiesA policy attached to the principal allows the action.
- not setPermissions boundaryNo permissions boundary is set.
- not setSession policyNo session policy was passed when the role was assumed.
What the explorer leaves out: the root user, VPC endpoint policies, S3 ACLs and Block Public Access, conditions, and services whose resource policies must allow explicitly even in one account, such as KMS key policies and role trust policies. Each can change the outcome; the AWS pages listed under Sources cover them.
Reading an AccessDenied message
Most AccessDenied messages follow one format, which names the type of policy that denied the request with one of two phrases. “with an explicit deny in a … policy” means a Deny statement matched: find it and narrow it, because no Allow can override it. “because no … policy allows” means nothing of that type allowed the action: add or fix an Allow there.
| The message says | Where to look |
|---|---|
because no identity-based policy allows | Nothing attached to the user, its groups or the role allows the action on that resource. Add an Allow, or check the Resource and Condition of the one you think applies. |
with an explicit deny in an identity-based policy | A Deny statement in a policy attached to the user, its groups or the role. The message usually names the policy's ARN. |
because no resource-based policy allows | The bucket, queue, key or secret policy does not name you. Across accounts that is required even when your IAM policy allows it. |
with an explicit deny in a resource-based policy | A Deny in the resource's own policy, often a condition such as a required VPC endpoint, TLS or encryption. |
because no permissions boundary allows | The user or role has a permissions boundary, and the action is outside it. The boundary must allow it too. |
because no session policy allows | Whoever created the session passed a session policy that leaves the action out. Common with tools that assume roles for you. |
because no service control policy allows | An SCP in AWS Organizations, on the account or an OU above it, does not allow the action. Only an organization administrator can change it. |
with an explicit deny in a service control policy | An SCP denies it, often a Region or service guardrail. Ask whoever runs the organization. |
with an explicit deny in a resource control policy | An RCP on the resource's account denies it, for example one that blocks access from outside the organization. |
because no role trust policy allows the sts:AssumeRole action | The role's trust policy does not name you, or its conditions do not match. You never got as far as the role's permissions. |
The message names only one policy type, even when several deny the request. Fixing the one it names can simply reveal the next. And some services, and S3 requests between accounts in different organizations, still return a bare “Access Denied” with no context at all.
Try it with the AWS CLI
This creates a role that people in your own account can assume, gives it read-only access to S3 through an AWS managed policy, and uses it. Replace 111122223333 with your account ID, which the first command prints. These commands were checked against the AWS CLI’s own help (version 2.32.5); they were not run against a live account for this page.
# Who am I? Needs no permissions. aws sts get-caller-identity
# The trust policy: let this account's IAM policies decide who may assume the role.
cat > trust.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:root" }, "Action": "sts:AssumeRole" }
]
}
EOF
aws iam create-role --role-name S3ReadOnlyDemo --assume-role-policy-document file://trust.json
aws iam attach-role-policy --role-name S3ReadOnlyDemo \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess# A CLI profile that assumes the role, using your current credentials. # Add to ~/.aws/config: [profile s3-reader] role_arn = arn:aws:iam::111122223333:role/S3ReadOnlyDemo source_profile = default
aws sts get-caller-identity --profile s3-reader # now an assumed-role ARN aws s3 ls --profile s3-reader # allowed: s3:List* aws s3 mb s3://amzn-s3-demo-bucket1 --profile s3-reader # fails: the role may read, not create
Assuming the role needs your own identity to be allowed sts:AssumeRole on it. An administrator already is; anyone else needs a policy saying so. When you are done:
aws iam detach-role-policy --role-name S3ReadOnlyDemo \ --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess aws iam delete-role --role-name S3ReadOnlyDemo
Troubleshooting
Start from the message, then confirm who made the request, then test the decision without making the request again.
"AccessDenied", and you are not sure who you are
Half of all IAM debugging is discovering that the request was made by a different identity than you thought: a leftover environment variable, a default profile, an instance role.
aws sts get-caller-identity
The Arn field is who AWS thinks you are. It needs no permissions, so it works even when everything else is denied.
env | grep '^AWS_'
Credentials or AWS_PROFILE in the environment take precedence over the config file. Only an explicit --profile on the command line overrides them.
- Unset the stray variables, or pass --profile explicitly.
- Compare the ARN with the principal your policies actually name.
Your IAM policy looks right, but the request is still denied
Test the decision without making the request. The policy simulator evaluates the principal's policies, its permissions boundary and any SCPs, and says which statement decided.
aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::111122223333:role/S3ReadOnlyDemo \ --action-names s3:GetObject \ --resource-arns arn:aws:s3:::amzn-s3-demo-bucket1/object-name
EvalDecision is allowed, explicitDeny or implicitDeny; MatchedStatements shows which policy decided. It does not fetch the resource's policy for you, and it does not evaluate RCPs.
- implicitDeny: check the Resource ARN (bucket ARN for ListBucket, object ARN with /* for GetObject) and any Condition.
- explicitDeny: find the Deny it names. Only removing or narrowing it helps; another Allow never does.
"is not authorized to perform: sts:AssumeRole"
Two policies must agree: your own identity policy must allow sts:AssumeRole on the role, and the role's trust policy must name you (or your account) as a principal, with every condition met.
aws iam get-role --role-name S3ReadOnlyDemo --query Role.AssumeRolePolicyDocument
The trust policy. Is your user, role or account in Principal? A unique ID such as AROADBQP57FF2AEXAMPLE where an ARN should be means the principal named there was deleted.
- Add your principal to the trust policy, or your account if your own IAM policies should decide.
- If the user or role was deleted and recreated with the same name, edit the trust policy and put the ARN back: it pointed at the old one's unique ID.
- For GitHub Actions, the trust policy's sub condition is the usual culprit: check it with the OIDC trust policy checker linked below.
Works in your account, denied for the other account
Across accounts both sides must allow: an identity-based policy in the caller's account and the resource's policy in the owner's account. And S3 only explains the denial when both accounts are in the same organization; otherwise the message is a plain Access Denied.
aws s3api get-bucket-policy --bucket amzn-s3-demo-bucket1 --query Policy --output text
Run in the bucket owner's account. The Principal must name the caller's role, user or account.
- Name the caller in the bucket policy, and allow the action in the caller's own IAM policy.
- Encrypted with a customer managed KMS key? Reading also needs kms:Decrypt on that key, and the key policy must allow the caller. Encrypted with the AWS managed aws/s3 key? Then only the key's own account can read it.
Production practice
- Temporary credentials everywhere. People sign in through an identity provider (AWS recommends IAM Identity Center) and assume roles. Workloads on EC2, Lambda or ECS use the role AWS delivers to them. CI systems outside AWS exchange their OIDC token for role credentials (AssumeRoleWithWebIdentity) rather than storing access keys.
- Least privilege, in steps. AWS managed policies are a reasonable start; IAM Access Analyzer can then generate a narrower policy from what the role actually used.
- Remove what nobody uses. Unused users, roles, keys and permissions are attack surface. Review last-accessed data regularly.
- Guardrails above accounts. In an organization, SCPs and RCPs enforce rules no account administrator can lift, such as approved Regions or no access from outside the organization.
- Protect the root user. MFA on, no access keys, used only for the tasks that require it.
Common mistakes
- Adding an Allow to fix an explicit deny. It cannot work; the Deny has to change.
- Using the bucket ARN for object actions, or the object ARN for
s3:ListBucket. - Reading
arn:aws:iam::111122223333:rootin a trust policy as “only the root user”. It means the whole account, through its IAM policies. - Granting cross-account access in the bucket policy and forgetting the caller’s own IAM policy, or the reverse.
- Deleting and recreating a role with the same name and expecting trust and bucket policies to follow it.
- Leaving long-lived access keys in CI secrets when the CI system supports OIDC.
- Debugging the wrong identity. Run
aws sts get-caller-identityfirst, every time.
Interview questions
What is the difference between an IAM user and an IAM role?
A user has long-term credentials and belongs to one person or workload. A role has no credentials of its own: it is assumed, and the session gets temporary credentials. Roles are the default for workloads, cross-account access and federated people.
A policy allows an action and another denies it. What happens?
It is denied. An explicit Deny in any applicable policy overrides any Allow.
Does an SCP that allows everything give a user access?
No. SCPs grant nothing; they only cap what IAM policies in member accounts can grant. The user still needs an identity-based or resource-based policy that allows the action.
Why can a role in account A not read a bucket in account B, though B’s bucket policy names it?
Across accounts both sides must allow. The role’s own IAM policy in account A must also allow the action on that bucket.
What is a permissions boundary for?
Delegation. It caps what a user or role can ever be allowed, so you can let a team create roles for their applications without letting them create roles more powerful than the boundary.
Questions people ask
Should I use the root user for daily work if I am the only person in the account?
No. Create an identity for daily work, preferably through IAM Identity Center, and keep the root user for the tasks that require it, with MFA.
Inline or managed policies?
Managed policies, usually customer managed, because they can be reused, versioned and reviewed in one place. Inline policies make sense when a permission must never be attached to anything else.
Is there a way to test a policy without risk?
The IAM policy simulator (console, or aws iam simulate-principal-policy) evaluates policies without making the request. It includes permissions boundaries and SCPs, accepts one resource-based policy when simulating an IAM user, and does not evaluate RCPs.
Where to go next
- GitHub Actions cannot assume your role? Not authorized to perform sts:AssumeRoleWithWebIdentity, and the OIDC trust policy checker that shows which condition rejects the job.
- The network the identities live in: Amazon VPC explained.
- Bucket policies, Block Public Access and why a public object still returns 403: Amazon S3 explained.
- Instance roles and the metadata service that hands them out: Amazon EC2 explained.
- Launching an instance with a role instead of keys: AWS EC2 and VPC: Building Your First Cloud Infrastructure.
Sources
Every fact on this page comes from these AWS pages, read on 20 October 2026.
- IAM User Guide: How AWS enforcement code logic evaluates requests to allow or deny access
- IAM User Guide: Policy evaluation for requests within a single account
- IAM User Guide: Cross-account policy evaluation logic
- IAM User Guide: Permissions boundaries for IAM entities
- IAM User Guide: Policies and permissions
- IAM User Guide: Managed policies and inline policies
- IAM User Guide: AWS JSON policy elements: Principal
- IAM User Guide: IAM JSON policy elements: Version
- IAM User Guide: IAM users
- IAM User Guide: IAM user groups
- IAM User Guide: IAM roles
- IAM User Guide: Create a role to give permissions to an IAM user
- IAM User Guide: AWS account root user
- IAM User Guide: Security best practices in IAM
- IAM User Guide: Troubleshoot access denied error messages
- IAM User Guide: Test IAM policies with the IAM policy simulator
- Amazon S3 User Guide: Troubleshoot access denied (403 Forbidden) errors
- AWS Organizations User Guide: Service control policies (SCPs)
- AWS Organizations User Guide: Resource control policies (RCPs)
- AWS managed policy reference: AmazonS3ReadOnlyAccess
- AWS CLI User Guide: Use an IAM role in the AWS CLI
- AWS CLI Command Reference: sts assume-role
Did this get you to an answer?
No text box on purpose — please do not paste production logs anywhere
Something wrong or out of date? Report it