An extremely simple API key validation service with S3 storage and CLI management.
- API Keys: Manage via CLI, stored in S3 as a single JSON file
- Validation: Single
/verifyendpoint that accepts API key and returns metadata - In-Memory: Fast lookups with S3 refresh on demand
- Logging: Structured logs via structlog tracking key IDs and user agents
- IDs: Uses
heare-idsfor both key IDs and secrets
# Install dependencies
uv sync
# Or with pip
pip install -e .Set environment variables:
export S3_BUCKET=heare-auth-keys
export S3_REGION=us-east-1
export AWS_ACCESS_KEY_ID=your_key
export AWS_SECRET_ACCESS_KEY=your_secret
# Optional: Encrypt data at rest in S3
export STORAGE_SECRET=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))")Encryption Notes:
- If
STORAGE_SECRETis set, all data written to S3 will be encrypted using Fernet encryption (AES-128) - The system supports transitioning from unencrypted to encrypted storage - it can read both formats
- Secret format: Any string works, but use 32+ characters with high entropy
- Generate:
python3 -c "import secrets; print(secrets.token_urlsafe(32))" ⚠️ Important: Back up this secret securely - if lost, you cannot decrypt your data!
To enable metrics collection via heare-stats-client:
export PROTOCOL=http
export DEST_HOST=stats-bridge.dokku.heare.io
export DEST_PORT=443
export SECRET=your_metrics_secretThe service will track:
heare-auth.verify.requests- Total verification requestsheare-auth.verify.success- Successful verificationsheare-auth.verify.failed- Failed verificationsheare-auth.verify.duration- Verification response time (ms)heare-auth.refresh.requests- Total refresh requestsheare-auth.refresh.success- Successful refreshesheare-auth.keys.count- Current number of loaded keysheare-auth.startup.*- Startup metricsheare-auth.health.requests- Health check requests
heare-auth create --name "My Service" --metadata '{"env": "prod"}'Output:
Created API key:
ID: key_A1h2xcejqtf2nbrexx3vqjhp41
Secret: sec_A1h2xdfjqtf2nbrexx3vqjhp42
Name: My Service
Secret Type: shared_secret
Created: 2024-01-20T10:30:00Z
Expires: Never
✓ Service refreshed - 5 keys loaded
⚠️ Save the SECRET securely - it will not be shown again!
Use the ID for reference and logging.
Note: When run from inside the container, the service automatically refreshes to load the new key.
uvicorn heare_auth.main:app --host 0.0.0.0 --port 8080cURL:
curl -X POST http://localhost:8080/verify \
-H "Content-Type: application/json" \
-H "User-Agent: MyService/1.0" \
-d '{"api_key": "sec_A1h2xdfjqtf2nbrexx3vqjhp42"}'Python:
import requests
def verify_api_key(api_key: str, service_url: str = "http://localhost:8080") -> dict:
"""
Verify an API key against the heare-auth service.
Args:
api_key: The API key secret to verify
service_url: Base URL of the heare-auth service
Returns:
Dict with verification result
Raises:
requests.HTTPError: If verification fails
"""
response = requests.post(
f"{service_url}/verify",
json={"api_key": api_key},
headers={"User-Agent": "MyService/1.0"},
timeout=5
)
response.raise_for_status()
return response.json()
# Usage
try:
result = verify_api_key("sec_A1h2xdfjqtf2nbrexx3vqjhp42")
if result["valid"]:
print(f"✓ Valid key: {result['name']}")
print(f" Key ID: {result['key_id']}")
print(f" Metadata: {result['metadata']}")
else:
print(f"✗ Invalid key: {result.get('error')}")
except requests.HTTPError as e:
print(f"✗ Verification failed: {e}")Response:
{
"valid": true,
"key_id": "key_A1h2xcejqtf2nbrexx3vqjhp41",
"name": "My Service",
"metadata": {
"env": "prod"
}
}# Basic key
heare-auth create --name "Key Name"
# With metadata
heare-auth create --name "Key Name" --metadata '{"key": "value"}'
# With expiration (ISO 8601 format)
heare-auth create --name "Key Name" --expires-at "2025-12-31T23:59:59Z"
# With specific secret type (currently only shared_secret)
heare-auth create --name "Key Name" --secret-type shared_secretThe service will automatically refresh after creating the key (when run from inside the container).
# Simple list
heare-auth list
# Detailed view with all fields
heare-auth list --detailedheare-auth show key_A1h2xcejqtf2nbrexx3vqjhp41heare-auth delete key_A1h2xcejqtf2nbrexx3vqjhp41The service will automatically refresh after deleting the key (when run from inside the container).
If needed, you can manually refresh:
Local development:
heare-auth refreshDokku deployment (from host):
dokku enter auth web heare-auth refreshTo skip automatic refresh, use --no-refresh:
heare-auth create --name "Key Name" --no-refresh
heare-auth delete key_xxx --no-refreshValidate an API key and return its metadata.
Request:
{
"api_key": "sec_..."
}Response (200 OK):
{
"valid": true,
"key_id": "key_...",
"name": "Service Name",
"metadata": {}
}Response (403 Forbidden):
{
"valid": false,
"error": "Invalid API key"
}Reload keys from S3 (localhost only).
Health check endpoint. Returns minimal status information without revealing service details.
Response (200 OK):
{
"status": "ok"
}- Storage: Single
keys.jsonfile in S3 - In-Memory: All keys loaded on startup for fast lookups
- Refresh: Manual refresh via localhost endpoint (CLI triggers this)
- IDs: Each key has two heare-ids:
key_*- Key ID for logging and referencesec_*- Secret for authentication
Structured JSON logs via structlog:
Successful verification:
{
"event": "verification_success",
"key_id": "key_...",
"key_name": "Service Name",
"user_agent": "MyService/1.0",
"timestamp": "2024-01-20T15:30:00Z",
"level": "info"
}Failed verification:
{
"event": "verification_failed",
"secret_prefix": "sec_",
"user_agent": "UnknownClient/1.0",
"timestamp": "2024-01-20T15:30:00Z",
"level": "warning"
}Note: Secrets are NEVER logged - only key IDs.
# Create app
dokku apps:create heare-auth
# Set environment
dokku config:set heare-auth \
S3_BUCKET=heare-auth-keys \
S3_REGION=us-east-1 \
AWS_ACCESS_KEY_ID=xxx \
AWS_SECRET_ACCESS_KEY=yyy
# Deploy
git push dokku maindocker build -t heare-auth .
docker run -p 8080:8080 \
-e S3_BUCKET=heare-auth-keys \
-e S3_REGION=us-east-1 \
-e AWS_ACCESS_KEY_ID=xxx \
-e AWS_SECRET_ACCESS_KEY=yyy \
heare-auth# Install dependencies
uv sync
# Run tests
pytest
# Run server (dev mode)
uvicorn heare_auth.main:app --reload
# Format code
ruff format .
# Lint code
ruff check .See DESIGN.md for full design documentation.
MIT