AWS Identity

AWS IAM Fundamentals: Users, Groups, Roles, Policies & the Evaluation Logic

Every action in AWS — launching a server, reading a file from storage, deleting a database — is an API request, and Identity and Access Management (IAM) is the gatekeeper that decides whether that request is allowed. Get IAM right and the rest of AWS is just services behind a door you control. Get it wrong and you either cannot do your job (everything says Access Denied) or, far worse, you have handed an attacker the keys to the estate. IAM is the single most important service to understand because it sits in front of all the others.

The good news is that IAM is built from a small number of pieces that fit together in a predictable way: principals that make requests, credentials that prove who they are, policies that say what they may do, and an evaluation algorithm that combines all the policies and returns a single Allow or Deny. This lesson teaches each piece from the ground up, then ties them together with worked policy JSON and the exact decision logic AWS runs on every request. IAM is also a global service — it is not tied to a Region — and it is free to use; you only pay for the resources your identities go on to consume.

By the end you will be able to read and write a policy, explain why a request was denied, choose between an IAM user and a role, and apply the least-privilege and root-account practices that every exam and every interviewer expects.

Learning objectives

By the end of this lesson you will be able to:

Prerequisites & where this fits

You need an AWS account and a basic grasp of what an AWS service is (compute such as EC2, storage such as Amazon S3). No prior security knowledge is assumed — every term is defined as it appears. This is the second lesson in the AWS Zero-to-Hero course, following AWS Cloud Fundamentals; it is the foundation that every later lesson on networking, compute, storage, and architecture quietly depends on, because each of those services is reached through an IAM-gated API. After this, the course moves on to networking with AWS VPC Networking Fundamentals.

Authentication vs authorisation: the two questions

IAM answers two separate questions on every request, and confusing them is the source of most early mistakes.

Question Plain English IAM mechanism
Authentication “Who are you, and can you prove it?” Credentials: password + MFA (console), or access keys / temporary tokens (API/CLI)
Authorisation “Now that I know who you are, what are you allowed to do?” Policies attached to identities and resources, run through the evaluation logic

A request first proves identity (authentication), then IAM evaluates policies to decide permission (authorisation). A perfectly valid set of credentials with no permissions can do nothing; a powerful policy with invalid credentials never even gets evaluated. Keep these two doors separate in your mind for the rest of the lesson.

Principals: who can make a request

A principal is the entity that makes a request to AWS. There are four kinds, and choosing the right one is the first real IAM decision you make.

Principal What it is Credentials Lifespan Use it for
Root user The account owner, created with the email address; has unrestricted access to everything including billing Email + password (+ MFA) Permanent Almost nothing — only a handful of tasks that require root
IAM user A named identity for a specific person or legacy application Password (console) and/or access keys (API) Permanent until deleted Break-glass admin, or systems that genuinely cannot use roles
IAM group A container of IAM users for attaching policies once; not a principal itself None — groups cannot log in Permanent Granting the same permissions to many users (e.g. “Developers”)
IAM role An identity with permissions but no long-term credentials, designed to be assumed temporarily Temporary security tokens from AWS STS The role is permanent; each session is short-lived Workloads (EC2, Lambda), cross-account access, federated/SSO users — the preferred principal

Two points new users miss. First, a group is not a principal — you cannot “log in as a group” and a group cannot appear in a policy’s Principal field; it is purely an administrative convenience for attaching policies to many users at once. Second, the modern best practice is to prefer roles over users: roles hand out short-lived credentials automatically and there are no access keys lying around to leak. Human access in a well-run organisation comes through IAM Identity Center (covered below), and workload access comes through roles — leaving very few reasons to create IAM users at all.

Credentials: how a principal proves identity

Authorisation is meaningless until the caller has authenticated. IAM principals use different credential types depending on how they access AWS.

Credential Used for Notes & risks
Console password Signing in to the AWS Management Console (web) Human use only; enforce a strong password policy and MFA
Access key (Access Key ID + Secret Access Key) Programmatic access via the CLI, SDKs, or API Long-lived — the biggest leak risk in AWS; avoid where a role will do
MFA (multi-factor authentication) A second factor on top of password or keys Virtual authenticator app, hardware key, or FIDO2 passkey; turn it on everywhere
Temporary security credentials Issued by AWS STS when a role is assumed or a user federates Short-lived (minutes to hours), auto-expiring — the safest option

The mental model to internalise: long-lived access keys are a liability; temporary credentials are the goal. A leaked access key works until someone notices and revokes it; a leaked temporary token expires on its own, often within the hour. This single fact is why roles are preferred and why so much of IAM design is about replacing static keys with assumed roles.

The policy document: anatomy of a permission

A policy is a JSON document that lists permissions. AWS reads these documents to decide every request, so being fluent in their structure is non-negotiable. Here is a complete, annotated policy that allows reading objects from one specific S3 bucket — but only over HTTPS:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowReadProjectBucket",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::kloudvin-project-data",
        "arn:aws:s3:::kloudvin-project-data/*"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "true"
        }
      }
    }
  ]
}

Every element does a specific job:

Element Required? What it specifies
Version Yes The policy language version; always use "2012-10-17" (the current one — not the document’s date)
Statement Yes One or more permission blocks; this is where the work happens
Sid (statement ID) Optional A human label for the statement; useful in reviews and logs
Effect Yes Either "Allow" or "Deny" — the heart of the statement
Action Yes* The API operations, in service:Operation form (e.g. s3:GetObject); wildcards like s3:* are allowed
Resource Yes* The Amazon Resource Names (ARNs) the actions apply to; "*" means all resources
Principal Resource-based only Who the statement applies to — used in resource-based policies, not identity-based ones
Condition Optional Extra tests (encryption, source IP, tags, MFA present) that must be true for the statement to apply

*In an identity-based policy you specify Action and Resource; in a resource-based policy you also specify Principal. A statement can use NotAction/NotResource/NotPrincipal as inverse forms, but lead with the positive forms while learning.

A few rules that prevent the classic beginner errors:

Identity-based vs resource-based policies

Policies attach in two places, and knowing which is which explains a great deal of IAM behaviour.

Identity-based policy Resource-based policy
Attached to A user, group, or role (the principal) A resource (an S3 bucket, SQS queue, KMS key, Lambda function)
Answers “What can this identity do?” “Who may touch this resource, and how?”
Has a Principal element? No (the identity is implied) Yes — it names who is allowed
Cross-account? Grants only within the same account Can grant access to principals in other accounts
Example A policy on the Developers role allowing dynamodb:* An S3 bucket policy allowing account B to read the bucket

The two work together. Within a single account, a principal is allowed if either an identity-based policy or a resource-based policy grants the action (and nothing denies it). Across accounts, you generally need both sides to agree: the resource policy in account B must allow the principal, and the identity policy in account A must allow the call. Resource-based policies are also the mechanism that makes cross-account access possible at all, because only they carry a Principal field that can name an external account.

The other policy types: SCPs, boundaries, and session policies

Beyond the two everyday types, three more policy types act as guardrails — they restrict, they never grant on their own. You will not write these on day one, but you must know where they sit, because they are why a request can be denied even when your identity policy clearly allows it.

Policy type Where it applies Grants or limits? Purpose
Service Control Policy (SCP) An entire account or Organizational Unit, via AWS Organizations Limits only — a ceiling on the whole account Org-wide guardrails (e.g. “no one may disable CloudTrail”, “only these Regions”)
Permissions boundary A single IAM user or role Limits only — a ceiling on that principal Safe delegation: let teams create roles that can never exceed the boundary
Session policy A single assumed-role session Limits only — narrows that session Hand out a temporary identity that is smaller than the role itself

The unifying idea: SCPs cap an account, boundaries cap a principal, session policies cap a session — none of them grant anything. Effective permissions are the intersection of what is granted (identity/resource policy) with every ceiling that applies. If your identity policy allows s3:* but an SCP only permits s3:GetObject, you get s3:GetObject. This is the number-one cause of the baffling “but my policy says Allow!” support ticket.

The policy evaluation logic

This is the section that separates people who use IAM from people who understand it. When a request arrives, AWS gathers every applicable policy — identity-based, resource-based, SCPs, boundaries, session policies — and runs one deterministic algorithm to produce a single decision. The core rule is short enough to memorise:

An explicit Deny always wins. Otherwise, you need an explicit Allow. With no Allow, the default is an implicit deny.

Said as a precedence ladder, highest priority first:

  1. Explicit deny — if any policy of any type has an "Effect": "Deny" that matches the request, the answer is Deny. Nothing can override this.
  2. Explicit allow — if no deny matched, AWS looks for an "Effect": "Allow" that matches. The required Allow must come from the right place (identity policy for an IAM principal; the SCP/boundary/session ceilings must also permit it where they apply).
  3. Implicit deny — if nothing explicitly allowed the request, it is denied by default. This is the safety net that makes “deny by default” real.

So the order of strength is explicit deny > explicit allow > implicit (default) deny. Walking it through with the worked policy above:

To see explicit deny in action, here is a guardrail statement you might attach as a boundary or SCP. Even if a principal also has an s3:* Allow, this denies any S3 request that is not encrypted in transit:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyInsecureS3Transport",
      "Effect": "Deny",
      "Action": "s3:*",
      "Resource": "*",
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}

Because explicit deny sits at the top of the ladder, this single statement overrides any number of permissive Allows — which is exactly why guardrails are written as denies. The practical reading: build permissions with Allows, build guardrails with Denies, and remember that a Deny anywhere is final. The diagram below traces the full request path through these layers.

AWS IAM model & policy evaluation

The diagram shows a request flowing from a principal, gathering all applicable policy types, and passing through the deny-check, then the allow-check, then the implicit-deny default — the same ladder described above.

Roles and sts:AssumeRole: temporary identity done right

A role is an identity you assume rather than log in as. It has permissions (via attached policies) but no password or access keys; instead, an authorised principal calls AWS Security Token Service (STS) to swap their own identity for the role’s temporary credentials. Every role has two halves:

Here is a trust policy that lets a specific IAM user in the same account assume the role, but only with MFA present:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAdminToAssumeWithMFA",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:user/alice"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}

When Alice calls aws sts assume-role, STS checks this trust policy, confirms MFA, and returns temporary credentials scoped to the role’s permissions for a limited time. Three closely related uses build on exactly this mechanism:

Use case How the role is assumed Why it beats access keys
Workloads on EC2 An instance profile (a wrapper that attaches a role to an EC2 instance) — the instance fetches temporary credentials automatically No keys stored on the server; credentials rotate themselves
Serverless / containers Lambda execution roles, ECS task roles — the service assumes the role on the function’s behalf Same: short-lived, no embedded secrets
Cross-account access The trust policy names another account; a principal there assumes the role One auditable doorway between accounts instead of shared keys

For cross-account roles you typically pair sts:AssumeRole with an External ID condition when a third party is involved, to prevent the “confused deputy” problem — but the foundation is always this trust-policy-plus-AssumeRole pattern. The takeaway: roles turn every workload and every cross-account hop into short-lived, auditable credentials, which is why they are the backbone of modern AWS security.

IAM Identity Center (SSO): how humans should sign in

For human access at any real scale, you do not create IAM users in each account. Instead you use IAM Identity Center (formerly AWS SSO): people sign in once with a central identity (your corporate directory or a built-in store), and Identity Center hands them a role in whichever account they need via permission sets (reusable bundles of permissions). The result is single sign-on across many accounts, central control of who has what, and — crucially — no long-lived per-account credentials for humans at all. Think of it as the managed, organisation-wide front door that turns “an IAM user in every account” into “one login, temporary roles everywhere”.

Hands-on lab

You will create a group, attach a read-only policy, add a user to it, sign in as that user to confirm the permissions, then test that the evaluation logic denies anything outside the grant. Everything here is within the AWS Free Tier — IAM itself is free.

Run these as an administrator (not the root user). Replace the account ID 111122223333 with your own where it appears.

Step 1 — Create a group and attach an AWS-managed read-only policy.

aws iam create-group --group-name ReadOnlyTeam

aws iam attach-group-policy \
  --group-name ReadOnlyTeam \
  --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess

Step 2 — Create a user, add them to the group, and give them a console password.

aws iam create-user --user-name lab-reader

aws iam add-user-to-group \
  --user-name lab-reader \
  --group-name ReadOnlyTeam

aws iam create-login-profile \
  --user-name lab-reader \
  --password 'ChangeMe-Str0ng!Pass' \
  --password-reset-required

Step 3 — Validate the permission with the IAM policy simulator (no sign-in needed). This asks IAM the authorisation question directly:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::111122223333:user/lab-reader \
  --action-names s3:ListAllMyBuckets s3:CreateBucket

Expected output (abridged): the result for s3:ListAllMyBuckets shows "EvalDecision": "allowed", while s3:CreateBucket shows "EvalDecision": "implicitDeny". That contrast is the evaluation logic: the read action is allowed by ReadOnlyAccess, and the write action — never granted — falls through to the default implicit deny.

Step 4 — (Optional) Confirm interactively. Sign in to the console as lab-reader (your account sign-in URL, IAM user, the password above). You will be able to view services but any create/delete attempt returns an explicit Access Denied — the same implicit deny, surfaced in the UI.

Step 5 — Validation checklist.

Cleanup (so nothing lingers):

aws iam delete-login-profile --user-name lab-reader
aws iam remove-user-from-group --user-name lab-reader --group-name ReadOnlyTeam
aws iam delete-user --user-name lab-reader
aws iam detach-group-policy \
  --group-name ReadOnlyTeam \
  --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
aws iam delete-group --group-name ReadOnlyTeam

Cost note: IAM users, groups, roles, and policies are free. This lab incurs no charge — the only way to spend money would be to actually launch billable resources, which we do not. There is therefore nothing cost-related to clean up beyond removing the identities above for hygiene.

Common mistakes & troubleshooting

Symptom Likely cause Fix
Access Denied despite an Allow policy An explicit Deny elsewhere (SCP, boundary, or a Deny statement) is overriding it Check for Deny statements and SCPs; remember deny always wins
S3 policy “does nothing” Resource missing the /* object form, or using only the bucket ARN Include both arn:aws:s3:::bucket and arn:aws:s3:::bucket/*
Action allowed but request still fails A Condition (e.g. aws:SecureTransport, source IP, MFA) is not satisfied Re-read the conditions; meet them or remove them while testing
“Cannot assume role” The role’s trust policy doesn’t name your principal, or MFA/External ID is required Edit the trust policy’s Principal; supply MFA/External ID as required
Cross-account call denied Only one side is configured Allow on both the resource policy (account B) and the identity policy (account A)
Group has no effect Tried to use a group as a Principal, or user not actually added Attach policies to the group and confirm membership; groups aren’t principals
Permissions wider/narrower than expected A permissions boundary is intersecting the identity policy Inspect the boundary — effective access is the intersection

Best practices

Security notes

Interview & exam questions

  1. What is the IAM policy evaluation order? Explicit Deny wins over everything; otherwise an explicit Allow is required; with no Allow, the default is an implicit deny. Order of strength: explicit deny > explicit allow > implicit deny.

  2. Difference between an IAM user and an IAM role? A user is a permanent identity with long-lived credentials (password/access keys); a role has no long-term credentials and is assumed temporarily via STS, yielding short-lived tokens. Roles are preferred for workloads, cross-account access, and federation.

  3. Identity-based vs resource-based policy? Identity-based policies attach to a principal and say what it can do (no Principal element). Resource-based policies attach to a resource, include a Principal, and can grant cross-account access. Within an account either can grant; across accounts you typically need both.

  4. Do permission boundaries or SCPs grant permissions? No. Both are ceilings that only restrict. Effective permissions are the intersection of the grant (identity/resource policy) and every applicable boundary/SCP. Neither grants anything by itself.

  5. What are the required elements of a policy statement? Effect (Allow/Deny), Action, and Resource (plus Principal for resource-based policies). Version, Sid, and Condition round it out; Version should be 2012-10-17.

  6. How does an EC2 instance get permissions without stored keys? Via an instance profile wrapping an IAM role; the instance retrieves temporary credentials automatically and they rotate on their own — no access keys on disk.

  7. What is a trust policy and how does it relate to sts:AssumeRole? A trust policy is the resource-based policy on a role that names who may assume it (the Principal) and allows sts:AssumeRole. STS checks it before issuing temporary credentials.

  8. Why is an explicit Deny used for guardrails instead of simply not granting? Because explicit Deny cannot be overridden by any Allow. “Not granting” relies on implicit deny, which a later Allow can satisfy; an explicit Deny is final regardless of other policies.

  9. A user has an identity policy allowing s3:*, but an SCP only permits s3:GetObject. What can they do? Only s3:GetObject. The SCP is a ceiling and effective permissions are the intersection of the grant and the ceiling.

  10. What is a group, and can it be a principal? A group is a container of IAM users for attaching policies once; it is not a principal — it cannot sign in or appear in a policy’s Principal field.

  11. What does IAM Identity Center solve? Central single sign-on for humans across many accounts, handing out roles via permission sets so there are no long-lived per-account user credentials.

  12. Two key root-account practices? Use the root user only for tasks that require it, and protect it with a strong password and hardware MFA; never create root access keys.

Quick check

  1. In the evaluation logic, which beats which: explicit Allow, explicit Deny, implicit deny?
  2. Which policy element appears in a resource-based policy but not an identity-based one?
  3. True or false: a permissions boundary can grant a principal new permissions.
  4. What credential type does STS issue when a role is assumed?
  5. Why must an S3 read policy usually list two ARNs for one bucket?

Answers

  1. Explicit Deny beats explicit Allow, which beats implicit deny (the default).
  2. Principal — it names who the resource policy applies to.
  3. False. Boundaries only restrict; effective access is the intersection of the identity policy and the boundary.
  4. Temporary security credentials — a short-lived access key, secret, and session token that auto-expire.
  5. Because the bucket (arn:aws:s3:::bucket, for ListBucket) and the objects (arn:aws:s3:::bucket/*, for GetObject) are separate resources.

Exercise

Design and test a least-privilege policy for a “build agent” that may only read objects from one S3 bucket and send messages to one SQS queue, all over HTTPS:

  1. Write a single identity-based policy with two Allow statements (S3 read with both ARNs; sqs:SendMessage on the queue ARN) and an aws:SecureTransport condition on each.
  2. Add one explicit Deny statement that blocks the action if the request is not encrypted in transit, and explain why the Deny is redundant-but-defensive given the conditions.
  3. Use aws iam simulate-principal-policy (or attach to a throwaway role and assume it) to confirm: the two allowed actions return allowed, and any third action (e.g. s3:DeleteObject) returns implicitDeny.
  4. Bonus: write the trust policy that lets only your CI account assume the role, gated on an External ID.

Certification mapping

Exam Objective area this supports
CLF-C02 (Cloud Practitioner) Security and compliance — the shared-responsibility model, IAM users/groups/roles, MFA, and the principle of least privilege.
SAA-C03 (Solutions Architect – Associate) Design secure architectures — choosing roles vs users, identity vs resource policies, cross-account access patterns, and applying the evaluation logic to design access.

Glossary

Next steps

Continue the course with AWS VPC Networking Fundamentals — once you control who can act, you control where traffic flows. Then go deeper on identity with:

AWSIAMSecurityPolicy EvaluationRolesLeast Privilege
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading