IAM User vs Role vs Policy: Who, Borrowed Identity, What Is Allowed
8 min readBytePatterns
IAM user vs role vs policy: a user has long-term keys, a role is an identity assumed for temporary credentials under a trust policy, a policy is the document.
IAM user vs role vs policy in one line: a user is an identity with long-term credentials, a role is an identity with no credentials of its own that a trusted party assumes to receive temporary ones, and a policy is the JSON document, attached to either, that says what is allowed. Two of the three are identities; the third is a document. Most confusion comes from treating a role as a kind of permission, when it is a kind of who. Facts come from the AWS documentation listed at the end, as of October 2026.
| Object | What it is | Credentials |
|---|---|---|
| IAM user | an identity for one person or application | long-term: a password, up to two access keys |
| IAM role | an identity that trusted parties assume | temporary: a session from STS, with an expiry |
| Policy | a JSON document of Allow and Deny statements | none; it is attached to a user, group, role or resource |
The problem it solves
An application on EC2 needs to read one S3 bucket. The quick fix is an IAM user with an access key pasted into a config file. Now a secret that never expires sits on a disk, in a repository, in a backup, and rotating it means redeploying. The documented alternative is an IAM role: the instance receives temporary credentials from the role, they refresh on their own, and the only long-lived thing is the role's definition in your account.
How AWS decides whether a request passes, default deny, explicit allow, explicit deny, is covered in AWS IAM policy evaluation. This article is about the three objects that evaluation reads.
The intuition
A user is a long-lived identity. It represents one person or one application, has an ARN, and can sign in with a password or call APIs with an access key. The key is a secret you store, and it stays valid until someone deactivates it. Users can be put into groups, which exist only to attach the same policies to several users at once; a group is not an identity and cannot sign a request.
A role is an identity you borrow. It also has an ARN and permissions, but no password and no access key. Something assumes it: an EC2 instance through an instance profile, a Lambda function through its execution role, a user in another account, a workforce user coming through IAM Identity Center, or a workload authenticating with an external identity provider. The assumption is an sts:AssumeRole call, and the answer is a session: an access key, a secret and a session token that expire, after one hour by default and at most after the role's configured maximum. Roles cannot be members of groups.
A role has two policies. The trust policy is attached to the role and names who may assume it; it is a resource-based policy where the resource is the role itself. The permissions policies say what a session of the role may do. Both must agree: the EC2 service must be trusted, and the policy must allow s3:GetObject, or the request fails.
A policy is only a document. It is JSON with Version and a list of Statement objects, each with Effect, Action, Resource and optionally Condition. The same document type appears in several places:
- Identity-based policies attach to users, groups and roles. A managed policy is a standalone object with its own ARN, either AWS managed or customer managed, attachable to many identities and versioned. An inline policy is embedded in one identity and dies with it.
- Resource-based policies attach to a resource: an S3 bucket policy, an SQS queue policy, a role's trust policy. They name a
Principal, because the resource needs to know who is asking. - Permissions boundaries, session policies and service control policies can only cut down what the other policies grant; none of them grants on its own.
So the three objects line up as: the user or role is who, the policy is what, and for a role the trust policy adds who may become this who.
Watch it run
The lesson animation runs the role case. The app on EC2 never holds a user's access key; it receives temporary credentials from an IAM role. A GetObject call is checked against the role's permissions policy: no explicit deny, an explicit allow, so it passes. A buggy DeleteObject finds neither, and the default deny applies. Then the policy is widened to s3:* and an explicit deny on delete is added; the deny wins. Every one of those frames is a session of a role being checked against a document. No user is involved anywhere.
Shared Responsibility & IAM
Step 1 of 10
The split first: AWS secures the hardware, network and hypervisor. What you configure — data, OS patches, who may call what — is yours.
The same interactive animation as the lesson — step through it with the controls.
The code
The two documents a role needs, both illustrative. The trust policy names who may assume the role; the permissions policy names what a session may do:
{"Version": "2012-10-17", "Statement": [{"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"}]}
{"Version": "2012-10-17", "Statement": [{"Effect": "Allow",
"Action": "s3:GetObject", "Resource": "arn:aws:s3:::photos/*"}]}
A toy model of the three objects, not of IAM. Policies are plain statement lists; a user carries a long-lived key and its attached policies; a role carries a trust list, its attached policies and a maximum session length:
from fnmatch import fnmatch
# policies: documents, attached to users or roles; nothing is allowed by default
POLICIES = {
"ReadPhotos": [{"Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::photos/*"}],
"WritePhotos": [{"Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::photos/*"}],
"NoDelete": [{"Effect": "Deny", "Action": "s3:DeleteObject", "Resource": "*"}],
}
USERS = {"dana": {"policies": ["ReadPhotos", "WritePhotos", "NoDelete"], "access_key": "AKIA...LONG-LIVED"}}
ROLES = {
"photo-reader": {"trust": ["ec2.amazonaws.com", "user/dana"], "policies": ["ReadPhotos"], "max_session": 3600},
}
def statements(principal):
"""The policy documents attached to a user or a role session."""
if isinstance(principal, dict): # a role session
names = ROLES[principal["role"]]["policies"]
else: # a user
names = USERS[principal]["policies"]
return [s for n in names for s in POLICIES[n]]
def can(principal, action, resource, now=0):
"""Toy model of the identity's check: deny wins, then an allow, else default deny."""
if isinstance(principal, dict) and now >= principal["expires"]:
return "ExpiredToken"
hits = [s["Effect"] for s in statements(principal)
if fnmatch(action, s["Action"]) and fnmatch(resource, s["Resource"])]
if "Deny" in hits:
return "explicit deny"
return "allowed" if "Allow" in hits else "implicit deny"
def assume_role(caller, role, now=0):
"""Toy model of sts:AssumeRole: the trust policy decides, the session expires."""
if caller not in ROLES[role]["trust"]:
raise PermissionError(f"{caller} is not trusted by {role}")
return {"role": role, "expires": now + ROLES[role]["max_session"], "key": "ASIA...TEMPORARY"}
The user acts with the key it always has. The role is assumed first, and what comes back is a session that works, then stops working, and that a stranger cannot obtain at all:
obj = "arn:aws:s3:::photos/cat.jpg"
print("user dana, GetObject: ", can("dana", "s3:GetObject", obj))
print("user dana, DeleteObject:", can("dana", "s3:DeleteObject", obj))
session = assume_role("ec2.amazonaws.com", "photo-reader", now=0)
print("role session, GetObject:", can(session, "s3:GetObject", obj, now=10))
print("role session, PutObject:", can(session, "s3:PutObject", obj, now=10))
print("same session an hour on:", can(session, "s3:GetObject", obj, now=3600))
try:
assume_role("user/mallory", "photo-reader")
except PermissionError as e:
print("AssumeRole:", e)
# user dana, GetObject: allowed
# user dana, DeleteObject: explicit deny
# role session, GetObject: allowed
# role session, PutObject: implicit deny
# same session an hour on: ExpiredToken
# AssumeRole: user/mallory is not trusted by photo-reader
Two things in that output are the whole article. The role session is allowed to read but not to write, because the role's attached policy is the only document that counts, not the caller's: Dana can write as herself, but a session of photo-reader assumed by Dana could not. And the session expired on its own, which the user's key never does. Real STS keys begin with ASIA and long-term user keys with AKIA, which is why the toy spells them that way.
The complexity
The cost axis is operational, not computational:
- Users cost rotation. Every access key is a secret with no expiry, so someone has to rotate it, store it and revoke it when the person leaves.
- Roles cost one more hop. Every session starts with an
AssumeRole, and code that caches credentials must refresh before the expiry. AWS SDKs do this automatically for instance profiles and execution roles. - Policies cost review. Managed policies are reused and versioned, so one change applies everywhere it is attached, for better and worse; inline policies are local and easy to lose track of.
AWS's stated best practice is to require human users to sign in through an identity provider with temporary credentials, to give workloads roles, and to avoid long-term access keys where another option exists.
Where it goes wrong
- Access keys in code. The key does not expire, and it leaks with the repository. Use a role; on EC2 that is an instance profile.
- A trust policy without the matching permissions policy. The service can assume the role and then do nothing, or the reverse: the permissions exist but nothing is allowed to assume the role.
- Expecting a role to inherit the caller's permissions. A session has exactly the role's permissions. Assuming a narrower role is how you drop privileges on purpose.
- Putting a role in a group. Groups hold users only.
- Granting through a resource-based policy and forgetting the identity side. For a principal in the same account, either side can allow; across accounts, both must.
When it shows up in interviews
As a security follow-up to any design on AWS: "how does the service authenticate to S3?" The strong answer is a role with an instance profile or execution role, a least-privilege permissions policy, and no stored keys. Certification questions turn on the vocabulary: which object holds the trust policy, whether groups can contain roles, and what makes a credential temporary.
How to say it in an interview
"An IAM user is a long-lived identity with a password or access keys; a role is an identity with no credentials of its own that a trusted principal assumes to get temporary credentials from STS; a policy is the JSON document of allow and deny statements attached to a user, group, role or resource. A role has a trust policy for who may assume it and permissions policies for what a session may do. For workloads I use roles, so nothing stores a key, and for people I use federation through Identity Center, which also ends in a role."
The role's temporary credentials and the evaluation that checks them are the Shared Responsibility and IAM lesson; the order in which deny, allow and the default are checked is in why an explicit deny beats any allow.
Frequently asked questions
What is the difference between an IAM user and an IAM role?
A user is an identity with long-term credentials, a password or access keys, meant for one person or application. A role is an identity with permissions but no credentials of its own; a trusted service, user or federated identity assumes it and receives temporary credentials that expire. Both can have policies attached.
What is the difference between a trust policy and a permissions policy?
A trust policy is attached to a role and names the principals allowed to assume it; it is a resource-based policy whose resource is the role. A permissions policy, attached to a user, group or role, says which actions on which resources are allowed or denied. A role needs both to be useful.
Can an IAM role be added to a group?
No. IAM user groups contain users only, and they exist to attach the same policies to several users. Roles get their permissions from policies attached directly to them, and groups cannot be nested either.
Sources
- IAM users — IAM User Guide
- IAM roles — IAM User Guide
- Roles terms and concepts — IAM User Guide
- Use an IAM role to grant permissions to applications running on Amazon EC2 instances — IAM User Guide
- Policies and permissions in AWS Identity and Access Management — IAM User Guide
- Choose between managed policies and inline policies — IAM User Guide
- IAM user groups — IAM User Guide
- Security best practices in IAM — IAM User Guide