AWS Security Token Service (STS) & Temporary Credentials
Key concepts
AssumeRole for cross-account access
AssumeRoleWithSAML for federation
AssumeRoleWithWebIdentity for web apps
GetSessionToken for MFA-protected API calls
Temporary credentials expire automatically
The Credential That Destroys Itself
Tuesday, 9:00 a.m. Priya, the external auditor from lesson one, opens a terminal in her own AWS account and runs a single command against Trailbase. AWS hands her back three strings of text. She spends the next hour reading Trailbase's log buckets, and at 10:00 the three strings stop working everywhere, all at once. Nobody at Trailbase revoked anything. Nobody rotated a key or filed a ticket. The credential reached the end of its scheduled life and dissolved on its own.
Lesson one waved at this machinery and made a promise: temporary credentials "are issued by AWS STS, the Security Token Service, which you will meet properly in lesson 4." This is lesson 4, and the promise comes due. AWS Security Token Service (STS) is the web service that mints those self-destructing badges: temporary, limited-privilege credentials for IAM users or federated users (identities that live outside AWS, such as a corporate directory account, that AWS has been told to trust). They behave almost identically to long-term access keys, with one defining difference: they expire automatically after a duration you choose.
That one property repairs everything that made long-term keys dangerous back when Theo could delete a production bucket untraced. There is no permanent secret to distribute or embed, so nothing sits in a config file waiting to leak. A stolen temporary credential is worth minutes or hours instead of forever, which shrinks the blast radius of any compromise. And because STS will mint a badge for anyone a trust policy approves, it powers both cross-account access (Priya) and identity federation (users who never had an IAM identity at all).
Anatomy of a Temporary Credential
Every credential STS issues has three components, and all three must accompany every API call made with it:
- Access Key ID: identifies the credential
- Secret Access Key: used for signing requests
- Session Token: required for all API calls using temporary credentials
Long-term keys have only the first two. The session token is the piece people forget, and forgetting it is the classic failure you will meet again in the pitfalls below.
STS questions are almost always which-API-fits-this-scenario: AssumeRole for cross-account access, AssumeRoleWithSAML for enterprise federation through Active Directory, AssumeRoleWithWebIdentity for mobile apps and social login, GetSessionToken for adding MFA to an existing IAM user. That mapping converts directly into correct answers.
The Life of One Badge
Follow Priya's 9:00 a.m. badge from birth to death and you have the entire STS model. Her command asks STS for credentials. STS checks the trust policy on the auditor role (the document from lesson one that answers who may assume this role) and, satisfied, returns the three strings. Every request Priya makes for the next hour carries all three, AWS validates them on each call, and at expiration they simply stop being honored.

Temporary Credentials Lifecycle
Request Phase:
- A user or application calls an STS API (for example, AssumeRole)
- STS validates the request against the trust policy
- STS returns temporary credentials
Usage Phase:
- The application uses the credentials for AWS API calls
- The credentials include the session token in every request
- AWS validates the credentials on each request
Expiration Phase:
- Credentials expire automatically, so no revocation step is needed
- The application must request new credentials before expiration
- Existing credentials cannot be extended; the only move is to request new ones
Notice what is missing from that lifecycle: a cleanup step. Every badge dies of natural causes, and how quickly depends on which API minted it and how the role is configured:
Credential Duration Limits
| STS API | Default Duration | Minimum | Maximum |
|---|---|---|---|
| AssumeRole | 1 hour | 15 minutes | 12 hours* |
| AssumeRoleWithSAML | 1 hour | 15 minutes | 12 hours* |
| AssumeRoleWithWebIdentity | 1 hour | 15 minutes | 12 hours* |
| GetSessionToken | 12 hours | 15 minutes | 36 hours |
| GetFederationToken | 12 hours | 15 minutes | 36 hours |
*Maximum duration can be set per role (up to 12 hours)
Which components make up a set of temporary security credentials issued by STS?
Five Ways to Ask for a Badge
STS exposes five APIs, and each one answers the same question in a different accent: who are you, and how did you prove it? Walk through who at Trailbase would trigger each and the exam scenarios sort themselves.
AssumeRole is the workhorse and the one Priya used at 9:00. It also covers work inside a single account: when Mara, the data engineer, needs elevated permissions for a risky schema migration, she assumes a privileged role for an hour instead of carrying those permissions all day.
AssumeRole
Purpose: Assume an IAM role within your account or cross-account
Use Cases:
- Cross-account access to resources
- Privilege escalation for specific tasks
- Temporary elevated permissions
Requirements:
- Caller must have
sts:AssumeRolepermission - Role must have a trust policy allowing the caller
- Optional: MFA requirement in the trust policy
Returns: AccessKeyId, SecretAccessKey, SessionToken, Expiration
AssumeRoleWithSAML exists for companies with a corporate directory. A SAML assertion is a signed XML document from an identity provider (such as Active Directory Federation Services) vouching for who you are; this API exchanges that assertion for AWS credentials. If Trailbase grows to fifty people and Dana wires up a corporate directory, employees will sign in there and never hold IAM users at all.
AssumeRoleWithSAML
Purpose: Exchange a SAML assertion for temporary credentials
Use Cases:
- Enterprise single sign-on (SSO)
- Active Directory Federation Services (ADFS)
- Corporate identity provider integration
Requirements:
- Valid SAML 2.0 assertion from a trusted IdP
- IAM SAML identity provider configured
- Role trust policy allowing SAML federation
No IAM User Required: Federated users never need IAM accounts
AssumeRoleWithWebIdentity is how the Trailbase hiking app itself will work. Hikers sign in with Google or another OIDC provider (OpenID Connect, the web-native standard for proving identity with a signed token), and the app trades that token for AWS credentials scoped to that one hiker. In practice AWS recommends putting Amazon Cognito in front of this flow, because Cognito handles the token juggling for you.
AssumeRoleWithWebIdentity
Purpose: Exchange a web identity token for temporary credentials
Use Cases:
- Mobile applications
- Single-page web applications
- Social login (Amazon, Facebook, Google)
- OIDC-compatible identity providers
Recommended: Use Amazon Cognito instead for simplified integration
Requirements:
- Token from a web identity provider
- IAM OIDC identity provider configured
- Role trust policy allowing the IdP
GetSessionToken is different in kind: no role changes hands. Mara already has an IAM user with long-term keys, and she calls GetSessionToken with her MFA code to trade them for temporary, MFA-blessed credentials for her CLI session. Same identity, safer badge.
GetSessionToken
Purpose: Get temporary credentials for an existing IAM user
Use Cases:
- Add MFA protection to IAM user credentials
- CLI/SDK usage requiring temporary credentials
- When the user already has long-term credentials
Key Difference: Used by IAM users for their own identity rather than for role assumption
GetFederationToken is the one Trailbase will probably never call. It serves a custom identity broker: a server-side application you build yourself that authenticates users and then vends AWS credentials on their behalf. The caller decides what the federated user may do, always as a subset of its own permissions.
GetFederationToken
Purpose: Get credentials for a federated user (custom federation)
Use Cases:
- Custom identity broker implementations
- Proxy/broker services that manage federation
- Server-side applications managing user access
Note: The caller determines the federated user permissions (a subset of the caller permissions)
An enterprise wants employees to access AWS using their existing Active Directory credentials. Which STS API supports this?
Priya at 9:00, in Slow Motion
Now replay Priya's command frame by frame, because cross-account AssumeRole is the setup the exam tests most. Call Priya's account Account A, the source (111111111111) and Trailbase Account B, the target (222222222222). Access flows only when both sides agree, which takes three steps.

Step 1: Trailbase creates the role and its trust policy. Dana creates CrossAccountRole in Account B with read permissions on the logs, plus a trust policy naming Priya's account. The root principal here delegates trust to Account A as a whole, and Account A then controls which of its identities may use that trust. The MFA condition adds a second lock: STS will refuse the assumption unless Priya authenticated with MFA.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}Step 2: Priya's side grants permission to ask. Trusting the account is only half the handshake. In Account A, Priya's admin attaches a policy allowing her to call sts:AssumeRole on that specific role ARN.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::222222222222:role/CrossAccountRole"
}
]
}Step 3: Priya assumes the role. This is the 9:00 a.m. command. Note the response: the AccessKeyId begins with ASIA, the telltale prefix of temporary credentials (long-term keys begin with AKIA), and the session name she chose appears in the assumed-role ARN, which is exactly what CloudTrail will log.
# Assume the role
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/CrossAccountRole \
--role-session-name MySession \
--duration-seconds 3600
# Response includes temporary credentials:
# {
# "Credentials": {
# "AccessKeyId": "ASIA...",
# "SecretAccessKey": "...",
# "SessionToken": "...",
# "Expiration": "2024-01-15T13:00:00Z"
# },
# "AssumedRoleUser": {
# "AssumedRoleId": "AROA...:MySession",
# "Arn": "arn:aws:sts::222222222222:assumed-role/CrossAccountRole/MySession"
# }
# }
# Use the credentials
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...
# Now commands run as the assumed role
aws s3 ls s3://bucket-in-account-b/Because the trust policy demands MFA, Priya actually passes her MFA device and current code with the call:
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/SensitiveRole \
--role-session-name AdminSession \
--serial-number arn:aws:iam::111111111111:mfa/username \
--token-code 123456 \
--duration-seconds 3600The SAML flavor looks nearly identical from the CLI, except the proof of identity is the assertion document from the identity provider instead of IAM credentials:
aws sts assume-role-with-saml \
--role-arn arn:aws:iam::123456789012:role/SAMLRole \
--principal-arn arn:aws:iam::123456789012:saml-provider/ExampleProvider \
--saml-assertion file://saml-assertion.txt \
--duration-seconds 3600Which STS API should be used for cross-account access between two AWS accounts?
STS Where Nobody Types a Command
Priya asks for her badge explicitly. Most STS traffic at Trailbase happens silently, because the instance role from lesson one is STS underneath. The EC2 app servers that upload hiker photos to trailbase-photos have never held a permanent key; they fetch temporary credentials from the instance metadata service and the SDK refreshes them without any code at Trailbase noticing.
How EC2 Instance Roles Work
- The instance metadata service provides temporary credentials
- AWS SDKs automatically retrieve and refresh the credentials
- Credentials rotate automatically (typically every 6 hours)
- No credentials are stored on the instance
Endpoint: http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name
The same silent pattern repeats across the stack. When Trailbase splits into separate dev and prod accounts, developers in the dev account will assume a read-only role to inspect production logs, exactly the Priya handshake with different names. The Jenkins pipeline deploys without long-term credentials by assuming a deployment role through OIDC federation or its own instance role, so every deployment runs on fresh, short-lived keys. And when the mobile app ships, hikers will authenticate with Amazon Cognito, which exchanges their identity token for temporary credentials scoped to that one user's data in S3.
The Confused Deputy at the Door
One more Trailbase scenario, because it hides the sneakiest trap in this lesson. Suppose Dana signs up for a third-party cost-monitoring SaaS that works by assuming a role in each customer's account. An attacker who also uses that SaaS could configure it with Trailbase's role ARN instead of their own. The SaaS, acting in good faith with its legitimate access, would then read Trailbase's data on the attacker's behalf. This is the confused deputy problem: a trusted intermediary tricked into misusing its authority.
The fix is an External ID, a shared secret that the third party must present with every AssumeRole call, enforced as a condition in the trust policy. The attacker can guess the role ARN but cannot supply Trailbase's unique External ID, so the deputy stays unconfused.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::THIRD-PARTY-ACCOUNT:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-12345"
}
}
}
]
}STS Best Practices
- Use the shortest practical duration to minimize the exposure window
- Always require MFA for sensitive roles via a trust policy condition
- Use an External ID for third-party access to prevent the confused deputy problem
- Log all AssumeRole calls: CloudTrail captures every STS API call
- Use regional endpoints to reduce latency and improve availability
- Implement credential refresh so applications handle expiration gracefully
What is the purpose of an External ID in a role trust policy?
Reading the Exam Scenarios
SAA-C03 rarely says the word STS out loud. It describes a Trailbase-shaped situation and lets the vocabulary point at the API: Active Directory means SAML, social login means Cognito or web identity, two account IDs mean AssumeRole, and a third-party SaaS means External ID.
STS Exam Scenarios
| Scenario | STS API | Why |
|---|---|---|
| User in Account A needs access to S3 in Account B | AssumeRole | Cross-account access via IAM role |
| Enterprise users login via Active Directory | AssumeRoleWithSAML | SAML 2.0 federation with ADFS |
| Mobile app users authenticate with Google | AssumeRoleWithWebIdentity (or Cognito) | OIDC federation for social login |
| IAM user needs MFA-protected temporary credentials | GetSessionToken | Adds MFA to existing IAM user |
| Application on EC2 needs DynamoDB access | Automatic (Instance Role) | EC2 instance role provides credentials automatically |
| Third-party SaaS needs access to your S3 | AssumeRole with External ID | Prevents confused deputy problem |
| Lambda function needs cross-account access | AssumeRole (in code) | Lambda execution role + assume target role |
Watch for the trap where an answer choice creates an IAM user with access keys for something a role handles: EC2 to S3, Lambda cross-account, third-party access. Whenever an AWS resource or an outsider needs credentials, the role-plus-STS answer beats the permanent-key answer.
Common Pitfalls
Forgetting the Session Token
Copying only the AccessKeyId and SecretAccessKey from a temporary credential set feels complete because that is all a long-term key has. The calls then fail with an InvalidAccessKeyId error, since temporary credentials require the session token on every request. Always carry all three values, make sure AWS_SESSION_TOKEN is set alongside the other environment variables, and prefer SDKs that handle the trio automatically.
Not Handling Credential Expiration
Temporary credentials expire, by default after one hour, and there is no warning beforehand. A long-running application that treats them as permanent will suddenly start failing mid-job. Implement refresh logic, or lean on the AWS SDK credential providers and instance roles that refresh for you, and always request new credentials before the old ones lapse.
Overly Permissive Trust Policies
A trust policy with Principal star invites any AWS account to attempt the assumption, which defeats the entire point of role-based access. Name exact principal ARNs, require an External ID for third parties, require MFA for sensitive roles, and add conditions to narrow the door further.
Skipping the External ID
Granting a third party cross-account access without an External ID leaves the confused deputy door open: a malicious actor can point the third-party service at your role and ride its legitimate access into your resources, a common vulnerability in multi-tenant systems. Generate a unique, unpredictable External ID for every such relationship and validate it in the trust policy.
Quick Reference
STS API Quick Reference
| API | Use Case | Requires IAM User? |
|---|---|---|
| AssumeRole | Cross-account access, role switching | No (caller needs permission) |
| AssumeRoleWithSAML | Enterprise SSO via SAML | No |
| AssumeRoleWithWebIdentity | Mobile/web app social login | No |
| GetSessionToken | MFA for IAM user credentials | Yes |
| GetFederationToken | Custom identity broker | Yes |
Global: https://sts.amazonaws.com
US East: https://sts.us-east-1.amazonaws.com
EU West: https://sts.eu-west-1.amazonaws.com
AP Tokyo: https://sts.ap-northeast-1.amazonaws.com
Regional endpoints reduce latency - credentials work globally# Assume a role
aws sts assume-role --role-arn <role-arn> --role-session-name <name>
# Assume role with MFA
aws sts assume-role --role-arn <role-arn> --role-session-name <name> \
--serial-number <mfa-arn> --token-code <code>
# Get session token (with MFA)
aws sts get-session-token --serial-number <mfa-arn> --token-code <code>
# Get caller identity
aws sts get-caller-identity
# Decode authorization message
aws sts decode-authorization-message --encoded-message <message>