How To Handle Brute Force Attack on the Backend Side

Photo by Possessed Photography on Unsplash

TL;DR

  • Set the limit of how much the user is allowed to fail to log in.
  • In every login request, check if the number of failed login attempts exceeds the limit. If it does, block the login request for the concerned username in that IP address for several minutes or hours.
  • Reset the count of the failed login attempts if the user is successful to log in, or it has passed the block time limit, or reset it periodically for example every one hour.

Definition

These attacks are done by ‘brute force’ meaning they use excessive forceful attempts to try and ‘force’ their way into your private account(s).
This is an old attack method, but it’s still effective and popular with hackers. Because depending on the length and complexity of the password, cracking it can take anywhere from a few seconds to many years [1].

Why You Should Care

Several months ago, my employer ask me to prevent the Brute Force Attack on a user login password on the backend side. So this is what I did.

Concept

So the obvious thing we should do as a Backend Engineer is to stop this suspicious excessive request to a login API for this specific username.

Database Design

Table failed_login_attm
Table failed_login_attm
Table failed_login_attm

Here is the query in PostgreSQL:

CREATE TABLE failed_login_attm (
failedloginid serial NOT NULL,
username varchar NULL,
ip_address varchar NULL,
failed_login_attempts int4 NULL,
failed_login_time timestamptz NULL,
CONSTRAINT sys_fail_login_attm_pkey PRIMARY KEY (failedloginid)
);

Attributes Explanation

username

ip_address

failed_login_attempts & failed_login_time

You probably asking, why don’t just add the column on a user table to store the failed login attempt information? Well if the attacker inputting a username that doesn’t even exist on your database, the failed login attempt would not be recorded and the attacker still can flood your server with the excessive request. Which is one of the things that we concern about at the beginning of this article.

Code Implementation

Let say we have a login function. And a verifyCredential() function that verifies if the username with the password is a match or not.

const login = (req, res) => {
// get body request for username and password
// get information about the request ip_address
...
if(verifyCredential(username, password)) {
// if credential is verfied, it allowed to login
} else {
// if credential is false, user failed to login and we record the attempt
failedLoginAttmCount(username, ip_address)
}
}

We have recorded the failed login attempt. Now how to block the request if the failed login attempt surpasses the limit? We need one more function to check if the failed login attempt for the concerned username is exceeding the limit in a range time that we have specified. Let’s say that function is checkFailedLoginAttm()

Add this function in the first lines of the login function that we have defined before

const login = (req, res) => {
// get body request for username and password
// get information about the request ip_address
...
checkFailedLoginAttm(username, res) // if it does exceeds the failed login limit, it will stop the process by returning the response of the requestif(verifyCredential(username, password)) {
// if credential is verfied, it allowed to login
} else {
// if credential is false, user failed to login and we record the attempt
failedLoginAttmCount(username, ip_address)
}
}

Here is the checkFailedLoginAttm() function

checkFailedLoginAttm(username) {
// get information about failed login attempt to a database
...
failed_login_attempt = query_result.failed_login_attempt
failed_login_time = query_result.failed_login_time

currTime = Date.now()
time_limit = ... // failed_login_time + 10 minutes
reset_time = ... // added 1 hour from current time
if (failed_login_attempt > 10 && currTime < failed_login_time + time_limit)
return res.status(400).json({success: 0, message: "You have reached failed login limit."})
else if (failed_login_attempt > 10 && currTime > failed_login_time + time_limit)
// reset the failed failed login attempt count to 0
resetFailedLoginAttm(username)
else if (failed_login_time > reset_time)
// reset the failed failed login attempt count to 0 if the last failed login attempt is 1 hour ago
resetFailedLoginAttm(username)
}

Or if you prefer the boolean check

const login = (req, res) => {
// get body request for username and password
// get information about the request ip_address
...
const isExceedsFailedLogin = checkFailedLoginAttm(username, res)
// if it does surpass the failed login limit, it will return true. Otherwise it return false

if(!isExceedsFailedLogin ) {
if(verifyCredential(username, password)) {
// if credential is verfied, it allowed to login
} else {
// if credential is false, user failed to login and we record the attempt
failedLoginAttmCount(username, ip_address)
}
} else return res.status(400).json({success: 0, message: "You have reached failed login limit."})
}
checkFailedLoginAttm(username) {
// same code as the first one
...
let isExceedsFailedLogin = false
if (failed_login_attempt > 10 && currTime < failed_login_time + time_limit)
isExceedsFailedLogin = true
....
// same code as the first one
...
return isExceedsFailedLogin
}

References

Software Engineer | Content Creator | Entrepreneur