IAM: Users, Groups & Policies
Every request to AWS, whether it comes from the console, the CLI or your application, is checked by AWS Identity and Access Management (IAM). IAM answers two questions: who are you? (authentication) and are you allowed to do this? (authorisation). If you understand IAM well, the rest of AWS becomes much safer to use.
In this lesson you will learn the building blocks of IAM, how to read and write JSON policies, how AWS decides between allow and deny, and how to create a user, a group and a custom policy with the CLI.
Principals: who can make requests
A principal is an identity that can call AWS APIs:
- Root user: the account owner. Covered in Create an AWS Account Safely; avoid it.
- IAM user: a person or program with long-term credentials (a console password and, optionally, access keys).
- IAM group: a collection of users. Groups cannot sign in; they exist to hand out permissions.
- IAM role: an identity that is assumed for a short time and gives temporary credentials. Roles are the modern default for applications and for people in organisations; they get their own lesson.
IAM is global: a user created once works in every Region.
Policies: JSON documents of permissions
Permissions are written as policies in JSON. Here is a policy that lets someone list one bucket and read and upload files in it:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListNotesBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::priya-notes-2026"
},
{
"Sid": "ReadWriteNotes",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::priya-notes-2026/*"
}
]
}The parts of a policy:
Version: always"2012-10-17", the current policy language version. It is not today's date.Statement: a list of rules. Each has an optionalSid(a label), anEffect(AlloworDeny), one or moreActions (in the formservice:Operation) and theResourceARNs they apply to.Condition(optional): extra tests, such as the Region, the source IP address, or whether MFA was used.
Notice that the two statements use different resources. Listing is an action on the bucket (arn:aws:s3:::priya-notes-2026), while reading and writing are actions on objects (arn:aws:s3:::priya-notes-2026/*). Mixing these up is one of the most common IAM errors.
Kinds of policies
- AWS managed policies are written and updated by AWS, such as
AdministratorAccessorReadOnlyAccess. Easy to use, but often broader than you need. - Customer managed policies are written by you and can be attached to many users, groups and roles. Use these for least privilege.
- Inline policies are embedded in one identity. Use them rarely, for a strict one-to-one link.
- Resource-based policies are attached to a resource, such as an S3 bucket policy, and name a
Principalwho may access it.
Organisations add further guardrails: permissions boundaries cap what an identity's policies can grant, and service control policies (SCPs) and resource control policies (RCPs) in AWS Organizations set maximum permissions for whole accounts.
How AWS evaluates a request
AWS combines all policies that apply and follows three rules:
- By default, everything is implicitly denied.
- An
Allowin an applicable policy permits the action. - An explicit
Denyalways wins, even over anAllow.
That third rule makes deny statements powerful guardrails. This policy stops a group from launching EC2 resources anywhere except the two Indian Regions, no matter what other policies allow:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "KeepEc2InIndia",
"Effect": "Deny",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["ap-south-1", "ap-south-2"]
}
}
}
]
}Create a group, a user and a policy with the CLI
AWS recommends giving permissions to groups, not to individual users, so a new team member gets the right access by joining a group. Create an Admins group with the AWS managed AdministratorAccess policy:
aws iam create-group --group-name Admins
aws iam attach-group-policy --group-name Admins \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess{
"Group": {
"Path": "/",
"GroupName": "Admins",
"GroupId": "AGPASAMPLEGROUPID001",
"Arn": "arn:aws:iam::111122223333:group/Admins",
"CreateDate": "2026-10-30T05:12:44+00:00"
}
}Then create the user and add it to the group. attach-group-policy and add-user-to-group print nothing when they succeed.
aws iam create-user --user-name priya-admin
aws iam add-user-to-group --user-name priya-admin --group-name Admins{
"User": {
"Path": "/",
"UserName": "priya-admin",
"UserId": "AIDASAMPLEUSERID0001",
"Arn": "arn:aws:iam::111122223333:user/priya-admin",
"CreateDate": "2026-10-30T05:13:02+00:00"
}
}Set the console password and register an MFA passkey for this user in the IAM console, so the password never appears in your terminal history. IDs that start with AIDA belong to users and AGPA to groups; you will meet AROA for roles in the next lesson.
Now save the bucket policy from the start of this lesson as notes-policy.json and create it as a customer managed policy:
aws iam create-policy --policy-name NotesBucketAccess \
--policy-document file://notes-policy.json \
--query "Policy.Arn""arn:aws:iam::111122223333:policy/NotesBucketAccess"
Test a policy before you use it
The IAM policy simulator checks what a policy allows without touching real resources. Here it tests a read and a delete:
aws iam simulate-custom-policy \
--policy-input-list file://notes-policy.json \
--action-names s3:GetObject s3:DeleteObject \
--resource-arns arn:aws:s3:::priya-notes-2026/semester-3.pdf \
--query "EvaluationResults[].[EvalActionName, EvalDecision]" \
--output texts3:GetObject allowed s3:DeleteObject implicitDeny
implicitDeny means no statement allowed the action, so the default deny applied. An explicitDeny would mean a Deny statement matched.
Common mistakes
- Attaching
AdministratorAccessto everyone "to make it work". Start narrow and add only what is needed. - Using the bucket ARN for object actions (or
/*fors3:ListBucket). The action fails with Access Denied. - Attaching policies directly to many users. Use groups so access stays consistent.
- Writing
"Version": "2026-10-30". The version must be2012-10-17. - Forgetting that an explicit deny wins. If something is denied unexpectedly, search all applicable policies for a
Deny.
Key points
- IAM authenticates principals (root, users, roles) and authorises each API request with policies.
- Groups hold users and are the right place to attach permissions; IAM is global, not per Region.
- A policy statement has an
Effect,Action,Resourceand optionalCondition;Versionis always2012-10-17. - Requests are denied by default, allowed by an
Allow, and an explicitDenyalways wins. - Prefer customer managed policies for least privilege and test them with the policy simulator.
- Avoid long-term access keys; use
aws login, Identity Center or roles.
Exercise
Write a customer managed policy called MatchPhotosReadOnly that lets the cricket club's volunteers list the bucket rohtak-cricket-photos and download (but not upload or delete) any object in it. Then write the simulator command to prove that s3:PutObject is denied.
Show solution
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::rohtak-cricket-photos"
},
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::rohtak-cricket-photos/*"
}
]
}aws iam simulate-custom-policy \
--policy-input-list file://match-photos.json \
--action-names s3:GetObject s3:PutObject \
--resource-arns arn:aws:s3:::rohtak-cricket-photos/final-2026.jpg \
--query "EvaluationResults[].[EvalActionName, EvalDecision]" --output textThe simulator should report allowed for s3:GetObject and implicitDeny for s3:PutObject, because no statement allows uploads.
Quick Quiz
1. A user has a policy that allows s3:* and another that denies s3:DeleteObject. Can the user delete objects?
Explicit denies override any allows.
2. Where should you usually attach permissions for a team of developers?
Groups keep permissions consistent and easy to manage.
3. What does "Version": "2012-10-17" mean in a policy?
It is a fixed value for the current policy language, not a date you change.
4. The simulator returns implicitDeny. What does it mean?
Implicit deny is the default when nothing allows the action; explicit deny is when a Deny matches.
Learn AWS with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
