API Keys and Tokens: Developer's Guide to Credential Security
Essential security practices for managing API keys, access tokens, and developer credentials.
title: "API Keys and Tokens: Developer's Guide to Credential Security" description: "Essential security practices for managing API keys, access tokens, and developer credentials." date: "2025-12-26" author: "Security Team" category: "Technical" readTime: "10 min" keywords: ["API key security", "token management", "developer credentials", "secrets management"]
Introduction
API keys and tokens are the passwords of the developer world. A leaked API key can result in service abuse, data breaches, and massive bills. This guide provides comprehensive security practices for developers managing credentials in code, CI/CD pipelines, and production environments.
Types of Developer Credentials
API Keys
What they are: Long-lived credentials for service authentication
Common services:
- Cloud providers (AWS, Azure, GCP)
- Payment processors (Stripe, PayPal)
- Communication (Twilio, SendGrid)
- Analytics (Google Analytics, Mixpanel)
- Maps (Google Maps, Mapbox)
Security level: High - treat as passwords
Access Tokens
OAuth tokens: Short-lived, scoped access JWT tokens: Encoded claims with expiration Personal access tokens: GitHub, GitLab, Bitbucket
Characteristics:
- Time-limited
- Scope-restricted
- Revocable
- Renewable
Service Account Credentials
What they are: Machine-to-machine authentication
Examples:
- GCP service account keys
- AWS IAM credentials
- Azure service principals
- Database connection strings
Risk level: Critical - full service access
SSH Keys
Purpose: Server access, Git operations
Types:
- RSA (legacy, 2048+ bits)
- Ed25519 (modern, recommended)
- ECDSA (alternative)
Protection: Passphrase-protected, limited access
Database Credentials
Connection strings: Username, password, host, port
Risk: Direct data access
Protection: Environment variables, secrets management
Common Security Mistakes
❌ Hardcoding Credentials
The mistake:
// NEVER DO THIS
const apiKey = "sk_live_51H8xYz2eZvKYlo2C...";
const dbPassword = "MyPassword123!";
Why dangerous:
- Committed to version control
- Visible in code reviews
- Exposed in public repos
- Difficult to rotate
- Audit trail in Git history
Impact:
- GitHub scans for secrets (alerts sent)
- Bots scrape public repos
- Instant exploitation
- Costly service abuse
❌ Committing .env Files
The mistake:
# .env file committed to Git
DATABASE_URL=postgresql://user:pass@localhost/db
STRIPE_SECRET_KEY=sk_live_...
AWS_ACCESS_KEY_ID=AKIA...
Why dangerous:
- Permanent Git history
- Shared with all developers
- Exposed if repo public
- Difficult to remove
Prevention:
# .gitignore
.env
.env.local
.env.*.local
secrets.json
credentials.json
❌ Logging Credentials
The mistake:
console.log(`Connecting with key: ${apiKey}`);
logger.info(`Database password: ${dbPass}`);
Why dangerous:
- Logs often unencrypted
- Stored long-term
- Accessible to many people
- Sent to log aggregation services
Solution:
console.log(`Connecting with key: ${apiKey.substring(0, 8)}...`);
logger.info('Database connection established');
❌ Overly Permissive Keys
The mistake:
- Admin access for read-only needs
- Wildcard permissions
- No expiration
- Shared across environments
Impact:
- Excessive blast radius
- Difficult to audit
- Compliance violations
- Unnecessary risk
❌ No Key Rotation
The mistake:
- Same keys for years
- Never expire
- No rotation policy
- Forgotten keys active
Risk:
- Increased exposure time
- Compromised keys undetected
- Compliance failures
- Difficult incident response
Secure Credential Management
Environment Variables
Best practice:
// Load from environment
const apiKey = process.env.STRIPE_SECRET_KEY;
const dbUrl = process.env.DATABASE_URL;
// Validate presence
if (!apiKey) {
throw new Error('STRIPE_SECRET_KEY not set');
}
Setup:
# .env (not committed)
STRIPE_SECRET_KEY=sk_live_...
DATABASE_URL=postgresql://...
# Load in application
require('dotenv').config();
Advantages:
- Not in code
- Environment-specific
- Easy to rotate
- Standard practice
Secrets Management Systems
Enterprise solutions:
- HashiCorp Vault: Industry standard
- AWS Secrets Manager: AWS integration
- Azure Key Vault: Azure integration
- GCP Secret Manager: GCP integration
Features:
- Encryption at rest
- Access control
- Audit logging
- Automatic rotation
- Version history
Example (AWS Secrets Manager):
const AWS = require('aws-sdk');
const client = new AWS.SecretsManager();
async function getSecret(secretName) {
const data = await client.getSecretValue({
SecretId: secretName
}).promise();
return JSON.parse(data.SecretString);
}
const dbCreds = await getSecret('prod/database');
Configuration Management
Tools:
- dotenv: Development
- docker-compose: Container secrets
- Kubernetes secrets: K8s deployments
- AWS Parameter Store: AWS infrastructure
Best practices:
- Separate per environment
- Encrypted storage
- Access controls
- Audit trails
Password Managers for Developers
Team password managers:
- 1Password (developer-friendly)
- Bitwarden (open-source)
- LastPass (team features)
Use for:
- Shared credentials
- Emergency access
- Team onboarding
- Secure notes
CLI integration:
# 1Password CLI
op run -- npm start
# Bitwarden CLI
bw get password "API Key" | pbcopy
Learn more: Password Manager Security
Development Workflow
Local Development
Setup:
# .env.local (gitignored)
DATABASE_URL=postgresql://localhost/dev_db
STRIPE_SECRET_KEY=sk_test_...
DEBUG=true
Best practices:
- Use test/sandbox keys
- Separate from production
- Document required variables
- Provide .env.example
Example .env.example:
# Copy to .env.local and fill in values
DATABASE_URL=
STRIPE_SECRET_KEY=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
CI/CD Pipelines
GitHub Actions:
name: Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: npm run deploy
Best practices:
- Use platform secrets (GitHub Secrets, GitLab CI/CD variables)
- Mask sensitive output
- Limit secret access
- Rotate regularly
- Audit usage
Other platforms:
- GitLab CI: CI/CD variables (protected, masked)
- CircleCI: Context secrets
- Jenkins: Credentials plugin
- Travis CI: Encrypted variables
Production Deployment
Container secrets:
# docker-compose.yml
services:
app:
image: myapp
environment:
- DATABASE_URL=${DATABASE_URL}
secrets:
- db_password
secrets:
db_password:
external: true
Kubernetes secrets:
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
api-key: <base64-encoded>
db-password: <base64-encoded>
Best practices:
- Use secrets management
- Encrypt at rest
- Limit pod access
- Rotate regularly
- Monitor access
API Key Security
Key Generation
Requirements:
- Cryptographically random
- Sufficient length (32+ characters)
- Unpredictable
- Unique per user/service
Example generation:
const crypto = require('crypto');
function generateApiKey() {
return crypto.randomBytes(32).toString('hex');
}
// Result: 64-character hex string
Key Storage
Hash keys before storage:
const crypto = require('crypto');
function hashApiKey(key) {
return crypto
.createHash('sha256')
.update(key)
.digest('hex');
}
// Store hash, not plain key
const keyHash = hashApiKey(apiKey);
await db.apiKeys.create({ hash: keyHash });
Why hash:
- Database breach doesn't expose keys
- Similar to password hashing
- One-way verification
- Industry best practice
Key Rotation
Rotation policy:
- Regular schedule (90 days)
- After employee departure
- Suspected compromise
- Compliance requirements
Implementation:
// Support multiple active keys during rotation
const activeKeys = [
process.env.API_KEY_CURRENT,
process.env.API_KEY_PREVIOUS
];
function validateKey(providedKey) {
return activeKeys.some(key =>
crypto.timingSafeEqual(
Buffer.from(hashKey(providedKey)),
Buffer.from(hashKey(key))
)
);
}
Process:
- Generate new key
- Add to environment
- Update clients gradually
- Remove old key after transition
- Document rotation
Key Scoping
Principle of least privilege:
// Good: Scoped permissions
{
"key": "sk_live_...",
"permissions": ["read:users", "write:orders"],
"rateLimit": 1000,
"expiresAt": "2025-12-31"
}
// Bad: Admin access
{
"key": "sk_live_...",
"permissions": ["*"],
"rateLimit": null,
"expiresAt": null
}
Best practices:
- Minimum required permissions
- Time-limited when possible
- Rate limiting
- IP restrictions
- Usage monitoring
Token Management
JWT Best Practices
Secure generation:
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{
expiresIn: '15m',
issuer: 'myapp.com',
audience: 'myapp.com'
}
);
Security considerations:
- Strong secret (32+ characters)
- Short expiration (15-60 minutes)
- Refresh token pattern
- Signature verification
- Claims validation
Refresh tokens:
// Short-lived access token
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
// Long-lived refresh token (stored securely)
const refreshToken = jwt.sign(payload, refreshSecret, { expiresIn: '7d' });
// Refresh endpoint
app.post('/refresh', async (req, res) => {
const { refreshToken } = req.body;
const decoded = jwt.verify(refreshToken, refreshSecret);
const newAccessToken = jwt.sign({ userId: decoded.userId }, secret, { expiresIn: '15m' });
res.json({ accessToken: newAccessToken });
});
OAuth Token Security
Storage:
- Never in localStorage (XSS vulnerable)
- HttpOnly cookies (preferred)
- Secure, SameSite flags
- Short expiration
Example:
res.cookie('access_token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 15 * 60 * 1000 // 15 minutes
});
Monitoring and Detection
Audit Logging
Log all credential usage:
logger.info('API key used', {
keyId: key.id,
userId: key.userId,
endpoint: req.path,
ip: req.ip,
timestamp: new Date()
});
Monitor for:
- Unusual usage patterns
- Failed authentication attempts
- Geographic anomalies
- Rate limit violations
- Permission escalation attempts
Alerting
Set up alerts for:
- Multiple failed attempts
- Access from new locations
- High-value operations
- Permission changes
- Key rotation events
Tools:
- Datadog
- New Relic
- Sentry
- CloudWatch
- Custom webhooks
Secret Scanning
Tools:
- GitHub secret scanning: Automatic
- GitGuardian: Comprehensive
- TruffleHog: Open-source
- git-secrets: Pre-commit hooks
Implementation:
# Install git-secrets
brew install git-secrets
# Initialize in repo
git secrets --install
git secrets --register-aws
# Scan history
git secrets --scan-history
Incident Response
If Credentials Are Leaked
Immediate actions (within 1 hour):
- Revoke compromised credentials
- Generate new credentials
- Update all services
- Review access logs
- Assess damage
Within 24 hours: 6. Identify leak source 7. Fix vulnerability 8. Rotate related credentials 9. Notify affected parties 10. Document incident
Prevention:
- Implement secret scanning
- Pre-commit hooks
- Code review process
- Security training
- Regular audits
Removing from Git History
Tools:
# BFG Repo-Cleaner (recommended)
bfg --replace-text passwords.txt repo.git
# git-filter-branch (alternative)
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
Important:
- Credentials still compromised
- Must rotate immediately
- Force push required
- Notify all contributors
Best Practices Summary
✅ Do This
- Use environment variables
- Implement secrets management
- Rotate credentials regularly
- Scope permissions minimally
- Enable audit logging
- Use secret scanning
- Hash API keys before storage
- Implement rate limiting
- Monitor for anomalies
- Document all credentials
❌ Never Do This
- Hardcode credentials
- Commit secrets to Git
- Log sensitive data
- Share credentials
- Use admin keys unnecessarily
- Skip rotation
- Ignore alerts
- Store in plain text
- Use same key everywhere
- Disable security features
Developer Security Checklist
Before Committing
- [ ] No hardcoded credentials
- [ ] .env files gitignored
- [ ] No sensitive data in logs
- [ ] Secret scanning passed
- [ ] Code review completed
Before Deploying
- [ ] Environment variables set
- [ ] Secrets management configured
- [ ] Permissions scoped correctly
- [ ] Monitoring enabled
- [ ] Rotation schedule set
Regular Maintenance
- [ ] Rotate credentials (90 days)
- [ ] Review access logs
- [ ] Update permissions
- [ ] Remove unused keys
- [ ] Audit secret usage
Conclusion
Developer credential security is critical:
- Never hardcode - Use environment variables or secrets management
- Rotate regularly - 90-day maximum
- Scope minimally - Least privilege principle
- Monitor actively - Audit logs and alerts
- Scan continuously - Detect leaks early
Your API keys are as valuable as passwords. Treat them with the same security rigor.
Start now: Audit your codebase for hardcoded credentials and implement proper secrets management today.
Learn more:
Ready to Create a Strong Password?
Use our free Strong Password Generator to create secure passwords instantly.
Related Articles
Secure Password Maker for Developers: CI/CD, .env, and Secrets
Developer-focused guide to generating and managing passwords for technical environments.
Do Websites Store My Passwords? What Really Happens on the Backend
Understand how websites should (and shouldn't) store your passwords.
Password Hashing Algorithms Explained: bcrypt, Argon2, and More
Understand how websites securely store passwords using hashing algorithms and why it matters for your security.