ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

API Key Management

Secure the lifecycle of API keys from generation to revocation. Covers key generation best practices, rotation policies, scope limiting, usage monitoring, and the patterns that prevent API key compromise from becoming a security incident.

API keys are the most common authentication mechanism for service-to-service communication and developer APIs. They are also the most commonly leaked credential — committed to public GitHub repos, embedded in client-side code, shared in Slack messages, and stored in plain text configuration files. API key management is the discipline of generating, distributing, monitoring, and revoking keys securely.


Key Lifecycle

API Key Lifecycle:

1. Generation:
   ✓ Cryptographically random (32+ bytes, base64url encoded)
   ✓ Include prefix for identification: sk_live_xxx, pk_test_xxx
   ✓ Hash before storing (bcrypt/argon2) — never store plaintext
   ✗ Do NOT use sequential IDs, timestamps, or predictable patterns

2. Distribution:
   ✓ Show key exactly once at creation time
   ✓ Deliver via secure channel (not email, not Slack)
   ✓ Environment variables or secret manager
   ✗ Never embed in source code

3. Scoping:
   ✓ Minimum permissions required
   ✓ Rate limits per key
   ✓ IP allowlisting where possible
   ✓ Environment separation (test vs. production)

4. Monitoring:
   ✓ Log all key usage (IP, endpoint, timestamp)
   ✓ Alert on anomalous usage patterns
   ✓ Track unused keys (> 90 days unused = candidate for revocation)

5. Rotation:
   ✓ Scheduled rotation (90 days recommended)
   ✓ Support overlapping validity (old key works during transition)
   ✓ Automated rotation with secret manager integration

6. Revocation:
   ✓ Immediate revocation capability
   ✓ Revoke on suspected compromise
   ✓ Audit trail of revocation reason and timestamp

Implementation

import secrets
import hashlib
from datetime import datetime, timedelta

class APIKeyManager:
    """Secure API key lifecycle management."""
    
    def generate_key(self, owner: str, scopes: list,
                     environment: str = "live"):
        """Generate a new API key with proper security."""
        
        # 1. Generate cryptographically random key
        random_bytes = secrets.token_bytes(32)
        key_suffix = secrets.token_urlsafe(32)
        
        # 2. Add identifiable prefix
        prefix = f"sk_{environment}_"
        full_key = f"{prefix}{key_suffix}"
        
        # 3. Hash for storage (NEVER store plaintext)
        key_hash = hashlib.sha256(full_key.encode()).hexdigest()
        
        # 4. Store metadata
        self.db.insert("api_keys", {
            "key_hash": key_hash,
            "prefix": prefix + key_suffix[:8],  # Store prefix for identification
            "owner": owner,
            "scopes": scopes,
            "environment": environment,
            "created_at": datetime.utcnow(),
            "expires_at": datetime.utcnow() + timedelta(days=90),
            "last_used_at": None,
            "status": "active",
        })
        
        # 5. Return key exactly once — cannot be retrieved again
        return {
            "key": full_key,  # Show ONCE, then never again
            "prefix": prefix + key_suffix[:8],
            "expires_at": (datetime.utcnow() + timedelta(days=90)).isoformat(),
            "scopes": scopes,
        }
    
    def validate_key(self, provided_key: str):
        """Validate an API key and check permissions."""
        key_hash = hashlib.sha256(provided_key.encode()).hexdigest()
        
        record = self.db.find("api_keys", {"key_hash": key_hash})
        if not record:
            return {"valid": False, "reason": "Key not found"}
        
        if record["status"] != "active":
            return {"valid": False, "reason": f"Key is {record['status']}"}
        
        if record["expires_at"] < datetime.utcnow():
            return {"valid": False, "reason": "Key expired"}
        
        # Update last used timestamp
        self.db.update("api_keys", {"key_hash": key_hash}, {
            "last_used_at": datetime.utcnow(),
        })
        
        return {
            "valid": True,
            "owner": record["owner"],
            "scopes": record["scopes"],
        }

Anti-Patterns

Anti-PatternConsequenceFix
API keys in source codeKey leaked if repo is public or clonedEnvironment variables or secret manager
Store plaintext keys in databaseDatabase breach = all keys compromisedStore only hashed keys
No key rotation policyOld keys accumulate, attack surface grows90-day rotation with automated reminders
Single key with all permissionsCompromise = full accessScoped keys with minimum required permissions
No usage monitoringCannot detect compromised key usageLog all key usage, alert on anomalies

API key security is only as strong as its weakest lifecycle stage. A properly generated key stored in a Slack message is compromised. A well-protected key with no rotation policy becomes a ticking time bomb. Secure the entire lifecycle, not just generation.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →