The Four Approaches
API key management falls on a spectrum from "works on my machine" to "enterprise compliance." Here are the four approaches most teams use:
- dotenv (.env files) — Plaintext key-value pairs in a file. The default for most Node.js and Python projects.
- AWS Secrets Manager — Cloud-managed secrets with IAM integration, automatic rotation for RDS, and per-secret billing.
- HashiCorp Vault — Self-hosted or managed secrets engine with dynamic secrets, PKI, and fine-grained access policies.
- APIAuth — CLI-first encrypted keystore with hash-based verification, rotation, and CI/CD export. Offline-first, zero infrastructure.
These aren't mutually exclusive — many teams use a combination. But understanding what each one does well helps you avoid over-engineering or under-securing.
At a Glance
| Capability | dotenv | AWS Secrets Manager | HashiCorp Vault | APIAuth |
|---|---|---|---|---|
| Encryption at rest | ✗ | ✓ | ✓ | ✓ |
| Key verification (hash-based) | ✗ | ✗ | ~ | ✓ |
| Revocation tracking | ✗ | ~ | ✓ | ✓ |
| Expiry tracking | ✗ | ~ | ✓ | ✓ |
| CLI key rotation | ✗ | ~ | ✓ | ✓ |
| Works offline | ✓ | ✗ | ✗ | ✓ |
| Zero infrastructure | ✓ | ✓ | ✗ | ✓ |
| CI/CD export (4 formats) | ✗ | ✗ | ~ | ✓ |
| Audit health check | ✗ | ~ | ✓ | ✓ |
| Setup time | 0 min | 30 min | 2–4 hours | 2 min |
| Cost (team of 5) | Free | $1.95/secret + API calls | $0–$1,000+/mo | $79/mo (Team plan) |
Approach 1: dotenv Files
dotenv — The Default That Never Scales
- Keys stored as
KEY=valuein.env - Loaded by dotenv libraries in Node.js, Python, Ruby, etc.
- No encryption, no verification, no rotation, no audit
Every project starts here. A .env file in the project root, keys as plaintext, .gitignore entry to prevent accidental commits. It works — until it doesn't.
What dotenv gets right
- Zero friction. No services to provision, no IAM policies, no CLI tools. Just write
STRIPE_KEY=sk_live_abc123and go. - Universal support. Every language has a dotenv library. Environment variables are the lingua franca of configuration.
- Works offline. No network dependency. Your .env file is always available.
Where dotenv breaks down
- Plaintext storage. Anyone with file access can read every key. A single misconfigured
.gitignorecommits keys to git history. - No verification. When your API receives a key, you have no way to check if it's valid, revoked, or expired from the .env file alone. You need a separate system.
- No rotation. Changing a key means manually editing the file, deploying the change, and hoping you caught every instance.
- No audit. How many keys are in your .env? Which ones are expired? Which service do they belong to? You'd have to read the file and manually check each value.
- Environment sprawl.
.env.development,.env.staging,.env.production— each one a separate plaintext file with no cross-referencing.
# Typical .env file — everything is plaintext
STRIPE_SECRET_KEY=sk_live_abc123def456
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCY
DATABASE_URL=postgres://user:pass@host:5432/db
SENDGRID_API_KEY=SG.nge3fjr934kf...
Risk: A leaked .env file exposes every secret in plaintext. Git history leaks are the #1 cause of API key compromises. Tools like git-secrets and GitHub's secret scanning help, but they're reactive — they detect leaks after they happen.
Approach 2: AWS Secrets Manager
AWS Secrets Manager — Cloud-Managed with IAM Integration
- Secrets stored encrypted in AWS, accessed via IAM roles
- Automatic rotation for RDS credentials (Lambda-based)
- Cross-account access via resource policies
If you're already on AWS, Secrets Manager is the path of least resistance. IAM roles grant access, Lambda functions handle rotation, and you never see plaintext in code.
What AWS Secrets Manager gets right
- IAM-native. Access controlled by the same IAM policies you already manage. No separate auth system.
- Automatic rotation for RDS. Built-in Lambda rotation for database credentials. Set it and forget it.
- Audit via CloudTrail. Every secret access is logged. Compliance-friendly.
- No infrastructure to manage. Fully managed service.
Where AWS Secrets Manager falls short for API keys
- Not designed for API key verification. Secrets Manager stores and retrieves plaintext values. It doesn't hash keys or verify incoming requests. You'd need to build that yourself.
- No CLI workflow. Managing secrets requires the AWS console, CLI with IAM credentials, or SDK calls. Not a developer-first experience.
- Rotation is RDS-only. Automatic rotation is built for database credentials. For third-party API keys (Stripe, SendGrid, etc.), you write custom Lambda functions.
- Cost scales with secrets. $0.40/secret/month + API call charges. For a team with 50 keys across environments, that's $20/month just for storage, plus retrieval costs.
- AWS lock-in. Access requires IAM credentials and network connectivity. No offline mode.
# Storing a secret
aws secretsmanager create-secret \
--name "stripe/live-key" \
--secret-string "sk_live_abc123def456"
# Retrieving it
aws secretsmanager get-secret-value \
--secret-id "stripe/live-key" \
--query SecretString --output text
# No built-in way to:
# - Verify an incoming API key against stored secrets
# - Check if a key is revoked or expired
# - Export keys in dotenv/GitHub Actions format
# - Audit key health from the CLI
Best for: AWS-native teams that need managed secret storage with IAM integration. Not a complete API key lifecycle tool — you'll need to add verification, expiry tracking, and non-RDS rotation yourself.
Approach 3: HashiCorp Vault
HashiCorp Vault — Enterprise Secrets Infrastructure
- Dynamic secrets, PKI, transit encryption, database credentials
- Fine-grained access policies (HCL/RBAC)
- Audit logging, multi-region replication
Vault is the enterprise answer. It handles secrets, generates dynamic credentials, manages PKI, encrypts transit data, and integrates with every auth system you can name. It's also the most complex option.
What Vault gets right
- Dynamic secrets. Generate short-lived database credentials, TLS certificates, and cloud IAM credentials on demand. No long-lived keys to leak.
- Policy-based access. Fine-grained HCL policies control who can read what, with path-based templating.
- Complete audit trail. Every request is logged. File, syslog, or socket audit devices.
- PKI engine. Issue and manage TLS certificates. Build your own internal CA.
- Transit engine. Encrypt/decrypt data without exposing keys to applications. "Cryptography as a service."
Where Vault is overkill for API key management
- Infrastructure burden. Self-hosted Vault requires a cluster, storage backend (Consul/Raft), unsealing, monitoring, and backups. HCP Vault Managed removes ops overhead but adds cost ($1.58/hr = ~$1,140/month).
- No CLI key verification. Vault stores and delivers secrets. It doesn't verify incoming API keys by hash — you'd build a custom plugin.
- Complex for small teams. A 3-person startup doesn't need dynamic secrets, PKI engines, or multi-region replication. They need to store Stripe keys safely and know when they expire.
- Network dependency. Applications need Vault connectivity to retrieve secrets. No offline mode.
- Learning curve. HCL policies, secret engines, auth methods, leases, and renewals — Vault has its own conceptual model that takes weeks to master.
# Vault is powerful but complex
vault secrets enable -path=api-keys kv-v2
# Store a key
vault kv put api-keys/stripe value=sk_live_abc123def456
# Read a key
vault kv get -field=value api-keys/stripe
# Policy to restrict access
path "api-keys/stripe" {
capabilities = ["read"]
}
# What Vault doesn't do natively:
# - Verify an incoming API key against stored hashes
# - Track key expiry dates and alert
# - Export keys in .env, dotenv, or GitHub Actions format
# - Run a one-command health audit
Best for: Organizations with 50+ engineers, compliance requirements, and dedicated DevOps/SRE teams. If you're not running dynamic secrets or PKI, Vault adds complexity without proportional benefit for API key management.
Approach 4: APIAuth
APIAuth — CLI-First Encrypted Keystore
- AES-256-GCM encrypted keystore, SHA-256 key hashing
- Generate, import, verify, rotate, revoke, export — one CLI
- 4 export formats: env, dotenv, github-actions, json
APIAuth is built specifically for the API key lifecycle. Not general-purpose secret storage — focused on the workflow of generating keys, verifying incoming requests, rotating on schedule, and exporting to CI/CD pipelines.
What APIAuth gets right
- Hash-based verification.
apiauth verifychecks an incoming key against SHA-256 hashes in the keystore. Returns valid, revoked, expired, or not found. No plaintext needed at verification time. - Zero plaintext storage. Keys are SHA-256 hashed on import. The plaintext is shown once at generation and never stored.
- CLI-native rotation.
apiauth rotate <key-id>creates a new key value, increments the version, and invalidates the old hash. One command. - 4 export formats.
apiauth export --format env|dotenv|github-actions|json. Pipeline-ready output without scripting. - Health audit.
apiauth auditchecks for expired, expiring, and revoked keys.--exit-on-expiredfor CI gates. - Offline-first. Everything runs locally. No network required. No telemetry.
Where APIAuth is limited
- Single-machine keystore. The encrypted keystore lives on one machine. Team sharing requires the Team plan's shared keystore or manual sync.
- Not for database credentials. No dynamic secret generation. Use Vault or AWS for RDS credentials that auto-rotate.
- No transit encryption. APIAuth doesn't encrypt application data. It manages keys, not cryptographic operations on payloads.
- No web UI (yet). Everything is CLI. Dashboard is on the Team plan roadmap.
# Full API key lifecycle in 6 commands
pip install apiauth
# Generate
apiauth generate api-key -n "Production API" -s api-gateway --expiry-days 90
# ✓ Generated: ak_a1b2c3d4e5f6...
# ⚠ Save this key now — it won't be shown again.
# Import existing
apiauth import sk_live_existing_key -n "Stripe" -s stripe --expiry-days 365
# ✓ Imported. Hash stored. Plaintext discarded.
# Verify at runtime
apiauth verify ak_a1b2c3d4e5f6... --json-output
# {"status": "valid", "name": "Production API", ...}
# Rotate
apiauth rotate k7f2a9c1
# ✓ Rotated. New version: 2. New key: ak_new_value...
# Export for CI
apiauth export --format github-actions --service api-gateway
# echo "APIAUTH_PRODUCTION_API_ID=k7f2a9c1" >> $GITHUB_ENV
# Audit
apiauth audit --exit-on-expired
# ✓ All keys healthy (or exit 1 if any expired)
Best for: Solo developers and small-to-mid teams (1–20 people) who need API key lifecycle management without infrastructure overhead. If your secrets are primarily third-party API keys (Stripe, SendGrid, AWS, etc.) rather than database credentials, APIAuth covers the full workflow in a single CLI tool.
Security Model Comparison
| Security Property | dotenv | AWS SM | Vault | APIAuth |
|---|---|---|---|---|
| Encryption at rest | ✗ Plaintext | ✓ AWS KMS | ✓ Vault barrier | ✓ AES-256-GCM |
| Key stored as hash | ✗ Raw value | ✗ Raw value | ✗ Raw value | ✓ SHA-256 |
| Master key isolation | N/A | ~ AWS KMS | ✓ Unseal key | ✓ ~/.apiauth/master.key |
| Plaintext exposure surface | File on disk | API response | API response | Once at creation |
| Leak detection | ✗ None | ~ CloudTrail | ✓ Audit log | ~ Verify check |
The critical distinction: dotenv, AWS Secrets Manager, and Vault all store the plaintext key value. They encrypt it at rest, but when you retrieve a secret, you get the raw value. APIAuth stores only the SHA-256 hash after creation/import. This means a compromised keystore reveals key metadata (names, services, expiry dates) but not the key values themselves.
Rotation Workflow Comparison
dotenv — Manual
# 1. Get new key from provider dashboard
# 2. Edit .env file
# 3. Deploy updated .env
# 4. Repeat for every environment
# 5. Hope you didn't miss any servers
Time: 15–60 minutes per key. Error-prone. No version tracking.
AWS Secrets Manager — Lambda Rotation (RDS only)
# For RDS: built-in rotation
aws secretsmanager rotate-secret --secret-id my-rds-creds
# For API keys: write a custom Lambda
# 1. Create Lambda function
# 2. Call provider API to generate new key
# 3. Store new value in Secrets Manager
# 4. Update all consuming services
# Estimated: 4–8 hours to implement
Time: 0 minutes (RDS, automatic) or 4–8 hours (custom Lambda for API keys).
Vault — Lease-Based or Manual
# Dynamic secrets: automatic (Vault handles the lease)
# Static secrets: rotate manually or via custom plugin
# Manual rotation
vault kv put api-keys/stripe value=sk_live_new_key
# Custom rotation engine: 40+ hours to implement
Time: 0 minutes (dynamic) or 40+ hours (custom rotation engine for static API keys).
APIAuth — One Command
# Rotate any key in 2 seconds
apiauth rotate k7f2a9c1
# ✓ Rotated key k7f2a9c1
# New version: 2
# New key: ak_new_value...
# ⚠ Save this key now — it won't be shown again.
# Old key is immediately invalid
apiauth verify ak_old_value
# ✗ Key is INVALID
Time: 2 seconds per key. Version tracking built in. Old value instantly invalid.
CI/CD Integration Comparison
| CI/CD Feature | dotenv | AWS SM | Vault | APIAuth |
|---|---|---|---|---|
| Inject secrets into CI | Manual copy | iam role + sdk | vault agent | export --format |
| dotenv format | ✓ (native) | ✗ | ✗ | ✓ |
| Shell env format | ~ (manual) | ✗ | ✗ | ✓ |
| GitHub Actions format | ✗ | ✗ | ✗ | ✓ |
| JSON format | ✗ | ~ (SDK) | ~ (API) | ✓ |
| Fail CI on expired keys | ✗ | ✗ | ✗ | ✓ audit --exit-on-expired |
APIAuth's 4-format export is a deliberate design choice. Most CI platforms need secrets in a specific format — GitHub Actions uses $GITHUB_ENV, Docker uses .env files, shell scripts use export KEY=value. Instead of writing format-conversion scripts, apiauth export outputs in the exact format your pipeline expects.
# GitHub Actions
apiauth export --format github-actions --service production
# Docker Compose .env
apiauth export --format dotenv --service api-gateway > .env
# Shell source script
eval "$(apiauth export --format env --service auth)"
# Programmatic JSON
apiauth export --format json | jq '.[] | .name'
When to Use Which
Use dotenv when:
You're prototyping alone, the keys are for local development only, and the project will never see production. Replace with APIAuth before deploying.
Use AWS Secrets Manager when:
You're fully on AWS, your secrets are primarily RDS credentials or IAM-linked, and you're okay building custom rotation for non-RDS keys. Pair with APIAuth for API key verification and CI/CD export.
Use HashiCorp Vault when:
You have 50+ engineers, compliance requirements (SOC 2, HIPAA), need dynamic secrets or PKI, and have a dedicated platform team to operate it. Use Vault for infrastructure secrets and APIAuth for API key lifecycle.
Use APIAuth when:
You manage 5–500 API keys across services, need verification at runtime, want CI/CD integration without scripting, and don't want to operate infrastructure. Works standalone or alongside Vault/AWS SM for the API key-specific workflow.
The Layered Approach: APIAuth + Cloud Secrets Manager
The most common production setup uses APIAuth for the key lifecycle (generate, verify, rotate, audit) and a cloud secrets manager for delivery (making keys available to running applications):
# 1. Generate key with APIAuth
apiauth generate api-key -n "Gateway" -s api-gateway --expiry-days 90
# Plaintext shown once → immediately store in AWS Secrets Manager
# 2. Import hash into APIAuth
apiauth import ak_gateway_key_value -n "Gateway" -s api-gateway
# Hash stored. Plaintext discarded from keystore.
# 3. Application reads plaintext from AWS SM at runtime
# (AWS handles delivery, IAM handles access control)
# 4. Verify incoming requests with APIAuth
apiauth verify ak_incoming_request_key --json-output
# {"status": "valid"} or {"status": "revoked"} or {"status": "expired"}
# 5. Rotate with APIAuth, update AWS SM with new value
apiauth rotate k7f2a9c1
# New key generated → store new value in AWS SM
# Old value immediately fails verification
# 6. Audit before deploy
apiauth audit --exit-on-expired
This separation gives you the best of both worlds: cloud-managed delivery with IAM access control, plus CLI-native key lifecycle management with hash-based verification.
Migration Paths
From dotenv to APIAuth
#!/bin/bash
# migrate_env_to_apiauth.sh
while IFS='=' read -r name value; do
[[ "$name" =~ ^#.*$ ]] && continue
[[ -z "$value" ]] && continue
apiauth import "$value" \
--name "$name" \
--service "migrated-from-env" \
--expiry-days 365
done < .env.production
# Verify everything imported
apiauth stats
# Total keys: 12 Active: 12
# Export in your preferred CI/CD format
apiauth export --format dotenv > .env.production.new
From AWS Secrets Manager to APIAuth
#!/bin/bash
# migrate_aws_to_apiauth.sh
for secret_name in $(aws secretsmanager list-secrets --query 'SecretList[].Name' --output text); do
value=$(aws secretsmanager get-secret-value \
--secret-id "$secret_name" \
--query SecretString --output text 2>/dev/null)
if [ -n "$value" ]; then
apiauth import "$value" \
--name "$secret_name" \
--service "aws-migration" \
--expiry-days 365
echo "✓ Imported $secret_name"
fi
done
# Keep AWS SM for delivery, use APIAuth for lifecycle
apiauth audit
Cost Comparison for a 5-Person Team
| Cost Factor | dotenv | AWS SM | Vault (HCP) | APIAuth Team |
|---|---|---|---|---|
| Monthly base | $0 | $0 | $1,140 | $79 |
| Per secret (50 keys) | $0 | $20 | Included | Included |
| API calls (est. 50K/mo) | $0 | $0.25 | Included | $0 |
| Operations effort | 0 hr | 2 hr/mo | 8 hr/mo | 0 hr |
| Total monthly | $0 | ~$22 | ~$1,940 | $79 |
Note: The "cost" of dotenv is $0 in direct spend but potentially unlimited in breach remediation. A single leaked Stripe key can cost thousands in fraudulent charges. The "free" approach has the highest risk-adjusted cost.
Install APIAuth
# pip
pip install apiauth
# Generate your first key
apiauth generate api-key --name "My First Key" --service "test"
# Import an existing key
apiauth import your_existing_key_value --name "Imported Key" --service "external"
# Verify it
apiauth verify your_existing_key_value
Star APIAuth on GitHub
Related Reading
- Validate API Keys at Runtime: Verify, Import, and Check Revocation — deep dive on verify
- Audit Your API Credentials: Catch Expired and Revoked Keys — keystore health check
- Zero-Downtime API Key Rotation in CI/CD — rotation workflow
- Envault + APIAuth: Rotate Keys Across Environments — cross-tool rotation