JAVASCRIPT
Implement Brute-Force Protection for Login Attempts
Secure user accounts by implementing a custom rate-limiting mechanism for failed login attempts to prevent brute-force attacks in Node.js.
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
// In-memory store for failed login attempts (in a real app, use Redis/database)
const loginAttempts = new Map(); // Stores { username: { count: number, lastAttempt: Date, blockedUntil: Date } }
const MAX_FAILED_ATTEMPTS = 5;
const BLOCK_DURATION_MS = 60 * 1000; // Block for 1 minute
function checkLoginAttempts(username) {
const userAttempts = loginAttempts.get(username);
if (userAttempts && userAttempts.blockedUntil && userAttempts.blockedUntil > new Date()) {
return { allowed: false, remaining: 0, blockedUntil: userAttempts.blockedUntil };
}
return { allowed: true, remaining: MAX_FAILED_ATTEMPTS - (userAttempts ? userAttempts.count : 0) };
}
function recordFailedAttempt(username) {
const now = new Date();
let userAttempts = loginAttempts.get(username) || { count: 0, lastAttempt: now };
userAttempts.count++;
userAttempts.lastAttempt = now;
if (userAttempts.count >= MAX_FAILED_ATTEMPTS) {
userAttempts.blockedUntil = new Date(now.getTime() + BLOCK_DURATION_MS);
userAttempts.count = 0; // Reset count after blocking for the next cycle
console.warn(`User ${username} blocked for ${BLOCK_DURATION_MS / 1000} seconds due to too many failed attempts.`);
}
loginAttempts.set(username, userAttempts);
}
function resetLoginAttempts(username) {
loginAttempts.delete(username);
}
// Simulate user database
const users = { 'admin': 'password123', 'testuser': 'securepass' };
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ message: 'Username and password are required.' });
}
const attemptStatus = checkLoginAttempts(username);
if (!attemptStatus.allowed) {
const timeRemaining = Math.ceil((attemptStatus.blockedUntil.getTime() - new Date().getTime()) / 1000);
return res.status(429).json({ message: `Too many failed login attempts. Please try again in ${timeRemaining} seconds.` });
}
if (users[username] && users[username] === password) { // In real app, use bcrypt.compare
resetLoginAttempts(username);
res.json({ message: 'Login successful!' });
} else {
recordFailedAttempt(username);
const newAttemptStatus = checkLoginAttempts(username);
let message = 'Invalid username or password.';
if (!newAttemptStatus.allowed) {
const timeRemaining = Math.ceil((newAttemptStatus.blockedUntil.getTime() - new Date().getTime()) / 1000);
message = `Invalid username or password. You have been temporarily blocked. Try again in ${timeRemaining} seconds.`;
} else if (newAttemptStatus.remaining <= MAX_FAILED_ATTEMPTS / 2) { // Give a hint without revealing exact count
message = `Invalid username or password. You have ${newAttemptStatus.remaining} attempts remaining before temporary block.`;
}
res.status(401).json({ message });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
How it works: This Node.js Express snippet implements a custom brute-force protection mechanism specifically for login attempts. It tracks failed attempts for each username (using an in-memory Map, but a robust solution would use Redis or a database). If a user exceeds 'MAX_FAILED_ATTEMPTS' within a certain period, their account (or IP, depending on implementation) is temporarily 'blocked' for 'BLOCK_DURATION_MS'. This prevents attackers from rapidly guessing passwords. The 'checkLoginAttempts' function determines if a login is allowed, and 'recordFailedAttempt' updates the attempt count and applies blocks. On successful login, attempts are reset. This helps protect user accounts from dictionary and brute-force attacks without impacting legitimate users too severely.