AWS IAM Explained: Users, Groups, Policies, MFA, and Best Practices
Learn AWS IAM basics: users, groups, JSON policies, MFA setup, root account security, and the least privilege principle.
When you first start with AWS, you use your root account for everything. You create EC2 instances, S3 buckets, and Lambda functions all with root credentials. Then someone tells you this is a security nightmare, and you should be using IAM.
But what exactly is IAM? How do users, groups, and policies work? And why is everyone so paranoid about the root account?
In this article, we’ll cover AWS Identity and Access Management (IAM) fundamentals: what it is, why it matters, how to create users and groups, how policies work, and security best practices including MFA and the principle of least privilege.
This is Part 1 of our AWS IAM series. In Part 2, we’ll dive deep into IAM Roles.
What Is IAM and Why It Matters
AWS IAM (Identity and Access Management) is the service that controls who can access what in your AWS account.
Think of it like building security:
- Root account: Master key that opens everything (dangerous to share)
- IAM users: Individual key cards for specific people
- IAM groups: Departments with shared permissions
- IAM policies: Rules about which doors each key card can open
Without IAM:
- Everyone uses root credentials (security disaster)
- No audit trail of who did what
- Can’t grant limited permissions
- Can’t enforce MFA
- Compliance requirements are impossible to meet
With IAM:
- Each person/service has their own credentials
- Fine-grained permission control
- Audit trail of all actions (CloudTrail)
- MFA enforcement
- Temporary credentials for services (roles)
Root Account vs IAM Users
The Root Account
When you create an AWS account, you start with a root user. This user:
- Has complete, unrestricted access to everything
- Can’t be restricted by policies
- Can close the account
- Can change billing information
- Is identified by your email address
You should almost never use the root account.
IAM Users
IAM users are identities you create within your AWS account:
- Each user has their own credentials
- Permissions are controlled by policies
- Can be audited individually
- Can be deleted without affecting the account
- Identified by username
Best practice: Create an IAM user for yourself with admin permissions, then lock away the root account.
Creating Your First IAM User
Via AWS Console
- Sign in as root (this time only!)
- Go to IAM console
- Click “Users” → “Add users”
- Enter username (e.g., “john.doe”)
- Select AWS access type:
- AWS Management Console access: For humans (creates password)
- Programmatic access: For CLI/SDK (creates access key)
- Set permissions (we’ll cover this next)
- Review and create
Via AWS CLI
First, install and configure the AWS CLI:
aws configure
# Enter your root access key (last time using root!)
# AWS Access Key ID: YOUR_ROOT_ACCESS_KEY
# AWS Secret Access Key: YOUR_ROOT_SECRET_KEY
# Default region: us-east-1
# Default output format: json
Create IAM user:
# Create user
aws iam create-user --user-name john.doe
# Create login profile (console password)
aws iam create-login-profile \
--user-name john.doe \
--password 'TempPassword123!' \
--password-reset-required
# Create access key (programmatic access)
aws iam create-access-key --user-name john.doe
Output:
{
"AccessKey": {
"UserName": "john.doe",
"AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
"Status": "Active",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"CreateDate": "2025-09-01T12:00:00Z"
}
}
Save the SecretAccessKey immediately—you can’t retrieve it later.
IAM Groups
Groups are collections of users with the same permissions. Instead of attaching policies to each user individually, you attach them to the group.
Why Use Groups?
Without groups:
- Add “S3 read” policy to alice
- Add “S3 read” policy to bob
- Add “S3 read” policy to charlie
- (Repeat for 50 developers… nightmare when you need to update permissions)
With groups:
- Create “Developers” group with “S3 read” policy
- Add alice, bob, charlie to group
- (Update one group to update everyone)
Creating Groups
Via console:
- IAM → Groups → Create New Group
- Name: “Developers”
- Attach policies (we’ll cover next)
- Add users to group
Via CLI:
# Create group
aws iam create-group --group-name Developers
# Add user to group
aws iam add-user-to-group --user-name john.doe --group-name Developers
# List groups for a user
aws iam list-groups-for-user --user-name john.doe
Typical Group Structure
Admins
├─ Full administrator access
└─ Members: DevOps team leads
Developers
├─ EC2, Lambda, S3 access
├─ No billing/IAM access
└─ Members: Engineering team
ReadOnly
├─ View-only access
└─ Members: Analysts, auditors
Billing
├─ View and manage billing
└─ Members: Finance team
Users can be in multiple groups:
- john.doe → Developers + Billing
- jane.smith → Admins
IAM Policies: How Permissions Work
Policies are JSON documents that define permissions. They answer: “Can this identity do this action on this resource?”
Policy Structure
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
Components:
- Version: Policy language version (always “2012-10-17”)
- Statement: Array of permission statements
- Effect: “Allow” or “Deny”
- Action: AWS service action(s)
- Resource: AWS resource(s) the action applies to
Multiple Actions and Resources
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::my-bucket/*",
"arn:aws:s3:::another-bucket/*"
]
}
]
}
Wildcards
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*", // All S3 actions
"Resource": "arn:aws:s3:::*" // All S3 buckets
}
]
}
Deny Overrides Allow
Explicit deny always wins:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
},
{
"Effect": "Deny",
"Action": "s3:DeleteBucket",
"Resource": "*"
}
]
}
This allows all S3 actions except DeleteBucket.
Managed Policies vs Inline Policies
AWS Managed Policies
Pre-built policies created and maintained by AWS:
- AdministratorAccess: Full access to all AWS services
- PowerUserAccess: Full access except IAM and billing
- ReadOnlyAccess: Read-only access to all services
- AmazonS3FullAccess: Full S3 access
- AmazonEC2ReadOnlyAccess: Read-only EC2 access
Attach via CLI:
aws iam attach-group-policy \
--group-name Developers \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
Pros: Easy, maintained by AWS, best practices built-in Cons: Might grant more permissions than needed
Customer Managed Policies
Custom policies you create and manage:
# Create policy from JSON file
aws iam create-policy \
--policy-name S3BucketSpecificAccess \
--policy-document file://policy.json
policy.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-specific-bucket/*"
}
]
}
Attach to group:
aws iam attach-group-policy \
--group-name Developers \
--policy-arn arn:aws:iam::123456789012:policy/S3BucketSpecificAccess
Pros: Precise control, least privilege Cons: More work to maintain
Inline Policies
Policies embedded directly in a user, group, or role (not recommended):
aws iam put-user-policy \
--user-name john.doe \
--policy-name InlineS3Access \
--policy-document file://policy.json
Cons: Hard to reuse, hard to track, no versioning When to use: One-off exceptions only
Setting Up MFA (Multi-Factor Authentication)
MFA adds a second factor (something you have) to authentication:
- Password (something you know)
- MFA device (something you have)
Even if someone steals your password, they can’t sign in without the MFA device.
MFA for Root Account (Critical!)
- Sign in as root
- Click account name → Security Credentials
- Multi-Factor Authentication → Activate MFA
- Choose device type:
- Virtual MFA (app like Google Authenticator, Authy)
- Hardware MFA (YubiKey, etc.)
- Scan QR code with authenticator app
- Enter two consecutive MFA codes
- Save recovery codes in a safe place
Never skip this step. A compromised root account = complete account takeover.
MFA for IAM Users
Via console:
- IAM → Users → [username]
- Security credentials tab
- Assigned MFA device → Manage
- Follow setup wizard
Via CLI:
# Create virtual MFA device
aws iam create-virtual-mfa-device \
--virtual-mfa-device-name john-mfa \
--outfile QRCode.png \
--bootstrap-method QRCodePNG
# Enable MFA device for user
aws iam enable-mfa-device \
--user-name john.doe \
--serial-number arn:aws:iam::123456789012:mfa/john-mfa \
--authentication-code-1 123456 \
--authentication-code-2 789012
Enforcing MFA with Policies
Require MFA for sensitive actions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:TerminateInstances",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
This allows terminating instances only if MFA is used.
Password Policies
Enforce strong passwords organization-wide:
Via console:
- IAM → Account settings
- Set password policy:
- Minimum length: 14 characters
- Require uppercase, lowercase, numbers, symbols
- Password expiration: 90 days
- Prevent password reuse
- Require admin password reset
Via CLI:
aws iam update-account-password-policy \
--minimum-password-length 14 \
--require-symbols \
--require-numbers \
--require-uppercase-characters \
--require-lowercase-characters \
--max-password-age 90 \
--password-reuse-prevention 5
The Principle of Least Privilege
Grant only the permissions necessary to perform a task—no more.
Bad Example: Too Much Access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
This is administrator access. Fine for admins, terrible for everyone else.
Good Example: Least Privilege
Developer needs to deploy Lambda functions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"lambda:CreateFunction",
"lambda:UpdateFunctionCode",
"lambda:UpdateFunctionConfiguration",
"lambda:GetFunction"
],
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:my-app-*"
},
{
"Effect": "Allow",
"Action": [
"iam:PassRole"
],
"Resource": "arn:aws:iam::123456789012:role/lambda-execution-role"
}
]
}
This grants:
- Lambda function management (only for functions starting with “my-app-”)
- Permission to assign the execution role
- Nothing else
How to Apply Least Privilege
- Start with no permissions
- Grant permissions as needed
- Use AWS Access Advisor to see unused permissions
- Regularly audit and remove unnecessary permissions
IAM Best Practices Checklist
Here’s a quick reference for IAM security:
Root Account Security
- Enable MFA on root account
- Create IAM admin user for daily use
- Delete root access keys (if they exist)
- Use root account only for tasks that require it
- Store root credentials securely (password manager)
Users and Groups
- Create individual IAM users (never share credentials)
- Use groups to assign permissions
- Require MFA for privileged users
- Rotate credentials regularly
- Deactivate/delete unused users
Permissions
- Grant least privilege
- Use AWS managed policies when appropriate
- Create customer managed policies for specific needs
- Avoid inline policies
- Regularly review permissions with Access Advisor
Password and Access Keys
- Enforce strong password policy
- Rotate access keys regularly (every 90 days)
- Never commit access keys to version control
- Use IAM roles instead of access keys when possible
Monitoring
- Enable CloudTrail for audit logging
- Set up alerts for suspicious activity
- Review IAM credential report monthly
- Monitor failed authentication attempts
Checking Your Security Posture
AWS provides tools to assess IAM security:
IAM Credential Report
Download a CSV of all users and their credentials status:
aws iam generate-credential-report
aws iam get-credential-report --output text --query 'Content' | base64 --decode > credentials.csv
Shows:
- Users with password enabled
- MFA status
- Access key age
- Last password change
- Last used date
Access Advisor
See which permissions users/groups/roles have used:
aws iam generate-service-last-accessed-details \
--arn arn:aws:iam::123456789012:user/john.doe
Remove permissions that haven’t been used in 90+ days.
Common IAM Mistakes
1. Using Root Account for Daily Tasks
Never do this. Create an admin IAM user instead.
2. Sharing Credentials
Each person should have their own IAM user.
3. Embedding Access Keys in Code
# Bad!
aws_access_key = "AKIAIOSFODNN7EXAMPLE"
aws_secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
Use IAM roles (covered in Part 2) or environment variables.
4. Overly Permissive Policies
Don’t use "Action": "*" unless absolutely necessary.
5. Not Enabling MFA
MFA prevents 99% of account compromises.
Conclusion
IAM is the foundation of AWS security. Without proper IAM configuration, everything else you build is vulnerable.
In this part, we covered:
- Root account vs IAM users
- Creating users and groups
- How IAM policies work (JSON structure)
- Managed vs inline policies
- MFA setup and enforcement
- Password policies
- The principle of least privilege
- Security best practices
In Part 2, we’ll dive deep into IAM Roles: how they work, when to use them, and how they enable secure service-to-service communication without credentials.
Stay secure, and remember: when in doubt, grant less permission. You can always add more later.