Skip to content

API Key Management at the Gateway

DodaTech Updated 2026-06-28 6 min read

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

How long should API keys be?

At least 32 bytes (64 hex chars) of cryptographically random data. Longer keys are more secure against brute force.

Should API keys expire?

Yes. Set expiration based on security requirements: 90 days for standard, 30 days for high-security environments.

How many keys should each customer have?

At least two (primary and secondary) so they can rotate without downtime. Some customers may need more for different services.

Can I use UUIDs as API keys?

UUIDs are not cryptographically random. Generate keys using secrets.token_hex or similar cryptographic RNG.

What format should API keys follow?

Use a prefix to identify the key type: sk_live_ for production, sk_test_ for testing, followed by the random portion.

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