Block Repeated Login Attempts in PHP

Last updated: August 25, 2026.

A login limiter should slow repeated failures without revealing whether a username exists. Track attempts on the server, use short rolling windows, and combine account-based and network-based limits so one mechanism cannot be bypassed easily.

Store rate-limit buckets

CREATE TABLE login_rate_limits (
  bucket_hash CHAR(64) PRIMARY KEY,
  attempts SMALLINT UNSIGNED NOT NULL,
  window_started_at DATETIME NOT NULL,
  blocked_until DATETIME NULL
);

Hash normalized bucket identifiers with a server-side key before storing them. Avoid retaining raw IP addresses longer than needed.

Apply the limiter before password verification

<?php
declare(strict_types=1);

$username = mb_strtolower(trim((string) ($_POST['username'] ?? '')));
$network = (string) ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
$bucket = hash_hmac('sha256', $username . '|' . $network, $_ENV['RATE_LIMIT_KEY']);

if ($limiter->isBlocked($bucket)) {
    http_response_code(429);
    exit('Sign-in is temporarily unavailable. Please try again later.');
}

$user = $users->findForLogin($username);
$valid = $user !== null && password_verify((string) $_POST['password'], $user->passwordHash);

if (!$valid) {
    $limiter->recordFailure($bucket);
    exit('The username or password is incorrect.');
}

$limiter->clear($bucket);
session_regenerate_id(true);

Recommended safeguards

  • Use prepared statements and atomic database updates.
  • Increase delays progressively and cap the blocking period.
  • Return the same message for unknown users and wrong passwords.
  • Log suspicious patterns without recording passwords or session tokens.
  • Add MFA and bot protection for higher-risk accounts.
  • Provide a secure recovery path so attackers cannot permanently lock out a user.
admin

admin

Leave a Reply

Your email address will not be published.