Tutorial: How to protect accounts from brute force attacks

Prevent brute force attacks tutorial image

What is a brute force attack?

Brute force attacks are one of the oldest and most persistent online threats, leveraging sheer computing power to break into user accounts. By guessing username and password combinations over and over, attackers attempt to gain unauthorized access to accounts without needing a database of leaked credentials.

These attacks are often automated, enabling attackers to quickly test thousands of credentials. One of the key reasons these attacks remain successful is the prevalence of weak passwords and poor password practices. 

In fact, weak passwords are responsible for an estimated 35% of hacks. With "123456" and "password" ranking among the most common choices, attackers can exploit predictable patterns without needing advanced techniques.

Some of the types of brute force account attacks include:

  • Trial and error hacking is the most basic form of brute force attack. In this approach, attackers repeatedly try different username and password combinations, often using automated scripts, until they find a valid match. The approach relies on guessing random or weak credentials, making it time-consuming but effective against simple passwords.
  • Credential stuffing fraudsters use large sets of previously leaked usernames and passwords, often obtained from data breaches, to attempt logins on various other websites. Since 85% of people reuse passwords across multiple services, attackers can gain access to accounts without needing to guess new credentials.
  • Password spraying involves trying a small set of commonly used passwords (like "123456" or "password") across many accounts instead of focusing on one account with many password attempts. This method takes advantage of users choosing weak, common passwords while also avoiding triggering security mechanisms like account lockouts.

Why is preventing brute force attacks important?

Brute force attacks are a persistent and dangerous threat that can have far-reaching consequences for both individuals and businesses. Preventing these attacks is critical because they can result in unauthorized access to user accounts, leading to significant financial loss, data breaches, and damaged brand reputation. For businesses, the impact goes beyond individual accounts — customer trust is eroded and recovery costs can skyrocket.

These attacks can affect businesses of any size and for various reasons. For instance, Dunkin’ Donuts faced a loyalty point scam starting in 2015, with over 300,000 customer accounts compromised and funds stolen. The following year, Alibaba experienced a breach involving over 20 million accounts, which were later sold or used for fake reviews. More recently, in 2024, Microsoft suffered a password spraying attack by Russian-state hackers, where a weak password on a legacy test account allowed access to corporate email, including that of senior leadership, for up to seven weeks before detection.

These examples show the ongoing importance of strong password policies and fraud prevention strategies in defending against brute force attacks.

How to stop brute force attacks

Businesses can take several proactive measures to prevent brute force attacks. Implementing the right security practices can significantly reduce the likelihood of a successful attack and protect user accounts from being compromised. Below are some effective strategies to strengthen your defenses.

  1. Strong password policies are a foundational defense against brute force attacks. They require complex passwords incorporating a mix of characters, numbers, and symbols and prompt users to change them regularly. This reduces the chances of attackers successfully guessing or cracking weak passwords.
  2. Passwordless authentication alternatives, such as biometrics or magic email links, further strengthen security since attackers cannot guess the password.
  3. Multi-factor authentication (MFA) adds an additional layer of security that ensures that even if a password is compromised, attackers still need a second form of verification, such as a code sent to a user’s phone. This makes brute force attacks much less effective.
  4. Account lockout mechanisms temporarily disable access to an account after a certain number of failed login attempts. This disrupts automated brute force attacks, making it harder for attackers to continue guessing credentials for an account.
  5. Rate limiting the number of login attempts to any account from the same device within a specific time frame can slow down or block brute force attempts altogether.
  6. Bot detection on login pages can help differentiate between legitimate users and automated tools attempting brute force attacks.

By combining multiple measures, businesses can create a robust defense against brute force attacks, protecting user accounts and sensitive data.

Using Fingerprint for brute force prevention

Many of the above prevention methods depend on accurately identifying and recognizing devices, whether it’s enforcing account lockouts, applying rate limits, detecting unfamiliar devices, or spotting bots. This is where Fingerprint comes in!

Fingerprint is a device intelligence platform that assigns a unique identifier to every visitor on your website or mobile app. It works seamlessly in the background whenever someone visits a page with our JavaScript fingerprinting agent installed. This identifier remains stable over time, even with browser updates, incognito mode, VPN use, or cleared cookies. Fingerprint’s Smart Signals also detect suspicious behaviors like bot activity, VPN use, or browser tampering.

By reliably recognizing returning browsers or devices, you can enforce account lockouts and rate limits more effectively while providing smoother logins for legitimate users. With bot detection, you can block automated tools from accessing your login system altogether. 

In the following tutorial, I’ll show you how to integrate Fingerprint into your login logic to prevent brute force attacks and protect user accounts. While the way you use Fingerprint data to prevent fraud will vary based on your specific use case, the following steps outline some best practices.

Identifying a visitor

To get started, you'll need a Fingerprint account to access the features we’ll be integrating. You can sign up for a 14-day free trial. Once you have your public and secret API keys from the Fingerprint dashboard, you're ready to install the JavaScript agent.

The JavaScript agent is a client-side script that collects device and browser attributes, sending them to Fingerprint for processing, identification, and bot detection. You can use the Fingerprint CDN as seen below or one of our various SDKs for popular frameworks.

You’ll want to load the agent as soon as possible on your login page:

const fpPromise = import('<https://fpjscdn.net/v3/PUBLIC_API_KEY>')
  .then(FingerprintJS => FingerprintJS.load())

Then you can wait until the user attempts to login to make the identification request to Fingerprint. Here is a simple login function example:

async function login() {
  // Collect browser signals and request visitor identification
  // from the Fingerprint API. The response contains a `requestId`.
  const fp = await fpPromise;
  const result = await fp.get();
  const { requestId } = result;

  // Send the user’s credentials together with
  // the `requestId` to your authentication API.
  const loginData = {
    username,
    password,
    requestId,
  };

  const response = await fetch("/api/authenticate", {
    method: "POST",
    body: JSON.stringify(loginData),
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
    },
  });
}

While the Fingerprint results also include the visitor ID, we’ll use the request ID and the Fingerprint Server API on the backend to retrieve the complete identification event, ensuring its authenticity through secure server-to-server communication.

If you notice that visitor identification is being blocked, be sure to disable any ad blockers you have running. While protecting Fingerprint identification requests is beyond the scope of this tutorial, you can explore various methods to do so in our guide on protecting your JavaScript agent.

Verifying the identification request

On the server side before using the visitor ID for our login logic, we want to verify that it hasn’t be tampered with or is being reused in a replay attack. Here are some example checks you can do to verify the identification. These snippets use the Fingerprint Node SDK (though you can also call the Server API directly).

First import the library and initialize a new Fingerprint Server API Client:

import {
  FingerprintJsServerApiClient,
  Region,
} from "@fingerprintjs/fingerprintjs-pro-server-api";

const client = new FingerprintJsServerApiClient({
  apiKey: "SECRET_API_KEY",
  region: Region.Global,
});

Then, pull the data from the request body of the login so you can use it in the following checks and get the visitor ID with the Fingerprint Server API.

const { username, password, requestId } = request.body;

const identificationEvent = await client.getEvent(requestId);
const identification = identificationEvent.products?.identification?.data;
const visitorId = identification.visitorId;

To prevent replay attacks, it's advisable to verify the freshness of the identification request, as an attacker could obtain a valid request ID through phishing.

// Identification event must not be more than 3 seconds old
if (Date.now() - Number(new Date(identification.time)) > 3000) {
  return {
    success: false,
    error: "Old identification request, potential replay attack.",
  };
}

You can also confirm that the IP address from the identification matches the client’s IP address, which helps detect potential spoofing attacks.

// This is an example of obtaining the client's IP address.
// In most cases, it's a good idea to look for the right-most
// external IP address in the list to prevent spoofing.
if (request.headers["x-forwarded-for"].split(",")[0] !== identification.ip) {
  return {
    success: false,
    error:
      "Identification IP does not match request IP, potential spoofing attack.",
  };
}

Fingerprint provides you a confidence score for each identification. You may want to request additional verification for identifications below a certain level of confidence.

// The Confidence Score must be of a certain level.
if (identification.confidence.score < 0.95) {
  return {
    success: false,
    error: "Identification confidence too low.",
    action: "promptMFA",
  };
}

Stopping bot attacks

At this point you can add additional checks using Fingerprint’s Smart Signals, such as Bot Detection. The bot detection results can be either bad for malicious bots, good for legitimate bots like search engines, or notDetected for humans. In this case we don’t want to allow any type of bots to access the login, so only notDetected should be allowed.

// Prevent bots from logging in
if (identificationEvent?.products?.botd?.data?.bot?.result !== "notDetected") {
  return {
    success: false,
    error: "Bot detected, login is blocked.",
  };
}

Recognizing legitimate devices

At this point, we want to assess whether the device attempting to log in is one we recognize and have previously linked to the account so we can allow for a smoother login experience. If it’s an unrecognized device for the account, which may indicate an unauthorized access attempt, the user will be prompted to verify their identity with some form of MFA check.

First, retrieve a list of recognized devices from the database. In this example, we're assuming there is a table known_devices where known visitor IDs are stored alongside the accounts they have successfully logged into.

// Query to get known visitor IDs for the given username
const [rows] = await db.execute(
  "SELECT visitor_id FROM known_devices WHERE username = ? GROUP BY visitor_id",
  [username]
);

const knownDevices = rows.map((row) => row.visitor_id);

Once we get the list, we can check if the visitor ID has been seen before for this account.

// Check if the current visitorId is in the list of known devices
if (!knownDevices.includes(visitorId)) {
  return {
    success: false,
    error: "Unknown device detected. Please provide additional verification.",
    action: "promptMFA",
  };
}

Enforcing rate limits

Another method to prevent brute force attacks is by using the visitor ID to enforce rate limits on login attempts. If a device exceeds the permitted number of attempts within a set timeframe, it can be temporarily blocked from logging in, even if it’s using a VPN or incognito mode.

// Add a row for this current login attempt
await db.execute(
  "INSERT INTO login_attempts (visitor_id, last_attempt) VALUES (?, NOW())",
  [visitorId]
);

// Retrieve the number of login attempts in the last 24 hours
const [rows] = await db.execute(
  "SELECT COUNT(*) AS attempts FROM login_attempts WHERE visitor_id = ? AND last_attempt >= NOW() - INTERVAL 24 HOUR",
  [visitorId]
);

// If above your rate limit, block the login
if (rows[0].attempts >= 5) {
  return {
    success: false,
    error: "Too many login attempts. Try again later."
  };
}

At this stage, you can proceed with your usual password checks to complete the login process. Ensure that if the visitor successfully passes the checks and provides the correct credentials, you save the visitor ID with the account for future reference, as demonstrated earlier.

// If all checks pass, proceed with your login process
const loginSuccess = await authenticateUser(username, password); // Pseudo login function
if (!loginSuccess) {
  return {
    success: false,
    error: "Invalid username or password."
  };
}

// Store the visitor ID in the database alongside the username
await db.execute(
  "INSERT INTO known_devices (username, visitor_id, last_login) VALUES (?, ?, NOW())",
  [username, visitorId]
);

// Finally, log the user in
return { success: true, message: "Login successful." };

Secure your user accounts against brute force attacks

Brute force attacks pose a persistent threat to account security, but with the right strategies, businesses can effectively prevent them and protect user accounts. By implementing strong password policies, rate limiting, multi-factor authentication, and using tools like Fingerprint to identify and block suspicious devices, you can prevent unauthorized access while maintaining a smooth login experience for legitimate users.

Contact our team to learn more about how incorporating these best practices into your fraud prevention strategy can safeguard user accounts and your business from the impact of brute force attacks. Alternatively, start a free trial and experience it firsthand!

FAQ

What is a brute force attack?

A brute force attack is a method where attackers try numerous password or login combinations repeatedly until they find the correct one to gain unauthorized access to an account.

How can businesses protect against brute force attacks?

Businesses can protect against brute force attacks by enforcing strong passwords, implementing rate limits, using multi-factor authentication (MFA), incorporating device intelligence, and monitoring suspicious login behavior.

Why are brute force attacks effective?

Brute force attacks are effective because many users use weak or predictable passwords, and attackers can automate attempts to test large numbers of combinations quickly.

Share this post