Alright first let me make some statements about this thread.
- I realize there are frameworks which do this. I do not care. I’ve
explained reasonings behind this before and you are wasting time
telling me about frameworks. - I never claim this code is perfect. This
is a learning exercise for me. - I’m looking at the general OOP-ness
of this and seeing what principles this class breaks. Security
comments are also appreciated
The login can be played with here
http://www.codefundamentals.com/cadeui/login
admin@codefundamentals.com // password
After attempt 5, you will be locked out for 15 minutes so I recommend on attempt 4, you correctly log in.
When users submit their informatin, it goes to process-login.php
<?php
session_start();
require_once($_SERVER["DOCUMENT_ROOT"]."/cadeui/system/includes/bootstrap.php");
if($_SERVER["REQUEST_METHOD"]=="POST")
{
$email=filter_input(INPUT_POST,"email");
$password=filter_input(INPUT_POST,"password");
$remember=filter_input(INPUT_POST,"remember");
$login=new UserServices($pdo);
$isValidLogin=$login->checkCredentials($email,$password,$remember);
$isAjax=!empty($_SERVER["HTTP_X_REQUESTED_WITH"]) && strtolower($_SERVER["HTTP_X_REQUESTED_WITH"])=="xmlhttprequest";
if($isAjax)
{
$result=["result" => $isValidLogin];
header("Content-Type: application/json");
exit(json_encode($result));
}
if($isValidLogin[0])
header("Location: http://www.codefundamentals.com/cadeui/dashboard");
else
header("Location: http://www.codefundamentals.com/cadeui/login?error=$isValidLogin[1]");
}
else
header("Location: http://www.codefundamentals.com/cadeui/login");
?>
If Javascript is on, it sends the data to this page and will get the results and do actions accordingly. This login will work with/without JS on. Now, the actual class this code references is this
<?php
class UserServices
{
private $pdo;
public function __construct(PDO $pdo)
{
$this->pdo=$pdo;
}
public function checkCredentials($email,$pass,$remember)
{
if($this->delayLogin())
{
$username=filter_var($email,FILTER_SANITIZE_EMAIL);
$password=filter_var($pass,FILTER_SANITIZE_STRING);
$remember=filter_var($remember,FILTER_VALIDATE_BOOLEAN);
$findUser=$this->pdo->prepare("SELECT * FROM Users WHERE username=:username");
$findUser->execute(array(":username" => $username));
if($findUser->rowCount()===0)
{
$error="username";
return array(false,$error);
}
$userDetails=$findUser->fetch(PDO::FETCH_ASSOC);
if(password_verify($password,$userDetails["password"]))
{
if($remember)
$this->setCookie($userDetails);
$this->setSessions($userDetails,$this->findIP());
return array(true,"");
}
else
{
$error="password";
return array(false,$error);
}
}
else
{
$error="attempts";
return array(false,$error);
}
}
private function delayLogin()
{
$grabIPuser=$this->pdo->prepare("SELECT * FROM Attempted_Logins WHERE ip_address = :ip_address");
$grabIPuser->execute(array(":ip_address" => $this->findIP()));
$ipUserDetails=$grabIPuser->fetch(PDO::FETCH_ASSOC);
if($grabIPuser->rowCount()===0)
{
$insertIPdetails=$this->pdo->prepare("INSERT INTO Attempted_Logins (ip_address,login_attempts,last_login_time) VALUES(:ip_address,:login_attempts,:last_login_time)");
$insertIPdetails->execute(array(":ip_address" => $this->findIP(),":login_attempts" => 0,":last_login_time" => $this->getDateTime("now","Y-m-d H:i:s")));
return true;
}
else
{
$dbLastAttempt=$ipUserDetails["last_login_time"];
$lastTimeAttempt=$this->getDateTime($dbLastAttempt,false);
$timeDiff=$lastTimeAttempt->diff($this->getDateTime("now",false));
if($timeDiff->format("%s")>2)
{
if($this->maxAttempts($ipUserDetails,$timeDiff->format("%i")))
return true;
else
return false;
}
else
return false;
}
}
private function maxAttempts($dbIpUser,$resetMinutes)
{
if($dbIpUser["login_attempts"]<5)
{
$updateAttempts=$this->pdo->prepare("UPDATE Attempted_Logins SET login_attempts=:login_attempts WHERE ip_address=:ip_address");
$updateAttempts->execute(array(":ip_address" => $dbIpUser["ip_address"],":login_attempts" => $dbIpUser["login_attempts"]+1));
return true;
}
else
{
if($resetMinutes>=15)
{
$updateAttempts=$this->pdo->prepare("UPDATE Attempted_Logins SET login_attempts=0,last_login_time=:last_login_time WHERE ip_address=:ip_address");
$updateAttempts->execute(array(":ip_address" => $dbIpUser["ip_address"],":last_login_time" => $this->getDateTime("now","Y-m-d H:i:s")));
return true;
}
else
{
$updateAttempts=$this->pdo->prepare("UPDATE Attempted_Logins SET login_attempts=:login_attempts WHERE ip_address=:ip_address");
$updateAttempts->execute(array(":ip_address" => $dbIpUser["ip_address"],":login_attempts" => $dbIpUser["login_attempts"]+1));
return false;
}
}
}
private function setCookie($userInfo)
{
setcookie("rememberMe",$userInfo["authkey"],time() + (86400 * 14),"/");
}
public function userCookies($loginCookie,$action)
{
$cookieUser=$this->pdo->prepare("SELECT * FROM Users WHERE authkey=:authkey");
$cookieUser->execute(array(":authkey" => $loginCookie));
if($cookieUser->rowCount()===0)
return false;
else
{
$cookieUserInfo=$cookieUser->fetch(PDO::FETCH_ASSOC);
if($action==="compare")
return $cookieUser["authkey"];
else if($action==="set")
$this->setSessions($cookieUser,$this->findIP());
else
return null;
}
}
private function setSessions($userDBinfo,$thisIP)
{
$_SESSION["loggedin"]=true;
$_SESSION["username"]=$userDBinfo["username"];
$_SESSION["userID"]=$userDBinfo["id"];
$_SESSION["firstname"]=$userDBinfo["firstname"];
$resetAttempts=$this->pdo->prepare("UPDATE Attempted_Logins SET login_attempts=0,last_login_time=:last_login_time WHERE ip_address=:ip_address");
$resetAttempts->execute(array(":ip_address" => $thisIP,":last_login_time" => $this->getDateTime("now","Y-m-d H:i:s")));
}
private function getDateTime($when,$format)
{
$instance=new DateTime($when);
if($format)
return $instance->format($format);
else
return $instance;
}
private function findIP()
{
return getenv("HTTP_CLIENT_IP")?:
getenv("HTTP_X_FORWARDED_FOR")?:
getenv("HTTP_X_FORWARDED")?:
getenv("HTTP_FORWARDED_FOR")?:
getenv("HTTP_FORWARDED")?:
getenv("REMOTE_ADDR");
}
}
?>