Security is dull & boring until you get hacked. Then it's REALLY interesting. Node is great at making it easy to create APIs overnight, but that also makes it easy to do it wrong.
I have seen others get compromised because of:
I am sure you have wondered what you'd do if someone hacked your app. Let’s look at some suggestions to avoid getting hacked.
Check What Users/Customers Send You
Always expect users to attempt strange things. Check ALL OF IT.
// Bad code
app.post('/users', (req, res) => {
db.users.create(req.body); // Accepting whatever users send? Bad idea!
});
// Better approach
app.post('/users', (req, res) => {
// Use something like Joi or express-validator
if (!req.body.email || !req.body.email.includes('@')) {
return res.status(400).send('Invalid email');
}
if (typeof req.body.age !== 'number') {
return res.status(400).send('Age must be a number');
}
// Now it's safer to save
db.users.create(req.body);
});
I found this out for myself when someone crashed my application by placing an emoji within the username field. Fun times.
Login Stuff: Don't Mess This Up
JWT tokens are cool but simple to get wrong. Here's my take:
// Creating tokens - keep them short lived!
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '1h' } // Dont make these last forever
);
// Check tokens on protected routes
function checkAuth(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).send('Login required');
}
try {
const user = jwt.verify(token, process.env.JWT_SECRET);
req.user = user; // Add user info to request
next();
} catch (err) {
return res.status(403).send('Invalid or expired token');
}
}
// Use it to protect routes
app.get('/profile', checkAuth, (req, res) => {
// Only logged in users get here
});
Auth has two halves: ensuring that the user is who they say they are (authentication) and ensuring that they can do what they're trying to do (authorization).
Never, under any circumstances, commit your database password to GitHub
// NO NO NO - don't hardcode passwords!!
const db = mysql.connect({
host: 'mydatabase.server.com',
user: 'admin',
password: 'SuperSecret123!' // This should never be in your code
});
// Do this instead
require('dotenv').config();
const db = mysql.connect({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD
});
And DO NOT forget to place your .env file to .gitignore.
SQL Injection Still Works?! Yes, it does. And it is so easy to stop:
// Dangerous - allows SQL injection
app.get('/users', (req, res) => {
const name = req.query.name;
db.query(`SELECT * FROM users WHERE name = '${name}'`, // BAD!
(err, results) => res.json(results)
);
});
// Safe - use parameters
app.get('/users', (req, res) => {
const name = req.query.name;
db.query('SELECT * FROM users WHERE name = ?',
[name], // This prevents SQL injection
(err, results) => res.json(results)
);
});
When someone attempts ?name=x'; DROP TABLE users; -- you will be happy you utilized parameters.
Too Many Requests = Crashed Server
The app crashed when a user abused the search API too much. Implement rate limiting:
const rateLimit = require('express-rate-limit');
// Basic protection for all routes
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit per IP
});
app.use(limiter);
// Extra protection for login attempts
const loginLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5 // 5 login attempts per hour
});
app.use('/login', loginLimiter);
Old Packages = Security Holes
Most hacks happen through outdated packages. Check yours:
# Run this often!
npm audit
# Fix what you can
npm audit fix
Use Helmet for HTTP Headers
One line of code that fixes several issues:
const helmet = require('helmet');
app.use(helmet()); // Adds security headers
Q: What do you need to fix first that is most important?
A: Input validation. Most attacks start there.
Q: What is the best way to know if my API security is sufficient?
A: Have someone attempt to break it. Or you can try a tool like OWASP ZAP.
Q: How can I prevent security vulnerabilities caused by dependencies?
A: Schedule a calendar event to update your dependencies in a timely manner. To detect these vulnerabilities, consider using tools like npm audit, snyk, or dependable.
Q: What is the best way to know if my API security is sufficient?
A: Have someone attempt to break it. Or you can try a tool like OWASP ZAP.
Security is not an afterthought - it should be built into the code from the outset.
Start with these basics:
So, which gap will you be closing today?