Menu

API Key Management

Generating, rotating, scoping, and revoking API keys: key hierarchies, rate limiting per key, usage analytics, and key storage best practices.

10 min read

API Keys — Simple but Consequential

API keys are long random strings (opaque tokens) that identify and authenticate a calling application or user. Unlike OAuth2 access tokens, they are typically long-lived and carry no embedded claims — the server looks up the key in a database to determine permissions. Their simplicity makes them popular for developer-facing APIs (think Stripe, Twilio, OpenAI, GitHub), but that simplicity also demands careful management to avoid security failures.

API key management is not just about generation. It is a complete lifecycle: creation, scoping, distribution, rotation, monitoring, and revocation. Poor management of any phase creates risk.

API Key Architecture

Loading diagram...
API key lifecycle: creation in portal, storage as hash, validation at gateway, usage analytics.

Generating Secure API Keys

A secure API key must be cryptographically random and long enough to resist brute-force. 256 bits (32 bytes) of entropy encoded as hex or base62 is a standard baseline. Key format best practices:

typescript
import crypto from "crypto";

// Generate a cryptographically secure API key
function generateApiKey(prefix: string = "sk_live"): string {
  // 32 bytes = 256 bits of entropy
  const rawBytes = crypto.randomBytes(32);
  // Encode as URL-safe base64 (shorter than hex)
  const keyBody = rawBytes.toString("base64url");
  // Format: prefix_version_body  e.g. sk_live_v1_abc123...
  return `${prefix}_v1_${keyBody}`;
}

// NEVER store the raw key — store only the hash
async function storeApiKey(rawKey: string, userId: string, scopes: string[]) {
  // Use SHA-256 for fast lookup; bcrypt is too slow for per-request hashing
  const keyHash = crypto.createHash("sha256").update(rawKey).digest("hex");

  await db.apiKeys.create({
    keyHash,          // What we store and lookup against
    keyPrefix: rawKey.slice(0, 12) + "...",  // For display only (never full key)
    userId,
    scopes,
    createdAt: new Date(),
    lastUsedAt: null,
    revokedAt: null,
    expiresAt: null,  // Or set an explicit expiry
  });

  // Return the raw key to the user ONCE — it cannot be recovered later
  return rawKey;
}

// Validate an incoming API key
async function validateApiKey(incomingKey: string) {
  const incomingHash = crypto.createHash("sha256").update(incomingKey).digest("hex");
  const keyRecord = await db.apiKeys.findByHash(incomingHash);

  if (!keyRecord || keyRecord.revokedAt || keyRecord.expiresAt < new Date()) {
    throw new Error("Invalid or expired API key");
  }

  await db.apiKeys.updateLastUsed(keyRecord.id); // Async, non-blocking
  return keyRecord;
}
⚠️

Never Store API Keys in Plaintext

Store only a SHA-256 hash of the API key in your database. The raw key is shown to the user exactly once during creation and cannot be recovered. This limits the blast radius of a database breach — an attacker gets hashes, not usable keys. Use SHA-256 (not bcrypt) for API key hashing because bcrypt's slow hashing would add unacceptable latency to per-request validation.

Key Scoping and Hierarchies

Follow the principle of least privilege: every API key should grant only the permissions needed for its purpose. A well-designed key system supports hierarchical scopes:

Key TypeScope ExampleUse Case
Master / Root KeyAll permissionsNever use in code; only for creating sub-keys. Keep in a vault.
Environment Keysk_live_... / sk_test_...Separate keys for production vs staging/test environments
Scoped Service Keyread:orders, write:inventoryPer-service keys scoped to minimum required API endpoints
Per-User Keyuser_id:123, read:own_dataUser-generated keys for accessing their own data only
Webhook Signing Keysign:webhooksUsed to HMAC-sign outgoing webhook payloads, not for API access

Rate Limiting Per API Key

Rate limits should be enforced at the API key level, not just at the IP level, because a single key might be used from many IPs (mobile clients, distributed services) while a single IP might legitimately host many different keys. A common strategy:

  • Sliding window counter in Redis: `INCR key:api_key_hash:minute_bucket`, `EXPIRE` to 60 seconds. Fast and accurate.
  • Token bucket per key: Allows burst traffic up to `burst_size` but sustained rate is capped at `rate_per_second`.
  • Tiered limits by plan: Free tier (100 req/min), Pro tier (1000 req/min), Enterprise tier (custom).
  • Per-endpoint limits: `POST /generate` (10 req/min) vs `GET /status` (1000 req/min) — compute-intensive endpoints need tighter limits.

Key Rotation and Revocation

Loading diagram...
Zero-downtime key rotation: create new key, deploy, then revoke old key.

Key Prefixes for Instant Recognition

A key format like `sk_live_v1_...` or `ghp_...` (GitHub Personal Access Tokens) has an important security benefit beyond aesthetics: secret scanners (GitHub, GitLab, Trufflehog, Gitleaks) can recognize these prefixes and automatically alert or block commits that contain leaked keys. Design your key format to be easily recognizable by automated scanning tools.

📌

Real-World Key Prefix Examples

Stripe: `sk_live_...` / `pk_live_...` | GitHub: `ghp_...` (personal) / `ghs_...` (service) | OpenAI: `sk-...` | Twilio: `ACxxxxxxx` (Account SID) + separate auth token | Anthropic: `sk-ant-...`. Each prefix is registered with GitHub's secret scanning partner program so leaked keys trigger automatic alerts.

Secure Key Storage for Clients

EnvironmentRecommended StorageAvoid
Server / containerEnvironment variables from secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler)Hardcoded in source code, .env committed to git
CI/CD pipelineCI secrets (GitHub Actions Secrets, GitLab CI variables)Pipeline logs, artifact metadata
Mobile appsSecure enclave / keychain, certificate pinningEmbedded in app binary, plaintext on device
Client-side browserDo not put server API keys in browser JS — use a backend proxylocalStorage, window object, source code
💡

Interview Tip

API key management questions come up when designing developer-facing APIs (like 'design a payment API' or 'design an AI inference API'). Key points: (1) hash keys before storing — show you know NOT to use bcrypt for this, (2) scope keys by environment and capability, (3) enforce rate limits at the key level via Redis sliding window, (4) use key prefixes that enable secret scanning. If asked about rotation, describe the two-key overlapping window — this is zero-downtime rotation and shows operational maturity.

📝

Knowledge Check

5 questions

Test your understanding of this lesson. Score 70% or higher to complete.

Ask about this lesson

Ask anything about API Key Management