Security Things That Matter
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:
- They employed packages they never audited (npm is wonderful & frightening)
- They rely on input from users (never, EVER do that)
- They hard-code credentials directly into the code (why in the world?)
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.
The Basics
-
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). n
-
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.
Real Security Problems I’ve Seen
-
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
FAQs that every Developer should know
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? n 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.
Final Thoughts – Lessons to learn from
Security is not an afterthought – it should be built into the code from the outset.
Start with these basics:
- Authenticate all users
- Secure your auth routes
- Store Secrets in Environment Variables
- Update dependencies
- Set rate limits
- Always use HTTPS
So, which gap will you be closing today?