Login System using PHP and PDO Prepared Statement
How safe is my code?


I would suggest if you protect the login form from


CSRF (Cross site request forgery) Attacks by generating a unique code for each session and assigning it to a hidden field in the form, and check every post request whether it matches the one from the session or not.




&lt;input type = "hidden" name="token" value="' . $_SESSION['csrf_token'] . '"&gt;




Example: ignore naming convention

   class token {

     public static function generate_csrf_token() {
           if (!isset($_SESSION['csrf_token'])) {
              $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
           }
        }

    public static function check_csrf_token($token) {
        return $token === $_SESSION['csrf_token'];
    }
 }


Then you want to check when the form is submitted and compare both token.


Timing attacks, disable logins requests under a second, mostly bots and robots.



    //Gets the value when the page loads and set it in a session
    $start = microtime(true);

    if (!isset($_SESSION['microtime'])) {
     $_SESSION['microtime'] = $start;
    }


and after the login button is clicked, check and perform the same and then compare the time

if (isset($_POST['submit'])) {
   $finish = microtime(true);
   $seconds_passed = $finish - $_SESSION['microtime'];
   if ($seconds_passed &lt; 1) {

?&gt;



Limit requests from an ip, so for example say, 3 requests maximally allowed per a second, to protect from lots of attack.
Putting captcha and login attempts will be useful.
Session Hijacking multiple things:



Have an identification method that is making each user unique (perhaps hashing user agent and ip) and check every request if they match to see if the current user is the same. And have a session requests tracker that would re-assign a new session id after 25 requests for example. 

 if (!isset($_SESSION['session_clicked_times'])) {
            $_SESSION['session_clicked_times'] = 1;
        } else {
            if (($_SESSION['session_clicked_times']) &gt;= 25) {
                $_SESSION['session_clicked_times'] = 0;
                session_regenerate_id(true);
            }
            $_SESSION['session_clicked_times'] += 1;
        }


Also generate a new id once logged in, and so on.

This code:

 if( $RowCount == 0 ){
   // error   
  } else{ // User exists

      if(password_verify($_POST['password'], $user['password'])){

     //login

      } else {
         //error
        }      
    }  
}


to just this (since they both handle the same thing in your case)

 if( $RowCount == 0 || !password_verify($_POST['password'], $user['password']) ){
       // error   
      } else { 
       //login
    }