API Key Management at the Gateway
In this tutorial, you'll learn about API Key Management. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
API key management involves generating, storing, validating, rotating, and revoking API keys that clients use to authenticate with the gateway.
What You'll Learn
By the end of this lesson, you will implement API key generation, secure hashing, key rotation, revocation, and tier-based Rate Limiting.
Why It Matters
Poorly managed API keys lead to security breaches. Stolen keys give attackers access to your API. Proper management limits damage and enables key lifecycle control.
Real-World Use
The gateway manages thousands of API keys, each tied to a customer tier. Keys are hashed before storage, can be rotated without downtime, and revoked instantly if compromised.
API Key Lifecycle
flowchart LR
Generate[Generate Key] --> Hash[Hash & Store]
Hash --> Issue[Issue to Client]
Issue --> Validate[Validate at Gateway]
Validate -->|Compromised| Revoke[Revoke Key]
Validate -->|Expired| Rotate[Rotate Key]
Secure Key Generation
# key_generation.py
import secrets
import hashlib
import hmac
from typing import Dict, Optional, Tuple
class APIKeyGenerator:
def __init__(self):
self.stored_keys: Dict[str, dict] = {}
def generate_key(self, client_name: str, tier: str = "free") -> dict:
raw_key = f"sk_live_{secrets.token_hex(24)}"
key_hash = self._hash_key(raw_key)
key_id = hashlib.sha256(raw_key.encode()).hexdigest()[:12]
self.stored_keys[key_id] = {
"hash": key_hash,
"client": client_name,
"tier": tier,
"active": True,
"created": "2026-06-28",
}
return {
"key_id": key_id,
"api_key": raw_key,
"tier": tier,
"warning": "Store this key securely. It will not be shown again.",
}
def _hash_key(self, key: str) -> str:
salt = secrets.token_hex(16)
hashed = hashlib.pbkdf2_hmac("sha256", key.encode(), salt.encode(), 100000)
return f"{salt}${hashed.hex()}"
def validate_key(self, raw_key: str) -> Tuple[bool, Optional[Dict]]:
key_id = hashlib.sha256(raw_key.encode()).hexdigest()[:12]
stored = self.stored_keys.get(key_id)
if not stored or not stored["active"]:
return False, None
salt, stored_hash = stored["hash"].split("$")
computed = hashlib.pbkdf2_hmac("sha256", raw_key.encode(), salt.encode(), 100000)
if computed.hex() != stored_hash:
return False, None
return True, {"client": stored["client"], "tier": stored["tier"]}
def revoke_key(self, key_id: str) -> bool:
if key_id in self.stored_keys:
self.stored_keys[key_id]["active"] = False
return True
return False
def rotate_key(self, key_id: str) -> Optional[Dict]:
if key_id not in self.stored_keys:
return None
client_name = self.stored_keys[key_id]["client"]
tier = self.stored_keys[key_id]["tier"]
self.revoke_key(key_id)
return self.generate_key(client_name, tier)
generator = APIKeyGenerator()
result = generator.generate_key("Acme Corp", "enterprise")
print(f"Generated key for {result['tier']} tier:")
print(f" Key ID: {result['key_id']}")
print(f" API Key: {result['api_key'][:20]}...")
is_valid, info = generator.validate_key(result["api_key"])
print(f" Valid: {is_valid}, Client: {info['client']}")
generator.revoke_key(result["key_id"])
is_valid, _ = generator.validate_key(result["api_key"])
print(f" After revoke - Valid: {is_valid}")
Expected output:
Generated key for enterprise tier:
Key ID: a1b2c3d4e5f6
API Key: sk_live_a1b2c3...
Valid: True, Client: Acme Corp
After revoke - Valid: False
Key Rotation Without Downtime
# key_rotation.py
import time
from typing import Dict, Optional, Tuple
class KeyRotationManager:
def __init__(self, overlap_minutes: int = 60):
self.overlap_seconds = overlap_minutes * 60
self.active_keys: Dict[str, dict] = {}
self.grace_keys: Dict[str, dict] = {}
def register_key(self, key_hash: str, client: str, tier: str):
self.active_keys[key_hash] = {
"client": client,
"tier": tier,
"created": time.time(),
}
def rotate(self, old_hash: str, new_hash: str):
if old_hash in self.active_keys:
info = self.active_keys.pop(old_hash)
info["expires"] = time.time() + self.overlap_seconds
self.grace_keys[old_hash] = info
self.active_keys[new_hash] = {
"client": info["client"],
"tier": info["tier"],
"created": time.time(),
}
def validate(self, key_hash: str) -> Tuple[bool, Optional[Dict]]:
if key_hash in self.active_keys:
return True, self.active_keys[key_hash]
if key_hash in self.grace_keys:
if time.time() < self.grace_keys[key_hash]["expires"]:
return True, self.grace_keys[key_hash]
else:
del self.grace_keys[key_hash]
return False, None
def cleanup(self):
now = time.time()
expired = [k for k, v in self.grace_keys.items() if v["expires"] < now]
for k in expired:
del self.grace_keys[k]
return len(expired)
manager = KeyRotationManager(overlap_minutes=1)
manager.register_key("hash_old", "Acme Corp", "enterprise")
manager.rotate("hash_old", "hash_new")
print("After rotation:")
print(f" Old key valid: {manager.validate('hash_old')[0]}")
print(f" New key valid: {manager.validate('hash_new')[0]}")
Expected output:
After rotation:
Old key valid: True
New key valid: True
Common Mistakes
1. Storing Keys in Plain Text
Never store API keys in plain text. Hash them with a strong algorithm (bcrypt, PBKDF2) before storage.
2. Exposing Keys in Logs
API keys in query parameters or headers can appear in access logs. Mask or redact keys in logging.
3. No Key Expiration
Keys that never expire become a permanent security risk. Implement key rotation policies (90-180 days).
4. Single Key per Customer
Each customer should have multiple keys (primary, secondary) for rotation without downtime.
5. Weak Key Generation
Using predictable keys (sequential IDs, timestamps) allows attackers to guess valid keys. Use cryptographically random keys.
Practice Questions
1. Why should API keys be hashed before storage?
If the database is breached, hashed keys cannot be used to access the API. Plain text keys would be immediately compromised.
2. How do you rotate API keys without downtime?
Issue a new key while keeping the old key valid for a grace period. Clients switch to the new key during the overlap.
3. What is a key revocation list?
A list of compromised or revoked keys that the gateway checks before every request. Can be stored in Redis for fast access.
4. How do you associate API keys with rate limits?
When generating a key, assign it a tier (free, pro, enterprise). The gateway checks the tier and applies corresponding rate limits.
Challenge
Design an API key management system that supports key generation with tier assignment, secure hashing, rotation with grace period, instant revocation, and rate limit association.
FAQ
Mini Project: Key Management System
# key_management.py
import secrets
import hashlib
from typing import Dict, Optional, Tuple
class KeyManagementSystem:
def __init__(self):
self.keys: Dict[str, dict] = {}
def create_key(self, client: str, tier: str = "free", prefix: str = "sk_live") -> str:
raw = f"{prefix}_{secrets.token_hex(24)}"
key_hash = hashlib.sha256(raw.encode()).hexdigest()
self.keys[key_hash] = {
"client": client,
"tier": tier,
"active": True,
"created": "2026-06-28",
"prefix": prefix,
}
return raw
def validate(self, raw_key: str) -> Tuple[bool, Optional[Dict]]:
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
entry = self.keys.get(key_hash)
if not entry or not entry["active"]:
return False, None
return True, {"client": entry["client"], "tier": entry["tier"]}
def revoke(self, raw_key: str) -> bool:
key_hash = hashlib.sha256(raw_key.encode()).hexdigest()
if key_hash in self.keys:
self.keys[key_hash]["active"] = False
return True
return False
def list_keys(self, client: str) -> list:
return [
{"tier": v["tier"], "active": v["active"], "created": v["created"]}
for k, v in self.keys.items() if v["client"] == client
]
kms = KeyManagementSystem()
key1 = kms.create_key("Acme Corp", "enterprise")
key2 = kms.create_key("Startup Inc", "free")
valid, info = kms.validate(key1)
print(f"Key 1 valid: {valid}, tier: {info['tier']}")
kms.revoke(key1)
valid, _ = kms.validate(key1)
print(f"Key 1 after revoke: {valid}")
acme_keys = kms.list_keys("Acme Corp")
print(f"Acme Corp keys: {len(acme_keys)}")
Expected output:
Key 1 valid: True, tier: enterprise
Key 1 after revoke: False
Acme Corp keys: 1
What's Next
You understand API key management. Next, learn about IP whitelisting, then explore circuit breaker pattern.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro