API Security: Don't Let Hackers Crash Your Party 🔒
API Security: Don't Let Hackers Crash Your Party 🔒
Your API is basically a VIP entrance to your data. And right now? You might be letting anyone walk in with a fake mustache! 🥸
Let me guess - you built an awesome API, deployed it, and now you're wondering why random bots are hammering your endpoints at 3 AM. Welcome to the internet, my friend!
Here's how to turn your API from a free-for-all into Fort Knox (but like, the cool kind that still lets legitimate users in).
1. Authentication - Check Their ID! 🎫
The Problem: Anonymous users doing whatever they want with your API.
The Bad Way (Please Don't):
// This is like having NO lock on your door
app.get('/api/users/:id', (req, res) => {
const user = db.getUser(req.params.id);
res.json(user); // Anyone can see anyone's data!
});
What happens: Hackers just iterate through IDs and steal everyone's data. Fun times! 😱
The Right Way:
// Use JWT tokens - your API's bouncer
app.get('/api/users/:id', authenticateToken, (req, res) => {
// Only works if they have a valid token
if (req.user.id !== req.params.id) {
return res.status(403).json({ error: 'Not your data, buddy!' });
}
const user = db.getUser(req.params.id);
res.json(user);
});
function authenticateToken(req, res, next) {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'No token, no entry' });
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ error: 'Invalid token' });
req.user = user;
next();
});
}
Pro Tip: Store your JWT secret in environment variables. Hardcoding it is like posting your house keys on Instagram!
2. Rate Limiting - Stop the Spam Bots 🚦
The Scenario: A bot makes 10,000 requests per second. Your server catches fire. 🔥
Without Rate Limiting:
// RIP your server bills
app.post('/api/send-email', (req, res) => {
sendEmail(req.body.to, req.body.message);
res.json({ success: true });
});
Result: Someone sends a million emails through your API. Your email provider bans you. You cry.
With Rate Limiting:
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: 'Whoa there, cowboy! Slow down! 🐎'
});
app.use('/api/', apiLimiter);
Translation: Each IP gets max 100 requests per 15 minutes. Bots hate this one simple trick!
Real Talk: Different endpoints need different limits. Login? 5 attempts per minute. Getting data? 100 per minute. Sending emails? Maybe 10 per hour.
3. Input Validation - Trust Nobody! 🕵️
The Truth Bomb: Users will try to break your API. Not because they're evil - because the internet is chaos!
The Dangerous Code:
// This is a hacker's playground
app.post('/api/profile', (req, res) => {
const { name, email, bio } = req.body;
db.updateProfile(req.user.id, { name, email, bio });
res.json({ success: true });
});
What could go wrong:
- Someone sends
email: "<script>alert('hacked')</script>" - Or
name: {"$ne": null}(MongoDB injection!) - Or a 10MB bio that crashes your database
The Safe Way:
const { body, validationResult } = require('express-validator');
app.post('/api/profile', [
body('name')
.trim()
.isLength({ min: 2, max: 50 })
.escape(), // Bye bye, XSS!
body('email')
.isEmail()
.normalizeEmail(),
body('bio')
.trim()
.isLength({ max: 500 })
.escape(),
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Now it's safe!
const { name, email, bio } = req.body;
db.updateProfile(req.user.id, { name, email, bio });
res.json({ success: true });
});
Golden Rule: Validate everything. Length, type, format, special characters - ALL OF IT!
4. HTTPS Only - No Naked Data! 🔐
The Horror Story: You send API tokens over HTTP. A hacker at Starbucks intercepts them with Wireshark. Game over.
The Fix:
// Redirect ALL HTTP to HTTPS
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https' && process.env.NODE_ENV === 'production') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});
// Even better - use HSTS header
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});
Translation: HTTPS encrypts everything. It's 2026 - Let's Encrypt is FREE. No excuses!
5. Error Messages - TMI is Real 📢
The Leak:
// This tells hackers EXACTLY what went wrong
app.post('/api/login', (req, res) => {
const user = db.findUser(req.body.email);
if (!user) {
return res.status(404).json({ error: 'User with email [email protected] not found in database table users' });
}
// More bad code...
});
Why it's terrible: You just confirmed that email exists AND revealed your database structure!
The Better Way:
app.post('/api/login', (req, res) => {
const user = db.findUser(req.body.email);
if (!user || !comparePassword(req.body.password, user.password)) {
// Vague message - same for both wrong email AND wrong password
return res.status(401).json({ error: 'Invalid credentials' });
}
// Success path...
});
// Handle errors globally
app.use((err, req, res, next) => {
console.error(err.stack); // Log it for YOU
// But show users something generic
res.status(500).json({
error: 'Something went wrong',
// Only in development:
...(process.env.NODE_ENV === 'development' && { details: err.message })
});
});
Pro Tip: Log everything on your side, but users only need to know "something went wrong" - not your entire stack trace!
6. API Keys - Rotate Like Your Life Depends On It 🔑
The Problem: You gave out API keys 2 years ago and forgot about them.
Best Practices:
// Store hashed API keys (like passwords!)
const crypto = require('crypto');
function generateApiKey() {
return crypto.randomBytes(32).toString('hex');
}
function hashApiKey(key) {
return crypto.createHash('sha256').update(key).digest('hex');
}
// In your database, store the HASH, not the actual key
app.post('/api/generate-key', authenticateUser, async (req, res) => {
const apiKey = generateApiKey();
const hashedKey = hashApiKey(apiKey);
await db.storeApiKey(req.user.id, hashedKey, {
createdAt: new Date(),
expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000) // 90 days
});
// Show the key ONCE, then never again
res.json({
apiKey,
message: 'Save this key! You won\'t see it again!'
});
});
Must-dos:
- Keys expire (30-90 days)
- Users can revoke keys
- Each app gets its own key
- Monitor usage per key
Your API Security Checklist 📋
Before you deploy that API:
- HTTPS everywhere (with HSTS header)
- JWT or OAuth for authentication
- Rate limiting on ALL endpoints
- Input validation with a library (don't roll your own)
- Generic error messages (no stack traces in production)
- API keys are hashed and expire
- CORS configured properly (not just
*) - Logging all access attempts
- No secrets in code (use environment variables)
- Dependencies updated (run
npm audit)
Quick Wins (Do These Today!) 🏃♂️
- Add helmet.js - One line, tons of security headers
const helmet = require('helmet');
app.use(helmet());
- Enable CORS properly:
const cors = require('cors');
app.use(cors({
origin: 'https://yourdomain.com', // NOT '*'
credentials: true
}));
- Add request logging:
const morgan = require('morgan');
app.use(morgan('combined')); // Log everything!
Real Talk 💬
Q: "Do I really need all this for my side project?"
A: If it's just you using it locally? Nah. If it's on the internet? YES. Bots will find it in hours!
Q: "What about OAuth vs JWT?"
A: OAuth is for "Login with Google/GitHub" - you're using someone else's auth. JWT is DIY auth. Both are good, just different use cases.
Q: "Rate limiting will annoy my users!"
A: Set reasonable limits! A real user won't make 1000 requests per minute. But a bot attacking you will!
The Bottom Line
API security isn't about being paranoid - it's about being realistic. The internet is full of:
- Bots scanning for vulnerable APIs
- Scripts trying common exploits
- Accidental bugs that expose data
Think of it like locking your car. Sure, a determined thief can still break in. But you're not making it EASY!
The 3 Rules:
- Authenticate everything - No anonymous access to sensitive data
- Validate everything - Never trust client input
- Limit everything - Rate limits, input lengths, permissions
Your future self (and your users) will thank you! 🙏
Building APIs? Let's connect on LinkedIn! I'm part of YAS and InitCrew communities and love talking about secure API design!
Want more security content? Check out my other posts on XSS, SQL Injection, and HTTPS/TLS!
Now go build secure APIs that hackers hate! 🛡️✨