API Keys — Complete Implementation Guide for Service Auth
In this tutorial, you will learn about API Keys. We cover key concepts, practical examples, and best practices to help you master this topic.
API keys are static tokens used to authenticate service-to-service communication and developer API access, providing a simple but effective authentication mechanism where a shared secret identifies the calling application.
What You'll Learn
By the end of this lesson, you will implement secure API key generation, validation, hashing for storage, rotation policies, and build a complete API key management system with tiers and permissions.
Why It Matters
API keys are the most common authentication method for public developer APIs. Services like Stripe, Twilio, and GitHub use API keys to authenticate millions of API calls daily. Doda Browser's public API uses API keys to authenticate third-party developers building on the platform.
Real-World Use
A developer signs up for a weather API service. The dashboard generates two API keys: a live key for production and a test key for development. The developer includes the key in the X-API-Key header. The API validates the key, checks the rate limit tier, and processes the request.
API Key Security Flow
flowchart LR
A[Developer Portal] -->|Generate| B[API Key: sk_live_abc123]
B --> C[Hash & Store in DB]
B --> D[Show to Developer Once]
D --> E[Developer uses in API calls]
E --> F[Server hashes incoming key]
F --> G[Compare with stored hashes]
G -->|Match| H[Authenticate Request]
G -->|No Match| I[Reject 401]
Secure API Key Generation
import secrets
import hashlib
import hmac
from datetime import datetime, timedelta
class APIKeyManager:
def __init__(self):
self.stored_keys = {}
def generate_key(self, client_name, tier="standard"):
prefix = "sk_live" if tier != "test" else "sk_test"
random_part = secrets.token_urlsafe(32)
api_key = f"{prefix}_{random_part}"
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
key_id = secrets.token_hex(8)
self.stored_keys[key_hash] = {
"key_id": key_id,
"client_name": client_name,
"tier": tier,
"created_at": datetime.utcnow(),
"expires_at": datetime.utcnow() + timedelta(days=365),
"is_active": True,
}
print(f"[APIKey] Key generated for {client_name}")
print(f"[APIKey] Key ID: {key_id}")
print(f"[APIKey] IMPORTANT: Store this key securely - it won't be shown again:")
print(f" {api_key[:12]}...{api_key[-4:]}")
return {"api_key": api_key, "key_id": key_id, "tier": tier}
def validate_key(self, api_key):
key_hash = hashlib.sha256(api_key.encode()).hexdigest()
key_data = self.stored_keys.get(key_hash)
if not key_data:
print(f"[APIKey] Invalid key rejected")
return None
if not key_data["is_active"]:
print(f"[APIKey] Deactivated key rejected")
return None
if datetime.utcnow() > key_data["expires_at"]:
print(f"[APIKey] Expired key rejected")
return None
print(f"[APIKey] Key validated: {key_data['client_name']} ({key_data['tier']})")
return key_data
def revoke_key(self, key_id):
for key_hash, data in self.stored_keys.items():
if data["key_id"] == key_id:
data["is_active"] = False
print(f"[APIKey] Key {key_id} revoked")
return True
return False
def rotate_key(self, old_api_key, client_name):
old_data = self.validate_key(old_api_key)
if not old_data:
return None
self.revoke_key(old_data["key_id"])
return self.generate_key(client_name, old_data["tier"])
manager = APIKeyManager()
result = manager.generate_key("Acme Corp", "premium")
print(f"\nKey preview: {result['api_key'][:12]}...")
validation = manager.validate_key(result["api_key"])
print(f"Validated: {validation['client_name'] if validation else 'FAILED'}")
Expected output:
[APIKey] Key generated for Acme Corp
[APIKey] Key ID: a1b2c3d4
[APIKey] IMPORTANT: Store this key securely...
sk_live_a1b2...c3d4
[APIKey] Key validated: Acme Corp (premium)
Validated: Acme Corp
API Key Validation in Express
const crypto = require("crypto");
class APIKeyAuthenticator {
constructor() {
this.keys = new Map();
}
addKey(apiKey, client) {
const hash = crypto.createHash("sha256").update(apiKey).digest("hex");
this.keys.set(hash, {
clientId: client.id,
clientName: client.name,
tier: client.tier || "standard",
rateLimit: client.rateLimit || 100,
});
}
authenticate(req, res, next) {
const apiKey = req.headers["x-api-key"] || req.query.api_key;
if (!apiKey) {
return res.status(401).json({ error: "API key required" });
}
const hash = crypto.createHash("sha256").update(apiKey).digest("hex");
const keyData = this.keys.get(hash);
if (!keyData) {
console.log(`[Auth] Invalid API key from ${req.ip}`);
return res.status(403).json({ error: "Invalid API key" });
}
req.client = keyData;
console.log(`[Auth] Authenticated: ${keyData.clientName} (${keyData.tier})`);
next();
}
}
const auth = new APIKeyAuthenticator();
auth.addKey("sk_live_test_key_123", {
id: "client_1",
name: "Acme Corp",
tier: "premium",
rateLimit: 10000,
});
module.exports = auth;
Common Mistakes
- Storing API keys in plain text in the database instead of hashed, exposing all keys on data breach.
- Generating predictable keys (sequential, timestamp-based) that can be guessed.
- Embedding API keys in client-side code (mobile apps, SPAs) where they can be extracted.
- Not supporting key rotation forces developers to keep the same key indefinitely.
- Using API keys without Rate Limiting allows a compromised key to abuse the API.
- Logging full API keys in request logs exposes them in the logging pipeline.
Practice Questions
- Why should API keys be hashed before storing in the database?
If the database is breached, plain-text API keys are immediately compromised. Hashing (SHA-256) means the attacker only has hashes, which cannot be reversed to valid keys.
- What is the difference between a key prefix and a key ID?
The prefix identifies the key type (sk_live, sk_test). The key ID is a public identifier used to look up which key made a request (shown in logs, not secret). The full key is the secret.
- How do you support multiple API keys per client?
Allow each client to generate up to N keys (typically 5-10). Each key has its own rate limit, permissions, and can be individually revoked. This enables key rotation without downtime.
- Challenge: Build a complete API key management system with generation, hashing storage, validation, tiers (free/pro/enterprise) with different rate limits, rotation endpoint, and automated expiry warnings.
FAQ
Mini Project: Developer API Key Dashboard
Build a CLI dashboard that manages API keys for multiple clients, showing active keys, usage statistics, expiry dates, and allowing key generation and revocation.
import json
import secrets
import hashlib
from datetime import datetime, timedelta
class APIDashboard:
def __init__(self, storage_path="api_keys.json"):
self.path = storage_path
self.load()
def load(self):
try:
with open(self.path) as f:
self.data = json.load(f)
except FileNotFoundError:
self.data = {"clients": {}, "keys": {}}
def save(self):
with open(self.path, "w") as f:
json.dump(self.data, f, indent=2, default=str)
def list_keys(self):
print(f"{'Client':<20} {'Key ID':<12} {'Tier':<10} {'Status':<10} {'Expires':<15}")
print("=" * 70)
for hash_val, key_data in self.data["keys"].items():
print(f"{key_data['client']:<20} {key_data['key_id']:<12} {key_data['tier']:<10} {'Active' if key_data['active'] else 'Revoked':<10} {key_data['expires']:<15}")
def generate(self, client, tier="standard"):
key = f"sk_live_{secrets.token_urlsafe(32)}"
key_hash = hashlib.sha256(key.encode()).hexdigest()
key_id = secrets.token_hex(6)
self.data["keys"][key_hash] = {
"key_id": key_id, "client": client, "tier": tier,
"active": True, "created": str(datetime.utcnow()),
"expires": str(datetime.utcnow() + timedelta(days=365)),
}
self.save()
print(f"Generated key: {key[:12]}...{key[-4:]} (ID: {key_id})")
print("Store this key securely - it will not be shown again")
def revoke(self, key_id):
for k, v in self.data["keys"].items():
if v["key_id"] == key_id:
v["active"] = False
self.save()
print(f"Key {key_id} revoked")
return
print("Key not found")
What's Next
Learn about Basic authentication for legacy compatibility, then explore token refresh patterns for managing long-lived sessions.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro