Integrate SSO into Your Code
Use AWS Single Sign-On (SSO) profiles locally and Identity and Access Management (IAM) roles in the cloud for secure, environment-aware access.Development Principles
- Local vs. cloud: Use SSO profiles for local development; use IAM roles in cloud environments
- Least privilege: Grant only the permissions your application needs
- No hardcoded secrets: Never store access keys or sensitive data in code or
.envfiles - Consistent naming: Reuse the same profile name across projects (e.g.,
<project>-workload-development)
Credential Resolution Overview
Your app decides credentials depending on the runtime environment:Environment Detection Pattern
Use a simple, reliable signal like AWS_PROFILE, .env.local, or NODE_ENV=development.Copy
import os, pathlib
def is_local():
return bool(os.getenv("AWS_PROFILE")) \
or os.getenv("NODE_ENV") == "development" \
or pathlib.Path(".env.local").exists()
Python Example (boto3)
Installation
Copy
# Install required packages
pip install boto3
Basic Implementation
- Environment Variable Method
- Direct Profile Method
- Class-Based Approach
Copy
def create_aws_client(service_name='s3'):
"""
- Local: uses SSO profile if AWS_PROFILE is set
- Cloud: falls back to default chain (instance/role)
"""
profile = os.environ.get("AWS_PROFILE")
region = os.environ.get("AWS_REGION", "us-west-2")
if profile:
try:
boto3.setup_default_session(profile_name=profile, region_name=region)
except ProfileNotFound as e:
raise RuntimeError(f"AWS profile '{profile}' not found") from e
else:
boto3.setup_default_session(region_name=region)
return boto3.client(service_name)
if __name__ == "__main__":
s3 = create_aws_client('s3')
resp = s3.list_buckets()
print([b['Name'] for b in resp.get('Buckets', [])])
Copy
import boto3
from botocore.exceptions import ProfileNotFound, NoCredentialsError
def create_aws_client_with_profile(service_name='s3', profile_name=None, region='us-west-2'):
try:
if profile_name:
session = boto3.Session(profile_name=profile_name, region_name=region)
return session.client(service_name)
# Cloud: default chain (role/metadata)
return boto3.client(service_name, region_name=region)
except ProfileNotFound as e:
raise RuntimeError(f"AWS profile '{profile_name}' not found") from e
except NoCredentialsError as e:
raise RuntimeError("No AWS credentials available") from e
# Usage example
def example_usage():
# For local development, set your SSO profile
profile = "<project>-workload-development"
# Create clients with the chosen profile
s3_client = create_aws_client_with_profile('s3', profile)
ses_client = create_aws_client_with_profile('ses', profile)
# Example: list S3 buckets
try:
buckets = s3_client.list_buckets()
print("S3 Buckets:")
for b in buckets.get("Buckets", []):
print(f" - {b['Name']}")
except Exception as e:
print(f"Error listing buckets: {e}")
if __name__ == "__main__":
example_usage()
Copy
import os
import boto3
from typing import Optional
class AWSClientManager:
"""Centralized AWS client/session management with SSO support."""
def __init__(self, profile_name: Optional[str] = None, region: Optional[str] = None):
self.profile_name = profile_name or os.getenv("AWS_PROFILE")
self.region = region or os.getenv("AWS_REGION", "us-west-2")
self._session = None
@property
def session(self) -> boto3.session.Session:
if not self._session:
if self.profile_name:
self._session = boto3.Session(profile_name=self.profile_name, region_name=self.region)
else:
self._session = boto3.Session(region_name=self.region)
return self._session
def client(self, service_name: str):
return self.session.client(service_name)
def resource(self, service_name: str):
return self.session.resource(service_name)
# Example
if __name__ == "__main__":
aws = AWSClientManager()
s3 = aws.client('s3')
print([b['Name'] for b in s3.list_buckets().get('Buckets', [])])
Environment Configuration
Copy
# Set profile for your development session
export AWS_PROFILE=<project>-<development_workload_account>
export AWS_REGION=us-west-2
# Run your Python application
python app.py
Node.js Example (AWS SDK v3)
Installation
Copy
# Install required packages
npm install @aws-sdk/client-s3 @aws-sdk/credential-providers
Implementation Examples
- Environment Variable Method
- Direct Profile Method
- Configuration Manager
Copy
import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3';
import { fromSSO } from '@aws-sdk/credential-providers';
/**
* Create AWS client with environment-based credentials
*/
function createS3() {
const region = process.env.AWS_REGION || 'us-west-2';
const profile = process.env.AWS_PROFILE;
if (profile) {
// Local with SSO
return new S3Client({
region,
credentials: fromSSO({ profile })
});
}
// Cloud: default provider chain (role)
return new S3Client({ region });
}
export async function listS3Buckets() {
const s3 = createS3();
const out = await s3.send(new ListBucketsCommand({}));
console.log('S3 Buckets:', out.Buckets?.map(b => b.Name));
return out.Buckets;
}
// Run example
listS3Buckets().catch(console.error);
Copy
import { S3Client } from '@aws-sdk/client-s3';
import { SESClient } from '@aws-sdk/client-ses';
import { fromSSO } from '@aws-sdk/credential-providers';
class AWSClientFactory {
constructor(profileName = process.env.AWS_PROFILE, region = process.env.AWS_REGION || 'us-west-2') {
this.profileName = profileName;
this.region = region;
}
config() {
const cfg = { region: this.region };
if (this.profileName) cfg.credentials = fromSSO({ profile: this.profileName });
return cfg;
}
s3() { return new S3Client(this.config()); }
ses() { return new SESClient(this.config()); }
}
async function example() {
const factory = new AWSClientFactory('<project>-<production_workload_account>');
const s3 = factory.s3();
const buckets = await s3.send(new ListBucketsCommand({}));
console.log(`Found ${buckets.Buckets?.length || 0} buckets`);
}
example().catch(console.error);
Copy
import { S3Client } from '@aws-sdk/client-s3';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { fromSSO } from '@aws-sdk/credential-providers';
class AWSConfig {
constructor() {
this.profile = process.env.AWS_PROFILE;
this.region = process.env.AWS_REGION || 'us-west-2';
this.clients = new Map();
}
creds() {
return this.profile ? fromSSO({ profile: this.profile }) : undefined;
}
get(ClientClass, key) {
if (!this.clients.has(key)) {
this.clients.set(key, new ClientClass({
region: this.region,
credentials: this.creds()
}));
}
return this.clients.get(key);
}
s3() { return this.get(S3Client, 's3'); }
dynamodb() { return this.get(DynamoDBClient, 'dynamodb'); }
}
// Usage
export const aws = new AWSConfig();
export const s3Client = aws.s3();
export const dynamoDBClient = aws.dynamodb();
Environment Configuration
Copy
# Set environment variables
export NODE_ENV=development
export AWS_PROFILE=<project>-<production_workload_account>
export AWS_REGION=us-west-2
# Run your Node.js application
npm start
Go Example
For Go applications, AWS provides excellent SSO support through the official SDK.Installation
Copy
go get github.com/aws/aws-sdk-go-v2/aws
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/s3
Implementation
- Basic Configuration
- Advanced Configuration
Copy
import (
"context"
"fmt"
"log"
"os"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func newConfig(ctx context.Context) (aws.Config, error) {
profile := os.Getenv("AWS_PROFILE")
region := os.Getenv("AWS_REGION")
if profile != "" {
return config.LoadDefaultConfig(ctx,
config.WithSharedConfigProfile(profile),
config.WithRegion(region),
)
}
// Cloud: default chain (role)
return config.LoadDefaultConfig(ctx, config.WithRegion(region))
}
func main() {
ctx := context.Background()
cfg, err := newConfig(ctx)
if err != nil {
log.Fatalf("unable to load SDK config: %v", err)
}
s3c := s3.NewFromConfig(cfg)
out, err := s3c.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
log.Fatalf("list buckets failed: %v", err)
}
for _, b := range out.Buckets {
fmt.Println(*b.Name)
}
}
Copy
package aws
import (
"context"
"os"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/ses"
)
type Cfg struct{ cfg aws.Config }
func New(ctx context.Context, regionDefault string) (*Cfg, error) {
region := envOr("AWS_REGION", regionDefault)
profile := os.Getenv("AWS_PROFILE")
opts := []func(*config.LoadOptions) error{ config.WithRegion(region) }
if profile != "" {
opts = append(opts, config.WithSharedConfigProfile(profile))
}
c, err := config.LoadDefaultConfig(ctx, opts...)
if err != nil { return nil, err }
return &Cfg{cfg: c}, nil
}
func (c *Cfg) S3() *s3.Client { return s3.NewFromConfig(c.cfg) }
func (c *Cfg) DynamoDB() *dynamodb.Client { return dynamodb.NewFromConfig(c.cfg) }
func (c *Cfg) SES() *ses.Client { return ses.NewFromConfig(c.cfg) }
func envOr(k, d string) string {
if v := os.Getenv(k); v != "" { return v }
return d
}
Development Workflow Best Practices
Environment Setup
Use environment-specific files and variables.- .env.local
- .env.production
Copy
# Local development
NODE_ENV=development
AWS_PROFILE=<project>-<development_workload_account>
AWS_REGION=us-west-2
# APP settings
LOG_LEVEL=debug
API_BASE_URL=https://staging-api.<project>.com
Copy
# AWS Cloud environment
NODE_ENV=production
# AWS_PROFILE not set - uses IAM roles
AWS_REGION=us-west-2
# Application settings
LOG_LEVEL=info
API_BASE_URL=https://api.<project>.com
Common Patterns and Tips
Environment Detection
Environment Detection
Best practices for detecting local vs. cloud environments:
Copy
import os,pathlib
def is_local_environment():
return any([
os.getenv('AWS_PROFILE'),
os.getenv('NODE_ENV') == 'development',
pathlib.Path('.env.local').exists()
])
Error Handling
Error Handling
Handle common SSO-related errors gracefully:
Copy
from botocore.exceptions import TokenRefreshRequired, ProfileNotFound
def handle_aws_errors(func):
"""Decorator for AWS error handling"""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except TokenRefreshRequired:
print("SSO token expired. Please run: aws sso login")
raise
except ProfileNotFound as e:
print(f"AWS profile not found: {e}")
raise
except Exception as e:
print(f"AWS operation failed: {e}")
raise
return wrapper
Configuration Validation
Configuration Validation
Validate your configuration before running:
Copy
def validate_aws_config():
"""Validate AWS configuration"""
profile = os.getenv('AWS_PROFILE')
if not profile:
raise ValueError("AWS_PROFILE environment variable not set")
# Test connection
try:
boto3.client('sts').get_caller_identity()
except Exception as e:
raise ValueError(f"AWS configuration invalid: {e}")
return True