Let’s Talk

Simple and Secure Way to Catch Brute Force Login Attacks, in PHP

Brute force attacks remain one of the most common threats to websites. A brute-force attack works by systematically guessing login credentials until a correct match is found, often targeting a login form with thousands of combinations of usernames and passwords.

Hackers and automated bots take advantage of weak passwords, common words, or simple password combinations without special characters, hoping that at least one user account will allow unauthorized access. Left unchecked, these attempts can overwhelm web servers, slow down site performance, and lead to serious security breaches.

Fortunately, with some proactive steps, you can make brute force attacks much harder to succeed. Below is a simple PHP method to track and block suspicious login activity, helping you secure your website against malicious users.

Step 1; Create Database Table

The first step is to log failed login attempts in your database. Create a table called failed_login_attempts with fields for date (datetime), email (varchar), password (varchar), and ip (varchar).

This allows you to identify patterns of repeated failures – for example, one IP address submitting hundreds of attempts in a short time is a clear sign of a brute force script at work.

Step 2; Store Failed Login Attempts

On each failed login, store the attempt in the table. Never store the raw password input, as even failed attempts might include genuine credentials. Instead, hash the value securely. Change [random_hash_string] to a random string of your own.

function storeFailedLoginAttempt($email, $password, $ip) {
    // Accept user's email, password and IP address

    global $db;

    // Store hashed, repeatable, password (not plaintext as could be genuine login attempts)
    $trackingHash = hash_hmac('sha256', $password, "[random_hash_string]");

    // Prepare the statement
    $stmt = $db->prepare("
        INSERT INTO failed_login_attempts (date, email, password, ip) 
        VALUES (NOW(), ?, ?, ?)
    ");

    $stmt->bind_param("sss", $email, $trackingHash, $ip);

    $stmt->execute();

    $stmt->close();
}

By hashing passwords, you protect against data breaches if the failed login logs themselves are ever compromised.

Step 3; Get Number of Failed Login Attempts

To catch different forms of brute force attacks, you’ll want to monitor attempts in several ways:

Note [random_hash_string] needs to be changed to the same as in Step 1.

function getFailedLoginAttemptsByIp($ip) {
    global $db;

    $sql = "
        SELECT COUNT(1)
        FROM failed_login_attempts
        WHERE ip = ?
          AND date > NOW() - INTERVAL 15 MINUTE
    ";

    $stmt = $db->prepare($sql);

    $stmt->bind_param("s", $ip);

    $stmt->execute();

    // Bind result
    $stmt->bind_result($attempts);
    $stmt->fetch();

    $stmt->close();

    return (int)$attempts;
}

function getFailedLoginAttemptsByEmail($email){
    global $db;

    $sql = "
        SELECT COUNT(1)
        FROM failed_login_attempts
        WHERE email = ?
          AND date > NOW() - INTERVAL 15 MINUTE
    ";

    $stmt = $db->prepare($sql);

    $stmt->bind_param("s", $email);

    $stmt->execute();

    $stmt->bind_result($attempts);
    $stmt->fetch();

    $stmt->close();

    return (int)$attempts;
}

function getFailedLoginAttemptsByPassword($password) {
    global $db;

    $trackingHash = hash_hmac('sha256', $password, "[random_hash_string]");

    $sql = "
        SELECT COUNT(1)
        FROM failed_login_attempts
        WHERE password = ?
          AND date > NOW() - INTERVAL 15 MINUTE
    ";

    $stmt = $db->prepare($sql);

    $stmt->bind_param("s", $password);

    $stmt->execute();

    $stmt->bind_result($attempts);
    $stmt->fetch();

    $stmt->close();

    return (int)$attempts;
}

Step 4; Identify and Stop Brute Force Attempts

With the checks in place, you can now prevent bots from repeatedly hammering your login page.

function suspectBruteForceHack($email, $password, $ip){
    /* Check, in last 15 minutes:
         - IP (10+ attempts)
         - email (5+ failures on same user from any IP)
         - password (5+ failures using same password)
    */

    // IP Check
    $ip_attempts = getFailedLoginAttemptsByIp($ip);
    if($ip_attempts >= 10){
        return ['result' => true,
                'message' => "Too many login attempts. Please wait 15 minutes."];
    }

    // Email Check
    $email_attempts = getFailedLoginAttemptsByEmail($email);
    if($email_attempts >= 5){
        return ['result' => true,
                'message' => "Too many login attempts. Please wait 15 minutes."];
    }

    // Password Check
    $password_attempts = getFailedLoginAttemptsByPassword($password);
    if($password_attempts >= 5){
        return ['result' => true,
                'message' => "Too many login attempts. Please wait 15 minutes."];
    }

    return ['result' => false];
}

Feel free to tweak the number of attempts you want to allow per type and the returned message; we’ve kept it generic to stop any hints for attackers yet still give a helpful message to genuine users who may trigger it.

Use like this:

$response = suspectBruteForceHack($email, $password, $ip);
if($response['result'] === true){
    // Error, output message found in $response['message']
    // Stop login process
}

This simple security measure prevents brute force tools from bombarding your system with endless guesses.

Extra Layers of Security

While the above code forms a strong base, it’s only the first step. Brute force attacks are constantly evolving, and attackers may use credential stuffing attacks with leaked databases, or rotate IP addresses to avoid detection. To further strengthen your defenses:

These extra steps reduce the likelihood of a successful attack and keep user data safe from suspicious activity.

Building Long-Term Security

Improving your login protection is just one part of a wider security posture. Regular maintenance, monitoring, and updates are crucial. Outdated platforms are often targeted by bad actors exploiting critical bugs.

Businesses should also ensure their website development and website design incorporate security from the start, rather than bolting it on afterwards. Regular updates to your website development practices and secure website design help reduce vulnerabilities. Choosing a reliable hosting provider improves resilience against downtime and poor site performance, which attackers often try to exploit.

And that’s it!

Brute force protection doesn’t need to be complex. By following these steps, monitoring for repeated failed attempts, and applying layered security practices like multi-factor authentication, you can greatly reduce the risk of security breaches.

As with most cyber threats, prevention is the most effective way to keep attackers out. Combining simple PHP tracking with good password hygiene and strong security measures gives you a robust foundation for defending against such attacks.

In production you’ll want some error checking on the database queries but everything else is ready to go. This code is free to use at your own discretion. It comes without warranty. Please feel free to feedback any edits or if you need any further help get in touch with us today!