Securing Fastify Apps: Rate Limiting and Account Lockout Methods
To protect your Node.js Fastify application from brute-force attacks on the login form, you can implement several strategies. Here are some methods that you can consider:
-
Rate Limiting: Limit the number of requests a single user can make to the login endpoint within a given timeframe. This way, you can slow down brute-force attacks by making them less effective.
-
Account Lockout Policy: Temporarily lock out accounts after a certain number of failed login attempts. This discourages continued brute-force attempts on a single account.
-
CAPTCHA: Implement CAPTCHAs after several failed login attempts to ensure that a human is performing the login attempts.
-
IP Blocking: Block IPs that are making too many failed login attempts.
-
Use Strong Passwords: Encourage or enforce strong password policies to make password guessing more difficult.
Here's a basic example of how you can implement rate limiting and account lockout in a Fastify application:
First, install a rate-limiting plugin such as fastify-rate-limit
:
npm install fastify-rate-limit
Then, apply it to your Fastify instance:
const fastify = require('fastify')();
const fastifyRateLimit = require('fastify-rate-limit');
fastify.register(fastifyRateLimit, {
max: 5, // maximum number of requests for a window
timeWindow: '1 minute', // resets every minute
keyGenerator: (req) => req.ip // use IP address as key
});
let failedAttempts = {}; // Track failed attempts
const MAX_FAILED_ATTEMPTS = 5; // Max failed attempts before lockout
const LOCK_TIME = 10 * 60 * 1000; // Lockout duration in milliseconds
fastify.post('/login', async (request, reply) => {
const { username, password } = request.body;
if (failedAttempts[username] && failedAttempts[username].blockUntil > Date.now()) {
return reply.status(429).send({ error: 'Account temporarily locked due to too many failed login attempts. Please try again later.' });
}
const isAuthenticated = await authenticateUser(username, password);
if (isAuthenticated) {
failedAttempts[username] = { count: 0, blockUntil: null }; // reset attempts on successful login
return reply.send({ success: true, message: 'Logged in successfully!' });
} else {
failedAttempts[username] = failedAttempts[username] || { count: 0, blockUntil: null };
failedAttempts[username].count++;
if (failedAttempts[username].count >= MAX_FAILED_ATTEMPTS) {
failedAttempts[username].blockUntil = Date.now() + LOCK_TIME;
return reply.status(429).send({ error: 'Too many failed login attempts, account is locked.' });
}
return reply.status(401).send({ error: 'Invalid username or password.' });
}
});
async function authenticateUser(username, password) {
// Replace with actual authentication logic against your user database
// Return true if successful, false otherwise
return false;
}
fastify.listen(3000, (err, address) => {
if (err) throw err;
console.log(`Server listening on ${address}`);
});
Key Points:
- Rate Limiting: Limits the number of requests per IP to slow down brute force attacks.
- Account Lockout: Temporarily locks an account after a certain number of failed attempts to prevent further attempts for a set duration.
- Clean Up: Consider adding logic to periodically clean up the
failedAttempts
object to prevent memory leaks, especially for usernames that do not exist.
By applying these strategies, you'll enhance the security of your login form against brute-force attacks. Remember to tailor these strategies according to your specific requirements and user base.