Authentication & Authorization#
API-key-based authentication with per-endpoint policies, rate limiting, audit trail, and Prometheus metrics.
Enabled by default: LLM_ROUTER_AUTH_ENABLED=false — set to "true" to enforce authentication.
Architecture#
Client Request → AuthMiddleware → Key Store Lookup → Permission Engine → Rate Limiter → Endpoint
→ Audit Bridge → AnyRequestAuditor
→ AuthMetrics → Prometheus
Components#
| Component | Module | Purpose |
|---|---|---|
| Key Store | core/auth/key_store/ |
Vault, Redis, or in-memory key storage |
| Permission Engine | core/auth/policies/engine.py |
Resolve key → policy → endpoint permissions |
| Rate Limiter | core/auth/rate_limiter.py |
Redis-backed sliding window rate limiter |
| Key Generator | core/auth/key_generator.py |
Generate keys in sk-litm- format |
| Audit Bridge | core/auth/audit.py |
Bridge auth events → AnyRequestAuditor |
| Metrics | core/auth/metrics.py |
Prometheus counters & histograms for auth |
| Middleware | core/auth/middleware.py |
Flask before_request hook |
Environment Variables#
All environment variables are documented in ENV_DEFINITIONS.md. Auth-specific vars start
with LLM_ROUTER_AUTH_*.
CLI Commands#
Key Management#
# Generate a new key (persists to seed file when --store memory)
llm-router auth key generate --policy developer --store memory
# List all keys
llm-router auth key list --store memory
# List with plaintext keys visible
llm-router auth key list --store memory --reveal
# Delete a key
llm-router auth key delete key-id
# Disable a key (revokes access immediately)
llm-router auth key disable key-id [--store memory]
# Enable a previously disabled key
llm-router auth key enable key-id [--store memory]
# Rotate a key (old key stays valid for grace_period)
llm-router auth key rotate key-id --grace 3600
# Reveal plaintext key (only in memory store)
llm-router auth key reveal key-id
Policy Management#
# List builtin policies
llm-router auth policy list
# Create a new policy
llm-router auth policy create my-team '{"can_access": true, "rate_limit": 120}'
Rate Limit#
# List available rate-limit presets
llm-router auth rate-limit list
# Apply a rate-limit preset to an existing key
llm-router auth rate-limit apply key-id --preset pro
# Remove rate-limit override from a key (revert to global default)
llm-router auth rate-limit remove key-id
Seed File (Memory Store)#
When using --store memory, keys are stored in process memory — they are lost on restart. To persist keys across
restarts (and between the CLI and router processes), use a seed file.
Seed File Format#
The seed file is a JSON array. Each record must have key_plain and optionally the following fields (matching the
ApiKeyRecord dataclass):
| Field | Type | Description |
|---|---|---|
key_id |
str |
Unique identifier for this key |
key_plain |
str |
The plaintext API key (generated by CLI or provided manually) |
key_prefix |
str |
First 7 characters of the plaintext key (auto-generated if omitted) |
policy_name |
str |
Name of the default policy to apply |
policy_override |
dict |
Inline policy override (takes precedence over the named policy) |
is_active |
bool |
Whether the key is currently valid |
expires_at |
float |
Expiry timestamp (null = no expiry) |
created_at |
float |
Unix timestamp of key creation (auto-generated if omitted) |
last_used_at |
float |
Last successful authentication time |
rotate_at |
float |
Scheduled rotation time |
grace_until |
float |
Key remains valid until this time after rotation |
metadata |
dict |
Arbitrary metadata (team, cost_center, etc.) |
[
{
"key_id": "manual-key-001",
"key_plain": "sk-litm-my-key-1234567890abcdefghij",
"policy_name": "developer",
"is_active": true,
"expires_at": null,
"created_at": 1718000000,
"last_used_at": null,
"rotate_at": null,
"grace_until": null,
"metadata": {},
"policy_override": {
"rate_limit": 300
}
},
{
"key_id": "manual-key-002",
"key_plain": "sk-litm-my-readonly-key-0987654321zyxwvutsrq",
"policy_name": "chat",
"is_active": true,
"expires_at": 1750000000,
"created_at": 1718000000,
"last_used_at": null,
"rotate_at": null,
"grace_until": null,
"metadata": {
"team": "backend"
}
}
]
How It Works#
- On router startup:
MemoryKeyStorereads the seed file and loads all keys into memory. - After CLI key operations:
generate,delete, androtateautomatically write back to the seed file. - No restart needed: Changes made via CLI are visible to the router on the next request (the router reloads the seed file on each request via the middleware).
Default Location#
~/.llm-router/configs/auth/memory-keys.json
Permission Engine#
The permission engine resolves key → policy → endpoint permissions:
- Public endpoints — always bypass auth (health checks, version, etc.)
- Key authentication — bcrypt hash lookup in key store
- Key validity — check expiry, rotation, grace period
- Policy resolution — named policy or inline override
- Endpoint permission — per-endpoint + per-model check
- Rate limit — sliding window check per key+IP
Builtin Policies#
| Policy | Access | Description |
|---|---|---|
developer |
All | Full access to all endpoints |
admin |
All | Admin access |
chat |
Chat | Chat completion endpoints |
embedding |
Embedding | Embedding endpoints |
anthropic |
Anthropic | Anthropic messages endpoint |
ollama |
Ollama | Ollama endpoints |
builtin |
All builtin | Built-in endpoints (translate, etc.) |
Prometheus Metrics#
Auth metrics are registered when LLM_ROUTER_USE_PROMETHEUS=true:
| Metric | Type | Labels | Description |
|---|---|---|---|
auth_attempts_total |
Counter | result, key_id |
Total auth attempts by result |
auth_latency_seconds |
Histogram | step |
Latency per auth step |
rate_limit_exceeded_total |
Counter | key_id, endpoint |
Rate limit events |
key_budget_usage_tokens |
Gauge | key_id, budget_total |
Token budget usage |
Key Format#
Generated keys follow the sk-litm-<base62> format:
sk-litm-aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789abcdefABCDEF123456789
- Prefix:
sk-litm-(configurable viaLLM_ROUTER_AUTH_KEY_PREFIX) - Entropy: 48 bytes cryptographically random (configurable via
LLM_ROUTER_AUTH_KEY_LENGTH) - Charset: base62 (a-zA-Z0-9)
Deployment Options#
Choose a key store backend based on your deployment environment. Each option is documented below with full environment configuration, key management commands, and operational notes.
1️⃣ In-Memory Store — Development / Quick Start#
Use when: You want to try auth quickly, run locally, or have a single-process deployment.
Pros: Zero external dependencies, instant setup. Seed file provides persistence across restarts. Cons: Still per-process — not multi-process safe even with seed file.
# 1. Create seed file (keys persist to disk)
mkdir -p ~/.llm-router/configs/auth
cat > ~/.llm-router/configs/auth/memory-keys.json << 'EOF'
[
{ "key_plain": "sk-litm-my-dev-key", "policy_name": "developer" }
]
EOF
# 2. Set environment variables
export LLM_ROUTER_AUTH_ENABLED=true
export LLM_ROUTER_AUTH_KEY_STORE=memory
# 3. Verify keys are loaded
llm-router auth key list --store memory --reveal
# 4. Generate more keys (auto-persisted to seed file)
llm-router auth key generate --policy readonly --store memory
# 5. Run router (keys loaded automatically from seed file)
python -m llm_router_api.rest_api
# 6. Use the key
curl -H "x-api-key: sk-litm-my-dev-key" https://host/api/chat/completions
| Variable | Value |
|---|---|
LLM_ROUTER_AUTH_ENABLED |
true |
LLM_ROUTER_AUTH_KEY_STORE |
memory |
Operational notes:
- Seed file auto-updates after
generate,delete, androtate. - Keys are visible to both CLI and router via the same seed file.
- Suitable for development, CI, and single-node deployments.
- No Redis or Vault required.
2️⃣ Redis Store — Multi-Process / Stateful Single-Node#
Use when: You run multiple workers/processes and need persistent key storage without Vault.
Pros: Persistent keys across restarts (if Redis is durable), multi-process safe. Cons: Requires Redis instance. No encryption-at-rest (use TLS for production).
# 1. Start Redis
redis-server
# 2. Set environment variables
export LLM_ROUTER_AUTH_ENABLED=true
export LLM_ROUTER_AUTH_KEY_STORE=redis
export LLM_ROUTER_AUTH_REDIS_HOST=127.0.0.1
export LLM_ROUTER_AUTH_REDIS_PORT=6379
export LLM_ROUTER_AUTH_REDIS_DB=0
# export LLM_ROUTER_AUTH_REDIS_PASSWORD=secret # if Redis requires auth
# 3. Generate keys (written to Redis)
llm-router auth key generate --policy developer --store redis
llm-router auth key list --store redis
# 4. Run router (reads keys from same Redis instance)
python -m llm_router_api.rest_api
# 5. Use the key
curl -H "x-api-key: sk-litm-..." https://host/api/chat/completions
| Variable | Value |
|---|---|
LLM_ROUTER_AUTH_ENABLED |
true |
LLM_ROUTER_AUTH_KEY_STORE |
redis |
LLM_ROUTER_AUTH_REDIS_HOST |
Redis host (default 127.0.0.1) |
LLM_ROUTER_AUTH_REDIS_PORT |
Redis port (default 6379) |
LLM_ROUTER_AUTH_REDIS_DB |
Redis DB number (default 0) |
LLM_ROUTER_AUTH_REDIS_PASSWORD |
Redis password (default: none) |
Operational notes:
- Keys stored as JSON strings in Redis under
secret:llm-router:api-keys:<key_id>. - Works across multiple Flask workers — all workers see the same key set.
- Redis is already required for load balancing (same instance works).
- For production, use Redis Sentinel or a managed Redis service with TLS.
3️⃣ HashiCorp Vault — Production / Multi-Cluster / Enterprise#
Use when: You need enterprise-grade key management, secret rotation, or multi-cluster consistency.
Pros: Centralized key management, encryption, RBAC, audit logging. Cons: Requires Vault infrastructure and auth method configuration.
3a. Kubernetes Auth (recommended for K8s deployments)#
# 1. Set environment variables
export LLM_ROUTER_AUTH_ENABLED=true
export LLM_ROUTER_AUTH_KEY_STORE=vault
export LLM_ROUTER_AUTH_VAULT_ADDR=https://vault.example.com
export LLM_ROUTER_AUTH_VAULT_PATH=secret/data/llm-router/api-keys
export LLM_ROUTER_AUTH_VAULT_AUTH_METHOD=kubernetes
# The K8s service account JWT is read from:
# /var/run/secrets/kubernetes.io/serviceaccount/token (hardcoded)
# 2. Generate and use keys
llm-router auth key generate --policy developer --store vault
llm-router auth key list --store vault
curl -H "x-api-key: sk-litm-..." https://host/api/chat/completions
| Variable | Value |
|---|---|
LLM_ROUTER_AUTH_ENABLED |
true |
LLM_ROUTER_AUTH_KEY_STORE |
vault |
LLM_ROUTER_AUTH_VAULT_ADDR |
Vault server URL |
LLM_ROUTER_AUTH_VAULT_PATH |
KV v2 mount path (default: secret/data/llm-router/api-keys) |
LLM_ROUTER_AUTH_VAULT_AUTH_METHOD |
kubernetes |
LLM_ROUTER_AUTH_VAULT_ROLE_ID |
(optional) Vault role ID |
Operational notes:
- Vault K8s auth reads the service account JWT automatically.
- Keys stored under
{vault_path}/{key_id}/datain KV v2 format. - On rotation the old key is deactivated (
is_active=False) and a new key is written. - Ensure the Vault Role has
readandcreatepolicies for the mount path.
3b. AppRole Auth (non-K8s / VM deployments)#
export LLM_ROUTER_AUTH_ENABLED=true
export LLM_ROUTER_AUTH_KEY_STORE=vault
export LLM_ROUTER_AUTH_VAULT_ADDR=https://vault.example.com
export LLM_ROUTER_AUTH_VAULT_AUTH_METHOD=approle
export LLM_ROUTER_AUTH_VAULT_ROLE_ID=your-approle-role-id
export LLM_ROUTER_AUTH_VAULT_SECRET_ID=your-approle-secret-id
3c. Token Auth (manual / CI pipelines)#
export LLM_ROUTER_AUTH_ENABLED=true
export LLM_ROUTER_AUTH_KEY_STORE=vault
export LLM_ROUTER_AUTH_VAULT_ADDR=https://vault.example.com
export LLM_ROUTER_AUTH_VAULT_AUTH_METHOD=token
export LLM_ROUTER_AUTH_VAULT_TOKEN=s.your-vault-token-here
Operational notes:
- For production, use AppRole or K8s auth — token auth is less secure.
- Vault path is configurable per environment (e.g.,
secret/data/llm-router-staging/api-keys). - Supports key rotation:
llm-router auth key rotate key-id --grace 3600.
Comparison Matrix#
| Feature | Memory (+seed) | Redis | Vault (K8s) | Vault (AppRole) |
|---|---|---|---|---|
| Persistence | ✅ Yes (seed file) | ✅ Yes (RDB/AOF) | ✅ Yes | ✅ Yes |
| Multi-process safe | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes |
| Encryption-at-rest | ❌ No | ⚠️ No (app does not configure SSL/TLS — use managed Redis with TLS) | ✅ Yes (Vault native) | ✅ Yes (Vault native) |
| Secret rotation | Manual | Manual | ✅ Automatic + manual | ✅ Automatic + manual |
| External dependency | None | Redis | Vault + K8s SA token | Vault + AppRole creds |
| Production ready | ❌ Dev only | ✅ Yes | ✅ Yes | ✅ Yes |
| Audit logging | ❌ No | ❌ No | ✅ Yes (Vault audit) | ✅ Yes (Vault audit) |
Recommendation:
- Dev / testing: Use
memorywith seed file (zero setup). - Staging / small production: Use
redis(already required for load balancing). -
Multi-cluster / enterprise: Use
vaultwith K8s or AppRole auth. -
Keys are stored as bcrypt hashes — plaintext is never persisted in the store
- The plaintext key is returned only once at creation time
- Rate limiting prevents brute-force attacks even if key hashes leak
- Audit bridge records all auth events (success, failure, rate limit) for compliance
See Also#
- Rate Limiting — sliding-window rate limiting, configuration, and monitoring
- Auditing subsystem — tamper-evident audit logging