To prevent brute-force attacks in a Node and Express.js app, I’ve found a few reliable methods that work well together. Here is what I typically do:
1. One of the simplest and most effective ways to slow down brute-force attacks is by setting up rate limiting. This limits the number of requests a single IP can make within a time frame. express-rate-limit is a solid choice for this.
Installing express-rate-limit is really simple:
npm install express-rate-limit
After installing it. we'll configure it for our login router:
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: "Too many attempts. Please try again later."
});
app.post('/login', loginLimiter, (req, res) => {
})
This stops attackers from blasting your login endpoint with hundreds of requests in seconds.
2. helmet is a great add-on to set secure HTTP headers, which helps protect against other types of attacks.
Installing helmet:
npm install helmet
Adding it to our Application:
const helmet = require('helmet');
app.use(helmet());
3. Sometimes, rate limiting alone isn’t enough. For cases where I’ve noticed persistent attempts from specific IPs, I set up an IP-based blocking mechanism. Here’s an example:
const loginAttempts = {};
app.post('/login', (req, res) => {
const ip = req.ip;
if (loginAttempts[ip] && loginAttempts[ip] >= 5) {
return res.status(429).send("Too many attempts. Try again later.");
}
const loginSuccessful = validateLogin(req.body.username, req.body.password);
if (!loginSuccessful) {
loginAttempts[ip] = (loginAttempts[ip] || 0) + 1;
setTimeout(() => delete loginAttempts[ip], 15 * 60 * 1000);
return res.status(401).send("Invalid credentials.");
}
delete loginAttempts[ip];
res.send("Login successful!");
});
For production environments, consider using a database or Redis to track these attempts more efficiently.
4. I can’t emphasize enough how important it is to hash passwords with bcrypt. It protects users in case your database gets compromised.
const bcrypt = require('bcrypt');
const saltRounds = 10;
const hashedPassword = await bcrypt.hash('userPassword', saltRounds);
5. When a bot doesn’t give up, adding CAPTCHA after a few failed attempts can help. I’ve used Google reCAPTCHA on forms after a certain number of login failures, and it’s effective at stopping bots.
6. If someone’s really determined to brute-force a specific user’s account, an account lockout mechanism can be a lifesaver. After a certain number of failed attempts on an account, lock it temporarily.