Skip to main content

AWS - Secretless Authentication

Configure AWS to accept secretless authentication from external services using OpenID Connect (OIDC), enabling workflows without storing long-lived credentials.

Overview

OpenID Connect (OIDC) allows external services to authenticate with AWS using short-lived tokens instead of permanent access keys. AWS validates these tokens and issues temporary credentials with configurable permissions and duration.

Benefits:

  • No static AWS credentials stored in external systems
  • Automatic credential expiration (15 minutes to 12 hours)
  • Fine-grained access control via trust policies
  • Complete audit trail via CloudTrail
  • Centralized credential management in AWS IAM

How It Works

Flow Steps:

  1. Token Generation: External service generates a JWT with identity claims
  2. Token Validation: AWS validates the token signature against the OIDC provider's public keys
  3. Trust Policy Check: AWS evaluates trust policy conditions against token claims
  4. Credential Issuance: AWS STS returns temporary credentials (typically valid for 1 hour)

Configuration Requirements

Prerequisites

  • AWS account with IAM permissions to create OIDC providers and roles
  • OIDC provider URL from your external service
  • Understanding of required token claims (issuer, subject, audience)

Required Attributes

AttributeDescriptionExampleRequired
Provider URLOIDC issuer URLhttps://token.example.comYes
AudienceToken audience claimsts.amazonaws.comYes
ThumbprintSSL certificate thumbprintAuto-generated or manualOptional*
Role ARNIAM role to assumearn:aws:iam::123456789012:role/MyRoleYes

*AWS now auto-validates many providers; thumbprint may not be required.

Trust Policy Selectors

Available JWT claims for filtering in trust policies:

SelectorDescriptionExample ValueUse Case
{provider}:audToken audiencests.amazonaws.comValidate intended recipient
{provider}:subSubject identifiersystem:serviceaccount:prodFilter by identity
{provider}:issToken issuerhttps://token.example.comImplicit (provider URL)
Custom claimsProvider-specificorganization_id, project_pathFine-grained filtering

Note: {provider} is the hostname of your OIDC provider (e.g., token.example.com).

SDK and CLI Usage

Environment Variables

AWS SDKs automatically detect web identity credentials via:

VariableDescriptionExampleSDK Support
AWS_ROLE_ARNRole to assumearn:aws:iam::123456789012:role/MyRoleAll AWS SDKs
AWS_WEB_IDENTITY_TOKEN_FILEPath to token file/tmp/tokenAll AWS SDKs
AWS_REGIONAWS regionus-east-1All AWS SDKs
AWS_ROLE_SESSION_NAMESession identifiermy-sessionOptional

SDK Configuration

SDKs automatically use web identity when these variables are set:

# Python (boto3) - automatic
import boto3

# No explicit configuration needed
s3 = boto3.client('s3')
s3.list_buckets()
// Node.js (AWS SDK v3) - automatic
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3';

// No explicit configuration needed
const client = new S3Client({});
const response = await client.send(new ListBucketsCommand({}));

CLI Configuration

# Set environment variables
export AWS_ROLE_ARN="arn:aws:iam::123456789012:role/MyRole"
export AWS_WEB_IDENTITY_TOKEN_FILE="/tmp/token"
export AWS_REGION="us-east-1"

# Use AWS CLI normally
aws sts get-caller-identity
aws s3 ls

Or use the CLI directly:

aws sts assume-role-with-web-identity \
--role-arn arn:aws:iam::123456789012:role/MyRole \
--role-session-name my-session \
--web-identity-token file:///tmp/token \
--duration-seconds 3600

Setup Guide

Step 1: Create OIDC Identity Provider

Register your external OIDC provider with AWS.

Using AWS Console:

  1. Navigate to IAM → Identity Providers → Add Provider
  2. Select OpenID Connect
  3. Provider URL: Enter your OIDC issuer URL (e.g., https://token.example.com)
  4. Audience: Enter the audience claim (typically sts.amazonaws.com)
  5. Click Get thumbprint (AWS auto-validates)
  6. Click Add provider

Using AWS CLI:

aws iam create-open-id-connect-provider \
--url https://token.example.com \
--client-id-list sts.amazonaws.com

Using Terraform:

resource "aws_iam_openid_connect_provider" "oidc_provider" {
url = "https://token.example.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [] # AWS auto-validates most providers
}

Step 2: Create IAM Role with Trust Policy

Create a role that trusts your OIDC provider.

Basic Trust Policy:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.example.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.example.com:aud": "sts.amazonaws.com",
"token.example.com:sub": "system:serviceaccount:production:app-deployer"
}
}
}
]
}

Subject Claim Patterns:

Configure the sub condition to match your identity provider's format:

Pattern TypeExampleSecurity Level
Specific identitysystem:serviceaccount:prod:deployerHighest
Namespace scopedorganization:myorg:project:myprojectHigh
Wildcard (scoped)organization:myorg:project:*Medium
Wildcard (broad)organization:myorg:*Lower
Critical Security

Always validate both aud and sub claims. Without subject filtering, any authenticated user of your OIDC provider could assume the role.

Create Role via CLI:

# Save trust policy
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.example.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.example.com:aud": "sts.amazonaws.com",
"token.example.com:sub": "system:serviceaccount:production:deployer"
}
}
}
]
}
EOF

# Create role
aws iam create-role \
--role-name OIDCDeployRole \
--assume-role-policy-document file://trust-policy.json \
--description "Role for OIDC-based deployments" \
--max-session-duration 3600

Create Role via Terraform:

data "aws_iam_policy_document" "oidc_trust" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]

principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.oidc_provider.arn]
}

condition {
test = "StringEquals"
variable = "token.example.com:aud"
values = ["sts.amazonaws.com"]
}

condition {
test = "StringEquals"
variable = "token.example.com:sub"
values = ["system:serviceaccount:production:deployer"]
}
}
}

resource "aws_iam_role" "oidc_role" {
name = "OIDCDeployRole"
assume_role_policy = data.aws_iam_policy_document.oidc_trust.json
max_session_duration = 3600
}

Step 3: Attach Permissions Policy

Grant the role permissions to access AWS resources.

Example Permissions Policy:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-deployment-bucket",
"arn:aws:s3:::my-deployment-bucket/*"
]
}
]
}

Attach via CLI:

# Create policy file
cat > permissions-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-deployment-bucket",
"arn:aws:s3:::my-deployment-bucket/*"
]
}
]
}
EOF

# Attach inline policy
aws iam put-role-policy \
--role-name OIDCDeployRole \
--policy-name DeploymentPermissions \
--policy-document file://permissions-policy.json

Attach via Terraform:

resource "aws_iam_role_policy" "oidc_permissions" {
name = "DeploymentPermissions"
role = aws_iam_role.oidc_role.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
"arn:aws:s3:::my-deployment-bucket",
"arn:aws:s3:::my-deployment-bucket/*"
]
}
]
})
}

Verification

Test the OIDC authentication:

# Assuming you have a token file at /tmp/token
aws sts assume-role-with-web-identity \
--role-arn arn:aws:iam::123456789012:role/OIDCDeployRole \
--role-session-name test-session \
--web-identity-token file:///tmp/token

# Expected output:
# {
# "Credentials": {
# "AccessKeyId": "ASIA...",
# "SecretAccessKey": "...",
# "SessionToken": "...",
# "Expiration": "2025-01-01T12:00:00Z"
# },
# "SubjectFromWebIdentityToken": "system:serviceaccount:production:deployer",
# "AssumedRoleUser": {
# "AssumedRoleId": "AROA...:test-session",
# "Arn": "arn:aws:sts::123456789012:assumed-role/OIDCDeployRole/test-session"
# }
# }

Best Practices

Least Privilege Permissions

Grant only the minimum required permissions:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/deployments/*"
}
]
}

Avoid:

  • Wildcard actions (s3:*, *)
  • Wildcard resources (*)
  • Administrative policies (AdministratorAccess)

Scope Trust Policies Tightly

Don't do this (allows anyone from the provider):

{
"Condition": {
"StringEquals": {
"token.example.com:aud": "sts.amazonaws.com"
}
}
}

Do this (specific identity):

{
"Condition": {
"StringEquals": {
"token.example.com:aud": "sts.amazonaws.com",
"token.example.com:sub": "system:serviceaccount:prod:deployer"
}
}
}

Separate Roles by Environment

Create different roles for dev, staging, and production:

  • OIDC-Dev-Role - Scoped to development identities
  • OIDC-Staging-Role - Scoped to staging identities
  • OIDC-Prod-Role - Scoped to production with tighter permissions

Session Duration Limits

Configure appropriate maximum session durations:

Use CaseDurationSetting
Short operations15-30 minutes900-1800
Standard deployments1 hour3600 (default)
Long-running tasks2-12 hours7200-43200
aws iam update-role \
--role-name OIDCDeployRole \
--max-session-duration 3600

Enable CloudTrail Monitoring

Monitor all role assumptions and API calls:

# Query recent role assumptions
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
--max-results 10 \
--query 'Events[].{Time:EventTime,Identity:Username,Role:Resources[0].ResourceName}'

Use Resource Tags

Tag roles for easier management and cost allocation:

aws iam tag-role \
--role-name OIDCDeployRole \
--tags Key=Environment,Value=Production \
Key=ManagedBy,Value=Terraform \
Key=AuthMethod,Value=OIDC

Security Considerations

Critical Security Settings
  1. Always Validate Audience: Specify and validate the aud claim to prevent token reuse
  2. Use Specific Subject Filters: Never use wildcard-only trust policies
  3. Enable MFA for Sensitive Operations: Consider adding MFA conditions for destructive actions
  4. Rotate Provider Certificates: Monitor and update OIDC provider certificates as needed

Trust Policy Security Levels

ConfigurationSecurityWhen to Use
aud + specific sub✓✓✓ HighestProduction environments
aud + scoped sub wildcard✓✓ HighMultiple identities in same namespace
aud + broad sub wildcard⚠️ MediumDevelopment/testing only
aud only❌ InsecureNever use

Monitoring and Alerts

Set up CloudWatch alarms for:

  • Failed AssumeRoleWithWebIdentity attempts
  • Role assumptions from unexpected subjects
  • High volume of role assumptions
  • Permission denied errors

AWS Partition-Specific Configuration

Different AWS partitions require different audience values:

PartitionRegionsAudience ValueProvider ARN
Standard AWSus-east-1, eu-west-1, etc.sts.amazonaws.comarn:aws:iam::...
AWS Chinacn-north-1, cn-northwest-1sts.amazonaws.com.cnarn:aws-cn:iam::...
AWS GovCloudus-gov-west-1, us-gov-east-1sts.amazonaws-us-gov.comarn:aws-us-gov:iam::...

Troubleshooting

Issue: Token Validation Failed

Symptoms:

  • Error: "Invalid identity token"
  • Error: "Token signature verification failed"

Solutions:

  1. Verify the OIDC provider URL exactly matches the token issuer
  2. Ensure the provider is registered in AWS IAM
  3. Check that the provider's JWKS endpoint is accessible
  4. Verify the token hasn't expired

Issue: Access Denied

Symptoms:

  • Error: "User is not authorized to perform: sts:AssumeRoleWithWebIdentity"
  • Authentication succeeds but operations fail

Solutions:

  1. Check trust policy sub claim matches your token
  2. Verify aud claim matches trust policy
  3. Review role permissions policy
  4. Ensure no SCPs are blocking the action

Issue: Provider Not Found

Symptoms:

  • Error: "OIDC provider not found"
  • Error: "Invalid provider ARN"

Solutions:

# List registered providers
aws iam list-open-id-connect-providers

# Check provider details
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/token.example.com

Use AWS with secretless authentication from:

CI/CD Tools

Infrastructure as Code

Additional Resources