Skip to main content
When using external services (AWS S3, OpenAI, etc.) in Sonamu projects, API keys are managed as environment variables. This guide explains how to securely store them without hardcoding in your code.
API keys should never be committed to Git! If leaked, immediately invalidate and reissue the keys.

Common API Keys

These are frequently used external service API keys in Sonamu.

AWS (S3 Storage)

.env
# AWS Credentials
AWS_ACCESS_KEY_ID=your-aws-access-key-id
AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key

# S3 Configuration
S3_REGION=ap-northeast-2
S3_BUCKET=my-project-uploads
sonamu.config.ts:
import { defineConfig } from "sonamu";
import { drivers } from "sonamu/storage";

export default defineConfig({
  server: {
    storage: {
      default: process.env.DRIVE_DISK ?? "fs",
      drivers: {
        s3: drivers.s3({
          credentials: {
            accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
            secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
          },
          region: process.env.S3_REGION ?? "ap-northeast-2",
          bucket: process.env.S3_BUCKET ?? "default-bucket",
          visibility: "private",
        }),
      },
    },
  },
});

OpenAI

.env
# OpenAI API Key
OPENAI_API_KEY=your-openai-api-key
OPENAI_ORG_ID=your-openai-org-id  # Organization ID (optional)
Usage example:
import OpenAI from "openai";

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  organization: process.env.OPENAI_ORG_ID,
});

const completion = await openai.chat.completions.create({
  model: "gpt-4",
  messages: [{ role: "user", content: "Hello!" }],
});

Optional External Service Integrations

Sonamu can integrate with various external services. Below are environment variable examples for commonly used services.
These services are not part of Sonamu’s default configuration. Add them only when needed for your project.
# Stripe Payments
STRIPE_SECRET_KEY=your-stripe-secret-key
STRIPE_PUBLISHABLE_KEY=your-stripe-publishable-key
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret

Environment-Specific Key Management

Never use production keys in development environments! Use separate keys for each environment.
.env.development
# Test or sandbox keys
OPENAI_API_KEY=your-test-openai-key

AWS_ACCESS_KEY_ID=your-test-aws-key-id
AWS_SECRET_ACCESS_KEY=your-test-aws-secret-key
S3_BUCKET=myproject-dev

STRIPE_SECRET_KEY=your-test-stripe-key
Characteristics:
  • Uses test mode keys
  • No or minimal charges
  • Limited permissions

Creating AWS IAM User

Access IAM Console

Create User

  1. Users → Add users
  2. User name: sonamu-s3-user
  3. Access type: Select Programmatic access

Set Permissions

Option 1: Attach Existing Policy
AmazonS3FullAccess  ❌ (too broad)
Option 2: Custom Policy (recommended)
sonamu-s3-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::myproject-uploads/*",
        "arn:aws:s3:::myproject-uploads"
      ]
    }
  ]
}
Enhance security by allowing only specific buckets and actions.

Issue Access Keys

After user creation completes:
  • Access key ID: your-aws-access-key-id
  • Secret access key: your-aws-secret-access-key
The secret access key is displayed only once! Save it to a secure location immediately.

Add to .env File

AWS_ACCESS_KEY_ID=your-aws-access-key-id
AWS_SECRET_ACCESS_KEY=your-aws-secret-access-key

Issuing OpenAI API Key

Access OpenAI Platform

Create API Key

  1. API keys → Create new secret key
  2. Name: sonamu-production
  3. Permissions: All or Restricted (recommended)

Set Usage Limits

Settings → Billing → Set Usage limits
Hard limit: $100/month (automatically blocked when exceeded)
Soft limit: $80/month (notification)
Unexpected charges may occur if you don’t set usage limits!

Save Key

OPENAI_API_KEY=your-openai-api-key
Save to a secure location immediately after issuance (cannot be viewed again)

Security Best Practices

Regular Key Replacement

Recommended Frequency:
  • Production: Every 3 months
  • Staging: Every 6 months
  • Development: Yearly or as needed

Rotation Procedure

1

Create New Key

Create a new key while keeping the existing one active.
2

Deploy New Key

# Add new key to environment variables
AWS_ACCESS_KEY_ID=your-new-aws-key-id
AWS_SECRET_ACCESS_KEY=your-new-aws-secret-key

# Restart application
3

Monitor

Verify it works without errors for 24 hours
4

Delete Old Key

Deactivate/delete the old key if no issues
To perform key rotation without downtime, use a blue-green approach that maintains both new and old keys simultaneously.

Minimize IAM Policies

// ❌ Bad example: All permissions
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}

// ✅ Good example: Only necessary permissions
{
  "Effect": "Allow",
  "Action": [
    "s3:PutObject",
    "s3:GetObject"
  ],
  "Resource": "arn:aws:s3:::my-bucket/uploads/*"
}

Separate Read-Only Keys

# Write permissions (application)
AWS_WRITE_ACCESS_KEY_ID=your-write-key-id
AWS_WRITE_SECRET_ACCESS_KEY=your-write-secret-key

# Read permissions (analytics/backup)
AWS_READ_ACCESS_KEY_ID=your-read-key-id
AWS_READ_SECRET_ACCESS_KEY=your-read-secret-key

Immediate Actions

Invalidate Key Immediately

# AWS CLI
aws iam delete-access-key \
  --access-key-id your-leaked-access-key-id \
  --user-name sonamu-user

# OpenAI Platform
# API keys → Click Revoke

Issue and Deploy New Key

Issue a new key immediately and deploy to production

Check Usage History

# Check for suspicious activity in AWS CloudTrail
# Check for abnormal calls on OpenAI Usage page

Clean Git History

# Remove key from commit history (carefully!)
git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch .env" \
  --prune-empty --tag-name-filter cat -- --all

# Force push
git push origin --force --all
Cleaning Git history affects collaborating team members. Coordinate with your team before proceeding.

Leak Detection Tools

# Install git-secrets
brew install git-secrets

# Apply to project
git secrets --install
git secrets --register-aws

# Scan
git secrets --scan

Development Environment

# Using dotenv-vault
npm install -g dotenv-vault

# Encrypt
npx dotenv-vault encrypt

# Decrypt (team members)
DOTENV_KEY=your-dotenv-vault-key npx dotenv-vault decrypt

Production Environment

AWS Systems Manager Parameter Store:
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";

const client = new SSMClient({ region: "ap-northeast-2" });

async function getSecret(name: string) {
  const command = new GetParameterCommand({
    Name: name,
    WithDecryption: true,
  });
  const response = await client.send(command);
  return response.Parameter?.Value;
}

// Usage
const apiKey = await getSecret("/myproject/prod/openai-api-key");
HashiCorp Vault:
import vault from "node-vault";

const client = vault({
  endpoint: "https://vault.example.com:8200",
  token: process.env.VAULT_TOKEN,
});

const { data } = await client.read("secret/data/myproject/prod");
const apiKey = data.data.OPENAI_API_KEY;

Key Usage Monitoring

AWS CloudWatch

// Monitor usage with AWS SDK
import { CloudWatchClient, GetMetricStatisticsCommand } from "@aws-sdk/client-cloudwatch";

const client = new CloudWatchClient({ region: "ap-northeast-2" });

const stats = await client.send(
  new GetMetricStatisticsCommand({
    Namespace: "AWS/S3",
    MetricName: "NumberOfObjects",
    Dimensions: [{
      Name: "BucketName",
      Value: "my-project-uploads",
    }],
    StartTime: new Date(Date.now() - 86400000), // 24 hours ago
    EndTime: new Date(),
    Period: 3600, // 1 hour
    Statistics: ["Average"],
  })
);

OpenAI Usage Alerts

// Check usage and send alerts
import OpenAI from "openai";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

// Run periodically (cron job)
async function checkUsage() {
  const usage = await fetch("https://api.openai.com/v1/usage", {
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    },
  }).then(r => r.json());

  const totalCost = usage.total_usage * 0.0001; // Example rate

  if (totalCost > 80) {
    // Send warning notification (Slack, Email, etc.)
    console.warn(`⚠️ OpenAI usage warning: $${totalCost}`);
  }
}

Test Environment Key Management

# .env.test
OPENAI_API_KEY=test-mock-key
AWS_ACCESS_KEY_ID=test-key-id
AWS_SECRET_ACCESS_KEY=test-secret-key
Use mocks in tests to avoid calling actual APIs. This reduces test costs and improves speed.

Troubleshooting

Symptoms:
Error: The AWS Access Key Id you provided does not exist in our records
Causes:
  1. Incorrect Access Key ID
  2. Key has been deleted or deactivated
  3. Typos (spaces, line breaks)
Solution:
# Verify key
echo $AWS_ACCESS_KEY_ID

# Check .env file
cat .env | grep AWS

# Check key status in IAM console
# Verify it's in Active status
Symptoms:
RateLimitError: Rate limit reached for gpt-4
Causes:
  • API request limit exceeded (RPM, TPM)
Solution:
// Implement retry logic
import { RateLimiter } from "limiter";

const limiter = new RateLimiter({
  tokensPerInterval: 10,
  interval: "minute",
});

async function callOpenAI(prompt: string) {
  await limiter.removeTokens(1);
  
  try {
    return await openai.chat.completions.create({
      model: "gpt-4",
      messages: [{ role: "user", content: prompt }],
    });
  } catch (error) {
    if (error.status === 429) {
      // Retry after 1 minute
      await new Promise(resolve => setTimeout(resolve, 60000));
      return callOpenAI(prompt);
    }
    throw error;
  }
}
Symptoms:
Access Denied: You do not have permission to perform this action
Causes:
  • IAM policy missing required permissions
  • Blocked by bucket policy
Solution:
// Add permissions to IAM policy
{
  "Effect": "Allow",
  "Action": [
    "s3:PutObject",
    "s3:GetObject",
    "s3:DeleteObject"
  ],
  "Resource": "arn:aws:s3:::my-bucket/*"
}
# Test permissions
aws s3 ls s3://my-bucket --profile myproject

Next Steps