This post is Part 2 (of 2) on implementing secure username/password authentication for your Mongoose User models. In Part 1 we implemented one-way password encryption and verification using bcrypt. Here in Part 2 we’ll discuss how to prevent brute-force attacks by enforcing a maximum number of failed login attempts. This was originally posted on the DevSmash Blog
If you haven’t done so already, I recommend you start with reading Part 1. However, if you’re like me and usually gloss over the paragraph text looking for code, here’s what our User model looked like when we left off:
As can be seen, there’s not much too it - we hash passwords before documents are saved to MongoDB, and we provide a basic convenience method for comparing passwords later on.
While our code from Part 1 is functional, it can definitely be improved upon. Hashing passwords will save your bacon if a hacker gains access to your database, but it does nothing to prevent brute-force attacks against your site’s login form. This is where account locking comes in: after a specific number of failed login attempts, we simply ignore subsequent attempts, thereby putting the kibosh on the brute-force attack.
Unfortunately, this still isn’t perfect. As stated by OWASP:
Password lockout mechanisms have a logical weakness. An attacker that undertakes a large numbers of authentication attempts on known account names can produce a result that locks out entire blocks of application users accounts.
The prescribed solution, then, is to continue to lock accounts when a likely attack is encountered, but then unlock the account after some time has passed. Given that a sensible password policy puts the password search space into the hundreds of trillions (or better), we don’t need to be too worried about allowing another five guesses every couple of hours or so.