What is an IAM Identity Provider?
An IAM Identity Provider is a configuration in AWS that tells IAM “trust this external identity system.” It lets users authenticated by external systems (Google, Okta, GitHub Actions, etc.) get temporary AWS credentials without creating IAM users.
Key Concepts
| Concept | What it is |
|---|---|
| Identity Provider (IdP) | External system that authenticates users (Google, Okta, Azure AD, etc.) |
| Service Provider (SP) | System that trusts the IdP and provides resources (AWS in this case) |
| Federation | Linking identities across different systems—user logs in once, accesses multiple systems |
| Trust Relationship | AWS saying “I believe what this IdP tells me about users” |
| OIDC Provider | IAM entity for trusting OpenID Connect-based IdPs |
| SAML Provider | IAM entity for trusting SAML 2.0-based IdPs |
OIDC vs SAML
- OIDC (OpenID Connect): Modern, JSON/REST-based protocol—used by web apps, mobile apps, and programmatic access (GitHub Actions, EKS pods, Cognito)
- SAML 2.0: XML-based enterprise protocol—used for browser-based SSO to AWS Console (Okta, Azure AD, corporate SSO)
Trust Anchor
When you create an IAM Identity Provider, you get:
- An ARN like
arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com - A trust anchor that IAM roles can reference in their trust policies
What is a Trust Anchor?
Every IAM role has two parts:
- Permission policy: What the role CAN DO (S3 access, EC2 actions, etc.)
- Trust policy: WHO CAN ASSUME this role
A trust anchor is the entity you specify in the trust policy as “trusted to assume this role”—the starting point of trust that AWS checks first.
Examples of Trust Anchors
| Trust Anchor | Who can assume the role |
|---|---|
arn:aws:iam::123456789012:user/alice | IAM user Alice |
arn:aws:iam::123456789012:role/OtherRole | Another IAM role |
ec2.amazonaws.com | EC2 service (for instance profiles) |
arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com | GitHub Actions (via OIDC) |
Creating Identity Providers
OIDC Provider Configuration
| Setting | What to specify | Details |
|---|---|---|
| Provider URL | The IdP’s OIDC discovery endpoint | Must be HTTPS. AWS fetches /.well-known/openid-configuration from here |
| Audience | Who the token is intended for | The aud claim in the JWT. Often sts.amazonaws.com for AWS |
| Thumbprint | SHA-1 fingerprint of IdP’s TLS certificate | AWS uses this to verify it’s talking to the real IdP |
# Example: GitHub Actions OIDC Provider
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
SAML Provider Configuration
| Setting | What to specify | Details |
|---|---|---|
| Provider Name | A name you choose | e.g., Okta, AzureAD, CorporateIdP |
| Metadata Document | XML file from your IdP | Contains IdP’s public certificate, SSO URLs, entity ID |
The SAML Metadata XML contains:
- EntityID: Unique identifier for the IdP
- SSO URL: Where AWS redirects users to log in
- X.509 Certificate: Public key to verify SAML assertions
Trust Policy Differences
OIDC Trust Policy
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/main"
}
}
}
SAML Trust Policy
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:saml-provider/Okta"
},
"Action": "sts:AssumeRoleWithSAML",
"Condition": {
"StringEquals": {
"SAML:aud": "https://signin.aws.amazon.com/saml"
}
}
}
Key Differences
| Aspect | OIDC | SAML |
|---|---|---|
| STS Action | AssumeRoleWithWebIdentity | AssumeRoleWithSAML |
| Token Format | JWT (JSON Web Token) | SAML Assertion (XML) |
| Condition Prefix | <provider-url>:claim | SAML:attribute |
| Audience Value | sts.amazonaws.com | https://signin.aws.amazon.com/saml |
AssumeRoleWithWebIdentity: Internal Flow
What happens inside AWS when a JWT is sent:
Step-by-Step Process
- Receive request: STS receives JWT + RoleArn
- Parse JWT: Extract issuer (
iss) from token payload (without verifying yet) - Lookup OIDC provider: Find IAM OIDC provider matching the issuer in the account
- Fetch JWKS: HTTP GET to
<issuer>/.well-known/jwks.jsonto get public keys - Verify signature: Use public key (matched by
kid) to verify JWT signature - Validate claims: Check
exp(not expired),iat(issued in past),aud(matches configured audience) - Evaluate trust policy: Check role’s trust policy conditions against JWT claims
- Generate credentials: Create temporary AccessKeyId, SecretAccessKey, SessionToken
- Return response: Send credentials back to caller
Failure Modes
| Step | Failure |
|---|---|
| 3 | “No OpenIDConnect provider found” |
| 5 | “Token signature invalid” |
| 6 | “Token expired” / “Invalid audience” |
| 7 | “Not authorized to perform sts:AssumeRoleWithWebIdentity” |
Security Points
- Signature verification proves authenticity: Only the real IdP has the private key
- JWKS fetched live: AWS doesn’t store IdP keys—fetches them each time (with caching)
- kid (Key ID): Allows IdP to rotate keys without breaking existing tokens
AssumeRoleWithSAML: Flow
User clicks AWS app in Okta
↓
Okta authenticates (password, MFA)
↓
Okta returns SAML Response (XML in HTML form that auto-submits)
↓
Browser POSTs to https://signin.aws.amazon.com/saml
↓
AWS validates:
- Verify XML signature using certificate from SAML metadata
- Extract role ARN from SAML attribute
- Call AssumeRoleWithSAML internally
↓
Redirect to AWS Console with session
What SAML Assertion Contains
<saml:Assertion>
<saml:Subject>
<saml:NameID>user@company.com</saml:NameID>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="https://aws.amazon.com/SAML/Attributes/Role">
<saml:AttributeValue>
arn:aws:iam::123456789012:role/OktaAdminRole,
arn:aws:iam::123456789012:saml-provider/Okta
</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="https://aws.amazon.com/SAML/Attributes/SessionDuration">
<saml:AttributeValue>3600</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
<ds:Signature>...</ds:Signature>
</saml:Assertion>
GetFederationToken API
sts:GetFederationToken creates temporary credentials for a federated user you define—without using an IAM role or external IdP.
Comparison with Other STS APIs
| API | Who calls it | Trust relationship |
|---|---|---|
AssumeRole | Anyone allowed by role’s trust policy | Role trusts the caller |
AssumeRoleWithWebIdentity | External IdP token holder | Role trusts the OIDC provider |
AssumeRoleWithSAML | SAML assertion holder | Role trusts the SAML provider |
GetFederationToken | IAM user | No role involved—caller’s permissions are the ceiling |
How It Works
aws sts get-federation-token \
--name "customer-123" \
--duration-seconds 3600 \
--policy '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/customers/123/*"
}]
}'
Permission Scoping
Federated user’s permissions = intersection of:
- The calling IAM user’s permissions
- The
Policyparameter you pass (optional)
The policy parameter can only restrict, not expand permissions.
When to Use
| Use Case | Better Choice |
|---|---|
| External IdP (Google, Okta, GitHub) | AssumeRoleWithWebIdentity / AssumeRoleWithSAML |
| Cross-account access | AssumeRole |
| Custom federation broker (your backend creates tokens for users) | GetFederationToken |
| Dynamic per-user policies (each user gets different resource access) | GetFederationToken |
Limitations
- Caller must be IAM user (not a role, not federated user)
- Cannot call certain STS APIs with resulting credentials
- Max duration: 12 hours
Configuring Audience (aud) Claim
The aud claim is set by the IdP when it issues the token.
GitHub Actions
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
audience: sts.amazonaws.com # Sets the aud claim
Cognito
The aud is automatically set to the App Client ID that was used to authenticate:
{
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXX",
"aud": "abc123clientid",
"sub": "12345678-1234-1234-1234-123456789012"
}
Control it by which App Client the user authenticates with.
AWS Side
When creating IAM OIDC Provider, specify accepted aud values:
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com # Accepted aud values
Both sides must match, or you get: "Invalid audience in token"
When to Use Which Protocol
| Use Case | Protocol |
|---|---|
| CI/CD (GitHub Actions, GitLab) | OIDC |
| Kubernetes pods (EKS) | OIDC |
| Mobile/Web apps via Cognito | OIDC |
| Enterprise SSO to Console | SAML |
| Okta/Azure AD/OneLogin | SAML |
| Custom backend token broker | GetFederationToken |