DEV Community

Yuki Ogawa
Yuki Ogawa

Posted on

Fixing AWS Login Credentials Expiring After 15 Minutes: The awscrt Library Mystery

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
Enter fullscreen mode Exit fullscreen mode
  • Uses AWS IAM Identity Center (formerly AWS SSO)
  • Requires accessing your organization's SSO portal for authentication

New method (aws login):

aws login
Enter fullscreen mode Exit fullscreen mode
  • 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!
Enter fullscreen mode Exit fullscreen mode

Error message:

ExpiredTokenException: The security token included in the request is expired
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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..."
}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

This resulted in the following error:

AttributeError: type object 'EC' has no attribute 'decode_der_signature_to_padded_pair'
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

Solution

Upgrading Libraries

The solution was simple: upgrade the awscrt library:

pip install --upgrade awscrt botocore boto3
Enter fullscreen mode Exit fullscreen mode

Versions After Upgrade

awscrt: 0.28.4 β†’ 0.30.0
botocore: 1.42.19 β†’ 1.42.21
boto3: 1.42.19 β†’ 1.42.21
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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!
================================================================================
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)