Repeated login attempts are a common way to attack password-protected applications. PHPRunner and ASPRunner.NET include a built-in option to lock a user account after three unsuccessful login attempts. If that is all you need, enable Lock user account after three unsuccessful logins in the audit and locking settings.
This article demonstrates a different approach: temporarily blocking login attempts from the same IP address. After three unsuccessful attempts from the same IP address, login access is blocked for 30 minutes.
Create a table named LoginAttempts to store the number and time of unsuccessful login attempts for each IP address.
CREATE TABLE LoginAttempts ( IP VARCHAR(45) NOT NULL PRIMARY KEY, Attempts INT NOT NULL DEFAULT 0, LastLogin DATETIME NULL );
CREATE TABLE LoginAttempts ( IP VARCHAR(45) NOT NULL PRIMARY KEY, Attempts INT NOT NULL DEFAULT 0, LastLogin DATETIME NULL );
The IP field is 45 characters long so it can store both IPv4 and IPv6 addresses.
Open your PHPRunner or ASPRunner.NET project and configure the normal login security settings for your application.
Open the Events screen and add the following three global Login page events:
Before validating the username and password, check whether this IP address has reached the limit. If three unsuccessful attempts occurred within the last 30 minutes, stop the login process.
$ip = $_SERVER["REMOTE_ADDR"];
$keys = array();
$keys["IP"] = $ip;
$rs = DB::Select("LoginAttempts", $keys);
$data = $rs->fetchAssoc();
if ($data && intval($data["Attempts"]) >= 3)
{
$lastAttempt = strtotime($data["LastLogin"]);
if ($lastAttempt && time() - $lastAttempt < 30 * 60)
{
$message = "Access denied for 30 minutes";
return false;
}
$values = array();
$values["Attempts"] = 0;
DB::Update("LoginAttempts", $values, $keys);
}
return true;
string ip = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
dynamic keys = XVar.Array();
keys["IP"] = ip;
dynamic rs = DB.Select("LoginAttempts", keys);
dynamic data = rs.fetchAssoc();
if (data && Convert.ToInt32(data["Attempts"]) >= 3)
{
DateTime lastAttempt = Convert.ToDateTime(data["LastLogin"]);
if ((DateTime.Now - lastAttempt).TotalMinutes < 30)
{
message = "Access denied for 30 minutes";
return false;
}
dynamic values = XVar.Array();
values["Attempts"] = 0;
DB.Update("LoginAttempts", values, keys);
}
return true;
After a successful login, reset the failed-attempt counter for the current IP address.
$ip = $_SERVER["REMOTE_ADDR"];
$keys = array();
$keys["IP"] = $ip;
$values = array();
$values["Attempts"] = 0;
DB::Update("LoginAttempts", $values, $keys);
string ip = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
dynamic keys = XVar.Array();
keys["IP"] = ip;
dynamic values = XVar.Array();
values["Attempts"] = 0;
DB.Update("LoginAttempts", values, keys);
After an unsuccessful login, increase the attempt counter and save the time of the attempt.
$ip = $_SERVER["REMOTE_ADDR"];
$keys = array();
$keys["IP"] = $ip;
$rs = DB::Select("LoginAttempts", $keys);
$data = $rs->fetchAssoc();
if ($data)
{
$values = array();
$values["Attempts"] = intval($data["Attempts"]) + 1;
$values["LastLogin"] = date("Y-m-d H:i:s");
DB::Update("LoginAttempts", $values, $keys);
}
else
{
$values = array();
$values["IP"] = $ip;
$values["Attempts"] = 1;
$values["LastLogin"] = date("Y-m-d H:i:s");
DB::Insert("LoginAttempts", $values);
}
string ip = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
dynamic keys = XVar.Array();
keys["IP"] = ip;
dynamic rs = DB.Select("LoginAttempts", keys);
dynamic data = rs.fetchAssoc();
if (data)
{
dynamic values = XVar.Array();
values["Attempts"] = Convert.ToInt32(data["Attempts"]) + 1;
values["LastLogin"] = DateTime.Now;
DB.Update("LoginAttempts", values, keys);
}
else
{
dynamic values = XVar.Array();
values["IP"] = ip;
values["Attempts"] = 1;
values["LastLogin"] = DateTime.Now;
DB.Insert("LoginAttempts", values);
}
Build the project and open the Login page. After three unsuccessful login attempts from the same IP address, additional login attempts from that address are blocked for 30 minutes.
After the 30-minute period expires, the failed-attempt counter is reset and the user can try again. A successful login also resets the counter.
For most applications, the built-in account lockout in PHPRunner and ASPRunner.NET is the simpler option. It locks the user account rather than the IP address and requires no custom event code.
IP-based blocking can be useful as an additional measure when you specifically want to slow repeated login attempts coming from the same source.