Fixing AWS Login Credentials Expiring After 15 Minutes: The awscrt Library Mystery
Introduction
I was having trouble with my Python application using boto3 - even though I authenticated with the aws login command, I kept getting "credentials expired" errors after about 15 minutes.
After investigation, I discovered that an outdated version of the awscrt library was causing automatic credential refresh to fail.
This article shares the root cause and solution. I hope it helps anyone facing the same issue!
What is aws login?
aws login is a relatively new command added in AWS CLI 2.15.0.
Differences from Traditional Authentication Methods
Traditional method (aws sso login):
aws sso login --profile my-profile
- Uses AWS IAM Identity Center (formerly AWS SSO)
- Requires accessing your organization's SSO portal for authentication
New method (aws login):
aws login
- Uses AWS Management Console credentials directly
- Same experience as logging into the AWS Console via browser
- More intuitive and simpler
Features of aws login
- π Refresh token support: Once logged in, credentials should automatically refresh
- π Simple: No profile configuration needed
- π boto3 integration: Works transparently with Python applications
The Problem
Symptoms
My Python application using boto3 was experiencing the following issue:
import boto3
session = boto3.Session()
sts = session.client('sts')
# Initially works
identity = sts.get_caller_identity()
print(f"Account: {identity['Account']}") # OK
# 15 minutes later...
identity = sts.get_caller_identity() # β Error!
Error message:
ExpiredTokenException: The security token included in the request is expired
Expected Behavior
boto3 should automatically use the refresh token to obtain new credentials when the current ones are about to expire.
However, in reality, the refresh was failing, and credentials were expiring after 15 minutes.
Investigation
1. Checking Credentials
First, I verified which credentials boto3 was using:
import boto3
session = boto3.Session()
credentials = session.get_credentials()
print(f"Provider: {credentials.method}")
# Output: login
print(f"Refreshable: {isinstance(credentials, RefreshableCredentials)}")
# Output: True
boto3 was correctly recognizing the login provider and treating it as refreshable credentials.
2. Checking Cache Files
aws login credentials are cached at:
~/.aws/login/cache/<hash>.json
The contents showed that the refresh token was properly stored:
{
"accessToken": {
"accessKeyId": "ASIA************",
"secretAccessKey": "**********************",
"sessionToken": "**********************",
"expiresAt": "2026-01-05T07:12:30Z"
},
"refreshToken": "**********************",
"dpopKey": "-----BEGIN EC PRIVATE KEY-----\n..."
}
3. Testing Refresh Behavior
I forced the expiration time to be shorter to see if refresh would execute:
import boto3
import json
from datetime import datetime, timedelta, timezone
# Load cache file
cache_file = "~/.aws/login/cache/<hash>.json"
with open(cache_file, 'r') as f:
token_data = json.load(f)
# Set expiration to 1 minute from now
new_expiry = datetime.now(timezone.utc) + timedelta(minutes=1)
token_data['accessToken']['expiresAt'] = new_expiry.strftime('%Y-%m-%dT%H:%M:%SZ')
# Update cache file
with open(cache_file, 'w') as f:
json.dump(token_data, f)
# Create new boto3 session
session = boto3.Session()
sts = session.client('sts')
# STS call (should trigger refresh)
identity = sts.get_caller_identity()
This resulted in the following error:
AttributeError: type object 'EC' has no attribute 'decode_der_signature_to_padded_pair'
This was the root cause!
4. Identifying the Root Cause
Examining boto3's source code revealed that aws login refresh processing requires generating a DPoP (Demonstrating Proof-of-Possession) header.
This header generation uses the EC class from the awscrt library, but the old version (0.28.4) was missing the required method.
# Simplified excerpt from botocore/credentials.py
from awscrt.auth import EC
def _build_dpop_header(private_key, uri):
# ...
signature_bytes = EC.decode_der_signature_to_padded_pair(
signature_der, EC.CurveType.SECP384R1
) # β This method doesn't exist in awscrt 0.28.4!
Solution
Upgrading Libraries
The solution was simple: upgrade the awscrt library:
pip install --upgrade awscrt botocore boto3
Versions After Upgrade
awscrt: 0.28.4 β 0.30.0
botocore: 1.42.19 β 1.42.21
boto3: 1.42.19 β 1.42.21
Adding to requirements.txt
To prevent this issue in the future, I specified minimum versions in requirements.txt:
# AWS credentials refresh support
# awscrt >= 0.30.0 is required for aws login credential auto-refresh
awscrt>=0.30.0
boto3>=1.42.21
botocore>=1.42.21
Verification
Test Script
After upgrading, I verified that refresh was working properly:
# test_credential_refresh.py
import boto3
import json
from datetime import datetime, timedelta, timezone
print("=" * 80)
print("Credential Auto-Refresh Test")
print("=" * 80)
# Cache file path
cache_file = "~/.aws/login/cache/<hash>.json"
# Set expiration to 1 minute from now (force refresh)
with open(cache_file, 'r') as f:
token_data = json.load(f)
original_access_key = token_data['accessToken']['accessKeyId']
print(f"Original access key: ASIA***************...")
new_expiry = datetime.now(timezone.utc) + timedelta(minutes=1)
token_data['accessToken']['expiresAt'] = new_expiry.strftime('%Y-%m-%dT%H:%M:%SZ')
with open(cache_file, 'w') as f:
json.dump(token_data, f)
# Create boto3 session
session = boto3.Session()
sts = session.client('sts')
# STS call (triggers refresh)
print("\nExecuting STS call...")
identity = sts.get_caller_identity()
print(f"β Success: {identity['Account']}")
# Check cache file after refresh
with open(cache_file, 'r') as f:
refreshed_token = json.load(f)
refreshed_access_key = refreshed_token['accessToken']['accessKeyId']
print(f"\nRefreshed access key: ASIA***************...")
if refreshed_access_key != original_access_key:
print("\nβββ New credentials obtained!")
print("Refresh is working properly!")
else:
print("\nβ Credentials were not updated")
print("=" * 80)
Execution Results
================================================================================
Credential Auto-Refresh Test
================================================================================
Original access key: ASIA***************...
Executing STS call...
β Success: **********
Refreshed access key: ASIA***************...
βββ New credentials obtained!
Refresh is working properly!
================================================================================
Perfect! New access keys were obtained, confirming that refresh is working properly.
boto3 Refresh Timing
boto3 attempts to refresh credentials at the following times:
| Timing | Description | Behavior on Failure |
|---|---|---|
| Advisory Refresh | 15 minutes before expiration | Continue using existing credentials |
| Mandatory Refresh | 10 minutes before expiration | Return error |
| aws login specific | 5 minutes before expiration | Return error |
In other words, automatic refresh occurs 5 minutes before expiration, resolving the 15-minute expiration issue.
Summary
Root Cause
The reason aws login credentials were expiring after 15 minutes was an outdated awscrt library version.
- awscrt 0.28.4 was missing the method needed for DPoP header generation
- This caused errors during refresh
- boto3 failed to auto-update, resulting in expired credentials
Solution
pip install --upgrade awscrt botocore boto3
Just this simple command made credential auto-refresh work properly!
Key Points
- β awscrt >= 0.30.0 is required
- β Latest boto3/botocore versions recommended
- β Credentials auto-refresh 5 minutes before expiration
- β No re-login needed as long as refresh token is valid
References
Conclusion
Since aws login is a relatively new feature, there's still limited information available. When I asked Kiro about it, I was repeatedly directed to aws sso login and modifications to credentials and config files...
I hope this article helps anyone encountering the same issue!
Top comments (0)