[eluser]Madoc[/eluser]
Hello there,
I decided to try an implement a maximum failed login system for Ion auth (that I use on my project). Even though I am very new at php and indeed codeigniter, I thought I would share my work hoping this might help some people.
I am also posting this in the hope at you experts will find ways to improve it and make it more secure and more generic as this only works with MySQL at the moment for query optimisation motivations from my part.
Anyway, here is my code. Please dont be too harsh !
Code:
/*
* CREATE TABLE
* ip_address: ip address of the person that attempted to login
* datetime: when the login attempt happened
* identity: email or username depending on ion_auth settings
*/
DROP TABLE IF EXISTS `auth_failed_login`;
CREATE TABLE IF NOT EXISTS `auth_failed_login` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ip_address` varchar(15) NOT NULL,
`datetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`identity` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
/*
* ion_auth CONFIG FILE
*/
//add to the tables list
$config['tables']['failed_login'] = 'auth_failed_login';
//number of failed login attempts allowed
$config['max_attempts'] = 5;
//limit the max_attempts to an amount of time
//i.e default = 5 attempts allowed within 180 seconds (5 minutes)
$config['max_within'] = 300;
//number of seconds after the last failed attempt the user
//will not be allowed to login (ban time). default = 600 seconds (10 minutes)
$config['ban_seconds'] = 600;
/*
* ion_auth_model MODEL FUNCTIONS
*/
/**
* add_failed_login
*
* create a new failed login line on the database
*
* @return bool
* @author Madoc
**/
public function add_failed_login($identity, $ipaddress)
{
if (!$identity || !$ipaddress) : return false; endif;
$this->db->set('ip_address', $ipaddress);
$this->db->set('identity', $identity);
$this->db->set('datetime', 'now()', false);
return $this->db->insert($this->tables['failed_login']);
}
/**
* get_count_failed_logins
*
* get the number of failed login attempts for a given $identity
* within the period $datefrom to ($datefrom - $period_seconds)
*
* @return int
* @author Madoc
**/
public function get_count_failed_logins($identity, $datefrom, $period_seconds)
{
if (!$identity) : return 0; endif;
if (!$datefrom) : return 0; endif;
if (!$period_seconds) : return 0; endif;
if (!is_numeric($period_seconds)) : return 0; endif;
$sqlquery = "select count(*) as nbattempts from " . $this->tables['failed_login'];
$sqlquery .= " where identity = '".$identity."'";
$sqlquery .= " and datetime between DATE_ADD('".$datefrom."', INTERVAL -".$period_seconds." SECOND) and now()";
$result = $this->db->query($sqlquery);
if ($result)
{
$row = $result->row_array();
return (int)$row['nbattempts'];
}
else
{
return 0;
}
}
/*
* get_last_attempt
*
* returns the timestamp of the last failed attempt for $identity
* and time difference in seconds between the last failed login attempt
* for $identity and NOW()
*
* @author Madoc
*
*/
public function get_last_attempt($identity)
{
if (!$identity) : return false; endif;
$this->db->select('datetime, TIME_TO_SEC(TIMEDIFF(NOW(), datetime)) as timediff',false);
$this->db->where('identity', $identity);
$this->db->order_by('datetime','desc');
$this->db->limit(1);
$result = $this->db->get($this->tables['failed_login']);
if ($result->num_rows > 0)
{
$row = $result->row_array();
return $row;
}
else
{
return false;
}
}
/*
* Ion_auth LIBRARY FUNCTIONS
*/
/**
* is_user_banned
*
* returns true if the identity is banned, false otherwise.
* identity banned = the identity was used more than the
* maximum number of attempts within the maximum allowed failed time
* and for which the last failed login attempt timestamp is
* within the banned period (or the max_within if bigger)
*
* @return bool
* @author Madoc
**/
public function is_user_banned($identity)
{
if (!$identity) : return false; endif;
$max_attempts = $this->ci->config->item('max_attempts', 'ion_auth');
$ban_seconds = $this->ci->config->item('ban_seconds', 'ion_auth');
$max_within = $this->ci->config->item('max_within', 'ion_auth');
//get the last attempt
$last_attempt_array = $this->ci->ion_auth_model->get_last_attempt($identity);
if ($last_attempt_array)
{
//time difference between now and the last failed attempt
$timediff = $last_attempt_array['timediff'];
//datetime of the last failed attempt
$last_attempt = $last_attempt_array['datetime'];
//number of failed attempts between the last failed attempt and the max_within
$count_failed_logins = $this->ci->ion_auth_model->get_count_failed_logins($identity, $last_attempt, $max_within);
//not banned if the maximum number of attempts is not reached
if ($count_failed_logins >= $max_attempts)
{
//at this point, end of ban = end of ban period
//or end of max_minthin if it is longer
if ($max_within > $ban_seconds) : $max_time = $max_within; else: $max_time = $ban_seconds; endif;
if ($timediff < $max_time)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
else
{
//no time difference means no record for this attempt
return false;
}
}
The is_user_banned function is used on my controller prior the login and if the login fails. The table is populated with a new line if the login fails and the user is not banned yet.
PS: Should I post that somewhere else ?