I'm building a website using Node and Express JS and would like to throttle invalid login attempts. Both to prevent online cracking and to reduce unnecessary database calls. What are some ways in which I can implement this?
If you run with https and your physical computer is secure from outsiders, then your express session cookie is protected from outsiders when stored locally and is protected (by https) when in transport to the server.
Maybe something like this might help you get started.
var failures = {};
function tryToLogin() {
var f = failures[remoteIp];
if (f && Date.now() < f.nextTry) {
// Throttled. Can't try yet.
return res.error();
}
// Otherwise do login
...
}
function onLoginFail() {
var f = failures[remoteIp] = failures[remoteIp] || {count: 0, nextTry: new Date()};
++f.count;
f.nextTry.setTime(Date.now() + 2000 * f.count); // Wait another two seconds for every failed attempt
}
function onLoginSuccess() { delete failures[remoteIp]; }
// Clean up people that have given up
var MINS10 = 600000, MINS30 = 3 * MINS10;
setInterval(function() {
for (var ip in failures) {
if (Date.now() - failures[ip].nextTry > MINS10) {
delete failures[ip];
}
}
}, MINS30);
So after doing some searching, I wasn't able to find a solution I liked so I wrote my own based on Trevor's solution and express-brute. You can find it here.
rate-limiter-flexible package with Redis or Mongo for distributed apps and in-Memory or with Cluster helps
Here is example with Redis
const { RateLimiterRedis } = require('rate-limiter-flexible');
const Redis = require('ioredis');
const redisClient = new Redis({
options: {
enableOfflineQueue: false
}
});
const opts = {
redis: redisClient,
points: 5, // 5 points
duration: 15 * 60, // Per 15 minutes
blockDuration: 15 * 60, // block for 15 minutes if more than points consumed
};
const rateLimiter = new RateLimiterRedis(opts);
app.post('/auth', (req, res, next) => {
// Consume 1 point for each login attempt
rateLimiter.consume(req.connection.remoteAddress)
.then((data) => {
const loggedIn = loginUser();
if (!loggedIn) {
// Message to user
res.status(400).send(data.remainingPoints + ' attempts left');
} else {
// successful login
}
})
.catch((rejRes) => {
// Blocked
const secBeforeNext = Math.ceil(rejRes.msBeforeNext / 1000) || 1;
res.set('Retry-After', String(secBeforeNext));
res.status(429).send('Too Many Requests');
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With