How To Handle Brute Force Attack on the Backend Side

Photo by Possessed Photography on Unsplash

TL;DR

  • Store the username, IP address, and the count of the failed login attempts request in your database.

Definition

In cryptography, Brute Force Attack is a cyberattack that relies on guessing the combination of credentials or encrypted data such as login, passwords, or encryption key until the correct credential combination is discovered.

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

It would be so devastating for your application’s reputation to get your user’s credentials stolen by someone and they using it for mean things. On the other hand, this kind of attack could increase junk traffic to your server. Which may make your app run slower than usual. If you’re doing nothing, your app will lose user’s trust and your business will start falling.

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

First thing first, Brute Force Attack on a user’s login password commonly would target a specific username. For example, they’ll use the username johndoe and then start guessing the password with Brute Force Attack. This is means, the hacker will access the login API a hundred or thousand times until the password is discovered.

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

To make that possible, we need to count the failed login attempt that happens continuously to a specific username and store it in our database. We need a table in a database that is responsible to store this information.

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

Because the Brute Force Attack would target a specific username, we need to store the username from the request login API that happens to be failed to log in. We don’t need to check if the username exists or not on our database. Just store it to our failed_login_atm table. Because we need to not let the attackers knowing more about our application or server’s information, otherwise it would make the attackers easier to attack.

ip_address

We also need to store the IP address to make it more unique to identify which request the system should block. This prevents if the username is blocked in a certain IP address, it still can be used for login in a different IP address. So the real user is not disturbed. Unless he is connected to the same IP address. Which also can make the investigation of where the brute force attack comes from easier.

failed_login_attempts & failed_login_time

Other information that needs to be stored is the amount of failed login attempts and the exact time when the login request is failed.

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

The system will count the failed login attempt for a certain username until the limit that we have specified. If the failed login attempt for a certain username exceeds the limit, it will block a login request for the username from the concerned IP address until the time that we have specified.

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

[1] https://www.kaspersky.com/resource-center/definitions/brute-force-attack

Software Engineer | Content Creator | Entrepreneur