API Key Management: Secure Generation, Storage, and Rotation
Whitespots Team ·
api
api-keys
authentication
secrets
Introduction
API keys are critical credentials that require careful management. Poor API key practices lead to unauthorized access, data breaches, and service abuse. This guide covers secure API key management from generation to rotation.
Secure API Key Generation
javascriptconst crypto = require('crypto'); function generateApiKey() { // Generate cryptographically secure random key const key = crypto.randomBytes(32).toString('base64url'); const prefix = 'sk_live_'; // Identifiable prefix return `${prefix}${key}`; } // Hash key for storage function hashApiKey(apiKey) { return crypto .createHash('sha256') .update(apiKey) .digest('hex'); } // Create API key app.post('/api/keys', authenticate, async (req, res) => { const { name, scopes } = req.body; const apiKey = generateApiKey(); const keyHash = hashApiKey(apiKey); const record = await db.apiKeys.create({ userId: req.user.id, name, keyHash, scopes: scopes || [], createdAt: new Date(), lastUsedAt: null }); // Return key only once res.json({ id: record.id, key: apiKey, // Show only on creation! name, scopes }); });
API Key Authentication
javascript// API key middleware async function authenticateApiKey(req, res, next) { const authHeader = req.headers['authorization']; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Missing API key' }); } const apiKey = authHeader.substring(7); const keyHash = hashApiKey(apiKey); const record = await db.apiKeys.findOne({ keyHash, active: true, expiresAt: { $gt: new Date() } }); if (!record) { return res.status(401).json({ error: 'Invalid API key' }); } // Update last used timestamp await db.apiKeys.update(record.id, { lastUsedAt: new Date() }); req.apiKey = record; req.user = await db.users.findById(record.userId); next(); } // Apply to API routes app.use('/api/v1', authenticateApiKey);
Scoped Permissions
javascript// Check API key scopes function requireScope(...requiredScopes) { return (req, res, next) => { const keyScopes = req.apiKey.scopes || []; const hasAllScopes = requiredScopes.every( scope => keyScopes.includes(scope) ); if (!hasAllScopes) { return res.status(403).json({ error: 'Insufficient permissions', required: requiredScopes, current: keyScopes }); } next(); }; } // Usage app.get('/api/v1/users', authenticateApiKey, requireScope('users:read'), getUsersHandler ); app.post('/api/v1/payments', authenticateApiKey, requireScope('payments:write'), createPaymentHandler );
Rate Limiting per API Key
javascriptconst rateLimit = require('express-rate-limit'); const RedisStore = require('rate-limit-redis'); const apiKeyLimiter = rateLimit({ store: new RedisStore({ client: redis, prefix: 'rl:apikey:' }), windowMs: 60 * 1000, // 1 minute max: async (req) => { // Different limits based on plan const user = await db.users.findById(req.apiKey.userId); return user.plan === 'premium' ? 1000 : 100; }, keyGenerator: (req) => req.apiKey.id, handler: (req, res) => { res.status(429).json({ error: 'Rate limit exceeded', limit: req.rateLimit.limit, remaining: 0, resetTime: req.rateLimit.resetTime }); } }); app.use('/api/v1', authenticateApiKey, apiKeyLimiter);
Key Rotation
javascript// Rotate API key app.post('/api/keys/:id/rotate', authenticate, async (req, res) => { const existingKey = await db.apiKeys.findOne({ id: req.params.id, userId: req.user.id }); if (!existingKey) { return res.status(404).json({ error: 'Key not found' }); } // Generate new key const newApiKey = generateApiKey(); const newKeyHash = hashApiKey(newApiKey); // Update record await db.apiKeys.update(existingKey.id, { keyHash: newKeyHash, rotatedAt: new Date() }); res.json({ id: existingKey.id, key: newApiKey, // Return new key rotatedAt: new Date() }); }); // Automatic expiration async function expireOldKeys() { const expiryDate = new Date(); expiryDate.setDate(expiryDate.getDate() - 90); // 90 days await db.apiKeys.updateMany( { createdAt: { $lt: expiryDate }, rotatedAt: null }, { active: false, expiredAt: new Date() } ); }
API Key Best Practices
- ✅ Use cryptographically secure generation
- ✅ Hash keys before storage
- ✅ Never log full API keys
- ✅ Implement key scopes/permissions
- ✅ Set expiration dates
- ✅ Enable key rotation
- ✅ Rate limit per key
- ✅ Monitor key usage
- ✅ Revoke compromised keys immediately
- ✅ Use prefixes for identification
- ✅ Audit key activity
- ✅ Allow multiple keys per user
Conclusion
Secure API key management requires proper generation, hashing, scoping, and rotation practices. Implement comprehensive key lifecycle management with monitoring and regular rotation to maintain API security.

