Secure login and authentication in PHP This seems to work for me, but I want to make it more secure. I want to see how far I can go without pre-built packages/bundles, so please don't suggest any.

How secure is this? What are some steps I can take to improve it?

In a controller:

public function loginUser($request, $response) {
    if (Security::isUser()) { return $this-&gt;redirect('home'); }
    $username = $request-&gt;getParam('username');
    $password = $request-&gt;getParam('password');
    $user = Security::authenticate($username, $password);
    if ($user) {
        Security::login($user-&gt;id);
        return $this-&gt;redirect('home');
    } else {
        $this-&gt;flash-&gt;addMessage('error', 'Invalid username and/or password.');
        return $this-&gt;redirect('login');
    }
}


Here is part of the Security class:

public static function hash($string) {
    return password_hash($string, PASSWORD_DEFAULT);
}

public static function authenticate($username, $password) {
    $user = User::where('username', $username)-&gt;orWhere('email', $username)-&gt;first();
    if (password_verify($password, $user-&gt;password) &amp;&amp; $user-&gt;banned === 0) { return $user; }
    else { return false; }
}

public static function getUser() {
    if (isset($_SESSION['uid'])) {
        $user =  User::where('id', $_SESSION['uid'])-&gt;first();
        if ($user-&gt;banned === 1) { return false; }
        else { return $user; }
    } else { return false; }
}

public static function isUser() {
    if (!self::getUser()) { return false; } else { return true; }
}

public static function isAdmin() {
    $user = self::getUser();
    if (!$user) { return false; }
    else if ($user-&gt;role === 'admin') { return true; }
    else { return false; }
}

public static function login($uid) {
    session_regenerate_id();
    $_SESSION['uid'] = $uid;
}

public static function logout() {
    session_unset();
    session_destroy();
}


Then, to secure something:

public function loginUser($request, $response) {
    if (!Security::isUser()) { // or use ::isAdmin()
        // deny access
    }
    // allow access
}


Csrf tokens are checked by Slim's Guard. This is all over https.
This isn't a matter of security, but mashing things onto a single line

else { return $user; }


is considered poor form in most style guidelines.



public static function isUser() {
    if (!self::getUser()) { return false; } else { return true; }
}


reads a bit more naturally if you don't use an inverted condition:

public static function isUser() {
    if (self::getUser()) { return true; } else { return false; }
}


And then it's easy to see that you can simplify it to just

public static function isUser() {
    return (bool)(self::getUser());
}




Similarly,

else if ($user-&gt;role === 'admin') { return true; }
else { return false; }


can be simplified to

return $user-&gt;role === 'admin';




$user = User::where('username', $username)-&gt;orWhere('email', $username)-&gt;first();


is a bit strange.  Users can log in via either email or username?  Are they both unique?  It will simplify your life (and theirs) a lot if there's only one unique identifier for each user.



You're using password_hash and password_verify, which is good.  You may want to consider setting the cost parameter to password_hash to something other than the default to get something that makes sense for your hardware.