Short version: AWS Identity Center (formerly AWS SSO) stores a short-lived accessToken
in a local JSON cache after you run aws sso login
. You can exchange that token for per-account IAM temporary credentials using the sso.get_role_credentials
API and then use those credentials with boto3.Session
to run operations across multiple accounts. This post explains how the token flow works, security implications (yes — plain text cache), and gives a hands-on Python example you can adapt.
Table of contents
- How Identity Center tokens work (high level)
- Token → IAM credentials: what happens under the hood
- Security considerations
- What an attacker can do with a stolen accessToken
- Full example: running a ReadOnly check in all your SSO accounts using
AWSReadOnlyAccess
- Hardening recommendations and alternatives
1) How Identity Center tokens work (high level)
When you run aws sso login
the CLI performs an OIDC/OAuth flow and writes a small JSON token file to the SSO cache directory (usually ~/.aws/sso/cache/
). The JSON contains an accessToken
(a Bearer token) and an expiresAt
timestamp.
That accessToken
represents your authenticated Identity Center session (your user + any required MFA). It is not long-lived — the token has an expiry (1-12h, depending on your org's config).
You cannot directly use that accessToken
like AWS credentials; instead you exchange it for IAM credentials for a specific account and role via the Identity Center sso
API.
2) Token → IAM credentials: what happens under the hood
The key API is sso.get_role_credentials
(available in boto3). You call it with:
-
accountId
— the target AWS account you want access to -
roleName
— the Identity Center role name assigned in that account (for example,AWSReadOnlyAccess
). You can also get available roles fromsso.list_account_roles
. -
accessToken
— the cached token from~/.aws/sso/cache/*.json
The get_role_credentials
call returns a short-lived set of credentials: accessKeyId
, secretAccessKey
, and sessionToken
. These are standard STS-style credentials and you use them to create a boto3.Session
that will act in the target account with the permissions associated to the role.
3) Security considerations
Important: the JSON files under ~/.aws/sso/cache/
are plain text JSON. The accessToken
inside them is effectively a bearer token that can be exchanged for AWS credentials until it expires.
That means:
- Treat the
accessToken
like a password / secret bearer token. - Protect the
~/.aws/sso/cache
directory with strict file permissions (e.g.,chmod 600
). - Monitor and rotate your sessions: use the shortest practical session duration configured in your Identity Center settings.
4) What an attacker can do with a stolen accessToken
If a bad actor gains access to your SSO accessToken
file, they can effectively impersonate you in AWS until that token expires. Here’s what happens:
- The attacker can use your
accessToken
on their own computer to call AWS SSO APIs (ListAccounts
,ListAccountRoles
, andGetRoleCredentials
). - Using
GetRoleCredentials
, they can obtain temporary IAM credentials for every account and role you have access to through Identity Center. - Those temporary credentials give them the same level of access that your assigned roles do — including read or write permissions, depending on your configuration.
- Once they have those IAM keys, they can use standard AWS APIs (e.g., S3, EC2, IAM) from anywhere, even outside your corporate network.
This is why the SSO token must be treated like a highly sensitive credential — it’s effectively a universal key for your Identity Center session.
Even though the keys are short-lived, this can still result in data exfiltration, resource modification, or privilege escalation depending on your roles.
5) Full example: running a ReadOnly check in all your SSO accounts using AWSReadOnlyAccess
This example hardcodes the role name AWSReadOnlyAccess
. It lists account IDs via SSO, then requests role credentials for AWSReadOnlyAccess
in each account and performs an sts.get_caller_identity()
check.
#!/usr/bin/env python3
"""Run a ReadOnly boto3 action in all AWS SSO accounts using a fixed role name"""
import json
import glob
import os
from datetime import datetime, timezone
import boto3
from botocore.exceptions import ClientError
AWS_SSO_CACHE_DIR = os.path.expanduser("~/.aws/sso/cache")
FIXED_ROLE_NAME = "AWSReadOnlyAccess" # fixed role name
SSO_CLIENT = boto3.client("sso")
def find_latest_sso_token():
files = glob.glob(os.path.join(AWS_SSO_CACHE_DIR, "*.json"))
if not files:
raise FileNotFoundError(f"No SSO cache JSON files found in {AWS_SSO_CACHE_DIR}")
latest = max(files, key=os.path.getmtime)
with open(latest, "r") as fh:
data = json.load(fh)
token = data.get("accessToken")
expires_at = data.get("expiresAt")
exp_dt = datetime.fromisoformat(expires_at.replace('Z', '+00:00')) if expires_at else None
return token, exp_dt
def get_role_credentials(account_id, role_name, access_token):
resp = SSO_CLIENT.get_role_credentials(
accountId=account_id,
roleName=role_name,
accessToken=access_token,
)
creds = resp.get("roleCredentials")
if not creds:
raise RuntimeError(f"No roleCredentials for account {account_id}")
return {
"aws_access_key_id": creds["accessKeyId"],
"aws_secret_access_key": creds["secretAccessKey"],
"aws_session_token": creds["sessionToken"],
}
def list_sso_account_ids(access_token):
account_ids = []
paginator = SSO_CLIENT.get_paginator("list_accounts")
for page in paginator.paginate(accessToken=access_token):
for acct in page.get("accountList", []):
account_ids.append(acct["accountId"])
return account_ids
def main():
token, exp = find_latest_sso_token()
if exp and exp <= datetime.now(timezone.utc):
raise SystemExit("SSO token expired — run `aws sso login` again.")
account_ids = list_sso_account_ids(token)
if not account_ids:
print("No accounts discovered for this SSO session.")
return
for acct_id in account_ids:
try:
creds = get_role_credentials(acct_id, FIXED_ROLE_NAME, token)
sess = boto3.Session(**creds, region_name="eu-west-1")
sts = sess.client("sts")
who = sts.get_caller_identity()
print(f"\n--> Account {acct_id}, caller identity:", {who['Arn']})
except ClientError as e:
print(" failed to get role credentials or call STS:", e)
except RuntimeError as e:
# Could indicate the role isn't assigned to you in that account
print(" runtime error:", e)
if __name__ == "__main__":
main()
Notes:
- If
AWSReadOnlyAccess
is not assigned to your Identity Center principal in a particular account,get_role_credentials
will fail for that account.
6) Hardening recommendations and alternatives
-
File permissions: make sure
~/.aws/sso/cache
is only readable by your user (chmod 700 ~/.aws/sso/cache && chmod 600 ~/.aws/sso/cache/*.json
). - Avoid long sessions: Keep AWS Identity Center session duration as small as practical.
-
Audit & monitor: enable CloudTrail and monitor
GetRoleCredentials
/AssumeRole
patterns for suspicious usage. -
Least privilege: maybe you don’t need that
AWSAdministratorAccess
role in every account — start withAWSReadOnlyAccess
and escalate only when necessary.
Top comments (0)