Filter concepts by levelShowing all levels.

AWS · Section 5

AWS Authentication and Federation

Level
intermediate
Read
28 min
Concepts
5

Reaching AWS as a person or a workload should never mean a standing, long-lived key. This section covers IAM Identity Center as the one federation point for workforce access, SAML and OIDC as the two standards that prove identity externally, how role assumption turns that proof into temporary credentials, how SDKs and the CLI find those credentials through the credential provider chain, and why MFA belongs on humans while workloads authenticate as roles.

This section

What is true here

  1. IAM Identity Center centralizes workforce access across multiple AWS accounts without replacing existing IAM federation.
  2. SAML (XML assertions) and OIDC (JWTs) both end the same way — exchanged for temporary AWS credentials via AssumeRoleWithSAML or AssumeRoleWithWebIdentity.
  3. Temporary role credentials (ASIA-prefixed) expire automatically, 15 minutes to 12 hours — no revocation step needed.
  4. The credential provider chain checks a fixed, ordered list of sources and stops at the first one with valid credentials.
  5. MFA (ideally phishing-resistant) protects human sign-in; a running workload should authenticate as a role, never as a person.

What you will be able to do

  • Explain what IAM Identity Center adds on top of existing IAM federation, rather than replacing it
  • Tell a SAML assertion and an OIDC JWT apart, and name the STS operation each is exchanged through
  • Distinguish an ASIA-prefixed temporary credential from an AKIA-prefixed long-lived key at a glance
  • Trace the credential provider chain to explain why the "wrong" identity got used
  • Justify why a production workload should hold an IAM role, not a person's credentials
From external identity to a working AWS session
authenticatestraded forcredentialsfound by

Identity provider

IAM Identity Center, SAML, or OIDC

Token exchanged

SAML assertion or OIDC JWT

Role assumed

temporary credentials, auto-expiring

SDK/CLI resolves it

via the credential provider chain

  • Identity provider — IAM Identity Center, SAML, or OIDC
    • leads to Token exchanged (authenticates)
  • Token exchanged — SAML assertion or OIDC JWT
    • leads to Role assumed (traded for credentials)
  • Role assumed — temporary credentials, auto-expiring
    • leads to SDK/CLI resolves it (found by)
  • SDK/CLI resolves it — via the credential provider chain

Authentication and Federation

Federation, the two token standards, role assumption, the credential provider chain, and where MFA and workload identity belong.

IAM Identity Center and federated access

coreintermediate

IAM Identity Center is one central point where workforce users sign in once and get access to multiple AWS accounts and applications, instead of each account managing its own separate set of IAM users.

Think of it as

A company badge that opens every building on campus, issued from one office, instead of a different key cut for every door in every building.

text
Identity provider (Okta, Entra ID, ...)
        │  users + groups synced

IAM Identity Center  →  permission sets  →  AWS accounts

What we're doing: See what an engineer actually does to reach an AWS account through Identity Center, versus a standalone IAM user in each account.

workflowtext
# Federated (IAM Identity Center)
1. Sign in once at the AWS access portal
2. Pick an assigned account + permission set
3. Get temporary credentials for that account

# Without federation (per-account IAM users)
5. A separate IAM user, password, and MFA device — per account
6. No single place to revoke access across all of them at once
1
One sign-in, one identity provider — the login event happens exactly once.
3
Credentials are temporary, scoped to the chosen account and permission set, not a standing IAM user.
5
Every account needs its own user and its own credential lifecycle to manage separately.
6
Offboarding someone means finding and disabling every one of those per-account users individually.

Why this works: Federation moves "who has access to what" into one place that can be audited and revoked centrally, instead of scattered per-account IAM users that each need separate lifecycle management.

Creating a new IAM user per account instead of assigning a permission set

Wrong

text
# New hire needs 3 accounts → create 3 IAM users, 3 passwords, 3 MFA setups

Better

text
# New hire needs 3 accounts → assign 1 Identity Center user to 3 permission sets

What you see: Offboarding takes longer than onboarding: nobody has a single list of every IAM user that person accumulated across every account over time.

Why: Identity Center centralizes the identity; access to more accounts is a permission-set assignment, not a new identity to separately create, secure, and later remember to remove.

One sign-in, many accounts
users +groupsfederatedaccess

Identity provider

Okta, Entra ID, or built-in

IAM Identity Center

one federation point

AWS accounts

permission sets assign access

  • Identity provider — Okta, Entra ID, or built-in
    • leads to IAM Identity Center (users + groups)
  • IAM Identity Center — one federation point
    • leads to AWS accounts (federated access)
  • AWS accounts — permission sets assign access

Remember: IAM Identity Center is one federation point for workforce access across multiple AWS accounts and applications — not a replacement requirement for working IAM federation.

See also: saml and oidc · role assumption and temporary credentials

SAML and OIDC federation

standardintermediate

SAML and OIDC are two different standards for the same job: proving who a user or workload is to AWS without AWS ever seeing a password, then exchanging that proof for temporary AWS credentials.

Think of it as

A hotel that accepts a boarding pass instead of checking your government ID directly — it trusts the airline (the identity provider) already checked, and just needs to see proof of that check.

text
SAML: IdP → SAML assertion (XML) → AssumeRoleWithSAML → temp creds
OIDC: IdP → JWT (JSON)         → AssumeRoleWithWebIdentity → temp creds

What we're doing: Compare the two token formats AWS accepts for federation, side by side.

oidc-jwt-claims.jsonjson
{
  "iss": "https://token.actions.githubusercontent.com",
  "sub": "repo:my-org/my-repo:ref:refs/heads/main",
  "exp": 1735689600,
  "aud": "sts.amazonaws.com"
}
3
"sub" is what a trust policy Condition typically matches against — here, a specific repo and branch.
4
"exp" is the token's stated expiry; IAM accepts it up to 5 minutes past this for clock skew.

Why this works: A workload never holds a long-lived AWS key at all — it presents this JWT to sts:AssumeRoleWithWebIdentity, and the trust policy's Condition on "sub" decides which repo/branch is allowed to assume the role.

Trusting an OIDC provider without constraining which subject can assume the role

Wrong

json
{ "Effect": "Allow", "Principal": { "Federated": "...github-actions-oidc" }, "Action": "sts:AssumeRoleWithWebIdentity" }

Better

json
{
  "Effect": "Allow",
  "Principal": { "Federated": "...github-actions-oidc" },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": { "StringEquals": { "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main" } }
}

What you see: Any workflow in any repository that can obtain a token from the trusted OIDC provider can assume the role — not just the one repository intended.

Why: Trusting the provider only proves the token is genuinely from that provider — it says nothing about which repository, branch, or workload the token belongs to. The Condition on the token's subject claim is what actually narrows access.

Remember: SAML and OIDC are two token formats for the same exchange — prove identity externally, then trade the proof for temporary AWS credentials via AssumeRoleWithSAML or AssumeRoleWithWebIdentity.

See also: identity center and federated access · role assumption and temporary credentials

Role assumption and short-lived credentials

coreintermediate

Assuming a role means trading proof of who you are for a temporary access key, secret key, and session token that expire on their own — no key to store, revoke, or accidentally leak permanently.

Think of it as

A hotel key card programmed to stop working at checkout time, versus a house key that works forever until someone physically takes it back.

text
sts:AssumeRole(role_arn, session_name) → { AccessKeyId, SecretAccessKey, SessionToken, Expiration }

What we're doing: See what a caller actually receives from AssumeRole, and how short its life is by comparison to an IAM user key.

assume-role-output.jsonjson
{
  "Credentials": {
    "AccessKeyId": "ASIAEXAMPLE123456789",
    "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/...",
    "SessionToken": "FQoGZXIvYXdzELr//////...",
    "Expiration": "2026-08-21T15:45:00Z"
  }
}
3
ASIA prefix — this key is temporary. A long-lived IAM user key would start with AKIA instead.
6
Expiration is set at assumption time, typically an hour out — after this, the credentials simply stop working.

Why this works: The AccessKeyId prefix alone tells you whether you are looking at a credential that needs active revocation (AKIA) or one that will disable itself (ASIA) — worth checking before assuming the worst during an incident.

Caching assumed-role credentials past their expiration

Wrong

python
creds = sts.assume_role(RoleArn=role, RoleSessionName="job")["Credentials"]
# stored once at process startup, reused for the process lifetime

Better

python
# use the SDK's built-in credential provider, which re-calls AssumeRole
# automatically before Expiration is reached
session = boto3.Session()

What you see: A long-running process starts failing with ExpiredTokenException hours after it started, with no code change to explain it.

Why: AssumeRole credentials are a snapshot with a fixed Expiration — nothing refreshes them unless something explicitly checks that timestamp and calls AssumeRole again. The SDK's built-in credential providers already do this; hand-rolled caching usually does not.

Assuming a role
Caller
STS
Target role
  1. 1. AssumeRole(role_arn)
  2. 2. checks trust policy
  3. 3. temporary credentials (ASIA...)
  4. 4. credentials expire automatically15 min – 12 hr later
  1. Caller → STS: AssumeRole(role_arn)
  2. STS → Target role: checks trust policy
  3. STS → Caller: temporary credentials (ASIA...)
  4. Caller → Caller: credentials expire automatically (15 min – 12 hr later)

Long-lived IAM user key vs. temporary role credentials

Long-lived IAM user key vs. temporary role credentials
PropertyIAM user access keyAssumed-role credentials
Key prefixAKIA...ASIA...
ExpiresNever, until manually deletedAutomatically, 15 min – 12 hr
RevocationManual — delete or deactivate the keyNot needed — it stops working on its own
Typical useRare — break-glass, legacy toolingDefault — humans and workloads alike

Together

bash
aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/deploy-role \
  --role-session-name ci-deploy

Remember: An assumed role returns temporary credentials that expire automatically (15 min–12 hr) — the ASIA prefix versus AKIA is the fastest way to tell temporary from long-lived at a glance.

See also: saml and oidc · sdk credential provider chain · users roles and federation

The SDK/CLI credential provider chain

standardintermediate

Every AWS SDK and the CLI check a fixed, ordered list of places for credentials — environment variables, config files, container metadata, EC2 instance role — and stop at the first one that has valid credentials.

Think of it as

Checking your pockets, then your bag, then the car, in a fixed order, and using whichever set of keys you find first — you do not keep searching once you have working keys.

text
code > env vars > shared config file > SSO > assume-role provider > container provider > IMDS

What we're doing: Trace which credential source actually wins when more than one is present.

terminalbash
export AWS_ACCESS_KEY_ID=AKIAEXAMPLE
export AWS_SECRET_ACCESS_KEY=examplesecret

# ~/.aws/credentials also has a [default] profile with different keys

aws sts get-caller-identity
1
Environment variables are checked before the shared credentials file in the standard chain.
6
This call resolves to the environment-variable keys, not [default] in the credentials file — even though both exist.

Why this works: When credentials look "wrong" and nothing was obviously changed, the usual cause is a higher-priority source in the chain (often a stray environment variable from an earlier `export`) silently winning over the one being edited.

Debugging AccessDenied by re-checking IAM policy before checking which credentials were actually used

Wrong

text
# "AccessDenied — let me re-read the IAM policy again for the tenth time."

Better

bash
aws sts get-caller-identity   # confirm which identity/role is actually active first

What you see: Time is spent scrutinizing a policy that was correct all along, because the command actually ran under a different identity than the one whose policy was reviewed.

Why: A stale environment variable, an unexpected named profile, or a leftover assumed-role session can all silently outrank the credentials someone assumes are active. Confirming the active identity first is faster than re-deriving policy logic from scratch.

Common credential sources, roughly in the order most SDKs check them

Common credential sources, roughly in the order most SDKs check them
SourceTypical use
Explicit in codeRare — hardcoding is discouraged
Environment variablesAWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY, CI runners
Shared config/credentials file~/.aws/credentials, local development, named profiles
SSO / IAM Identity Centeraws configure sso, workforce access
Assume role providerA profile configured to assume another role
Container credential providerECS tasks, EKS pods with IAM roles for service accounts
IMDS (instance metadata)EC2 instances with an attached instance profile role

Together

bash
export AWS_PROFILE=staging
aws sts get-caller-identity   # resolves via the chain, reports which identity was found

Remember: The credential provider chain stops at the first source with valid credentials, in a fixed order — a stray environment variable is the most common reason the "wrong" identity gets used.

See also: role assumption and temporary credentials

MFA, root protection, and workload identity

coreintermediate

Human sign-ins should require MFA (ideally a phishing-resistant kind, like a hardware security key), the root user should be locked away for emergencies only, and a running application should authenticate as its own workload identity — never as a person.

Think of it as

The building master key stays in a safe for emergencies; every employee gets their own badge; and the security system itself is wired to the building, not to any one employee's badge.

text
aws:MultiFactorAuthPresent (policy condition) — true only inside an MFA-authenticated session

What we're doing: See an IAM policy condition that requires MFA before a sensitive action, versus one that does not check at all.

require-mfa-for-deletion.jsonjson
{
  "Effect": "Allow",
  "Action": "s3:DeleteObject",
  "Resource": "arn:aws:s3:::backups/*",
  "Condition": {
    "Bool": { "aws:MultiFactorAuthPresent": "true" }
  }
}
5
Without this Condition block, the Allow above would grant delete access to anyone matching the identity, MFA or not.
6
aws:MultiFactorAuthPresent is only "true" inside a session established with a verified MFA device.

Why this works: A policy allowing an action does not mean MFA was used to get there unless a Condition explicitly checks for it — MFA on sign-in and MFA-gated actions are two different, independently configured protections.

Three identities, three different protections

Human sign-in

phishing-resistant MFA (FIDO2 key)

Root user

MFA-locked, emergency-only

Application workload

IAM role, never a person's credentials

  • Human sign-in — phishing-resistant MFA (FIDO2 key)
  • Root user — MFA-locked, emergency-only
  • Application workload — IAM role, never a person's credentials

Running a production application under a developer's personal IAM user

Wrong

text
# App server's ~/.aws/credentials holds an engineer's personal AKIA... key

Better

text
# App server assumes an IAM role scoped to exactly what the app needs
# — via an instance profile, task role, or IAM roles for service accounts

What you see: The application breaks the day that engineer's account is disabled, rotated, or offboarded — for a reason completely unrelated to the application itself.

Why: A workload's access should not be tied to any one person's employment status or personal credential lifecycle. A role scoped to the workload keeps working regardless of who joins or leaves, and its permissions can be reviewed against what the application actually does rather than what that one person happens to have.

Remember: MFA protects human sign-in (phishing-resistant where possible), root is emergency-only, and a workload authenticates as itself via a role — never by borrowing a person's credentials.

See also: role assumption and temporary credentials · iam vocabulary

Advertisement