Search code examples
phpverificationpassword-hash

PHP - password_hash() verification


The following code should be straight forward and simple, the insert into the db on signup creates a hash, but later when I try to login with the same password the hash it is creating isn't matching up to what is in the database (I had print_r's throughout to verify). Can someone see if I'm just overlooking something dumb?

session_start();
require_once("login.php");
$error = "";
$email = "";
$password = "";

if (isset($_GET['logout'])) {
    unset($_SESSION['id']);
    setcookie('id', '', time() - 60*60);
    $_COOKIE['id'] = "";
} else {
    if (isset($_SESSION['id']) or isset($_COOKIE['id'])) {
        header("Location: loggedinpage.php");
    }
}

if (isset($_POST["submit"])) {
    $link = mysqli_connect($hn, $un,$pw,$db);
    if($link->connect_error) die("Fatal Errror.");
    if (!$_POST["email"]) {
        $error .="An email address is required<br>";
    }
    if (!$_POST["password"]) {
        $error .="A password address is required<br>";
    }
    if ($error != "") {
        $error= "<p>There were error(s) in your form:</p>".$error;
    } else {
        if ($_POST['signup'] == 1) {
            $email = mysqli_real_escape_string($link, $_POST['email']);
            $password = mysqli_real_escape_string($link,$_POST['password']);
            $query = "SELECT id FROM `users` WHERE email = '".$email."' LIMIT 1";
            $result=$link->query($query);
            if (mysqli_num_rows($result) > 0) {
                $error = "That email address is taken.";
            } else {
                $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
                $query = "INSERT INTO `users`(`email`,`password`) VALUES ('".$email."', '".$hashedPassword."')";

                if (!mysqli_query($link,$query)) {
                    $error = "<p>Could not sign you up, please try again later</p>";
                } else {
                    $_SESSION['id'] = mysqli_insert_id($link);
                    if(isset($_POST['stayLoggedIn']) and $_POST['stayLoggedIn'] == 1) {
                        setcookie('id', mysqli_insert_id($link), time()+60*60*24);
                    }
                    header("Location: loggedinpage.php");
                }
            }
        } else {
            $email = mysqli_real_escape_string($link, $_POST['email']);
            $password = mysqli_real_escape_string($link, $_POST['password']);
            $hashedPassword = password_hash($password,PASSWORD_DEFAULT);
            $query = "SELECT * FROM users WHERE email = '".$email."' LIMIT 1";
            $result = $link->query($query);
            if ($result->num_rows > 0) {
                $row = $result->fetch_array(MYSQLI_ASSOC);
                if ($email == $row['email'] and password_verify($password,$row['password'])) {
                    if (isset($_POST['stayLoggedIn']) and $_POST['stayLoggedIn'] == 1) {
                        setcookie('id', $row['id'], time()+60*60*24);
                        header("Location: loggedinpage.php");
                    }  
                } else {
                    $error = "Incorrect Username/Password combination";
                }
            }
        }
    }
}

Solution

  • Although it's tucked away at the end of a paragraph, PHP documentation does say that "it is recommended to store the result in a database column that can expand beyond 60 characters (255 characters would be a good choice)."

    The current default algorithm, bcrypt, generates hashes that are 60 characters long. If your database column cannot hold at least this many characters, your hashes will be truncated and verification will fail.


    You've got a few other problems as well:

    • You're modifying the password before generating the hash (with mysqli_real_escape_string())
    • You're not using prepared statements
    • You appear to be relying on cookies for authentication. Cookies are user-generated data, they are not to be trusted! This is why PHP provides session support, because the data is stored on the server.
    • You should not be checking for an existing email address using a query, instead you should have a unique index set on the email column in the database.