I am building a dashboard system, with Apache running on a raspberry pi, and pre-generating a password and its hash for all new users.
The line I used to do this is password_hash('Password1@', PASSWORD_DEFAULT).
Users are shown a password reset window when they first log in. I am able to successfully use password_hash() and password_verify() after the users click submit on this password reset page.
The first login works just fine but on any login attempt after logging out results in the password_verify() failing.
What I Have Checked/Tried
- Set the password attribute in the database to
varchar(255). - A single user row is retrieved and I can return data from it.
PASSWORD_DEFAULTandPASSWORD_ARGON2IDboth do this.
Things I Know
- Database is
utf8mb4_unicode_ci. - The new password that users set are successfully pushed to the database.
- I have added an
ifstatement to check that the new hash can be verified and it can be.
- I have added an
- Hash string matches what comes back in the
SELECT, as it should. - I used the functions below on other projects, with PHP 7. This project is on PHP 8. (Could this be the issue?)
Password Reset Function
public function firstLoginUpdatePassword($username, $password, $confirm, $token)
{
if ($password != $confirm)
{
header("Location: first-login?mismatch&token=" . $token);
exit;
}
else
{
$newPassword = password_hash($password, PASSWORD_DEFAULT);
$token = bin2hex(openssl_random_pseudo_bytes(16));
try
{
$stmt = $this->con->prepare("UPDATE Account SET Password=:password, isFirstLogin=FALSE, Token=:token WHERE Username=:username");
$stmt->bindparam(":username", $username);
$stmt->bindparam(":password", $newPassword);
$stmt->bindparam(":token", $token);
if ($stmt->execute())
{
header("Location: home");
exit;
}
else
{
header("Location: first-login?error&token=" . $token);
exit;
}
}
catch (PDOException $ex)
{
echo $ex->getMessage();
}
}
}
Login Function
public function Login($user, $pwd)
{
try
{
$stmt = $this->con->prepare("SELECT Username, Password FROM Account WHERE Username=:username or Email=:username;");
$stmt->bindparam(":username", $user);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($stmt->rowCount() == 1)
{
if (password_verify($pwd, $row['Password']))
{
try
{
$stmt = $this->con->prepare("UPDATE Account SET LastLogin=CURRENT_TIMESTAMP WHERE Username=:username;");
$stmt->bindparam(":username", $row['Username']);
if ($stmt->execute())
{
$_SESSION['userSession'] = $row['Username'];
return true;
}
else
{
header("Location: login?error-other");
exit;
}
}
catch (PDOException $ex)
{
echo $ex->getMessage();
}
}
else
{
header("Location: login?error-credential");
exit;
}
}
else
{
header("Location: login?error-login");
exit;
}
}
catch(PDOException $ex)
{
echo $ex->getMessage();
}
}
password_hashandpassword_verifyfunctions. You're sayingif (password_verify($pwd, $row['Password']))is returning FALSE? If so, the only thing I can think of is your query statesWHERE Username=:username or Email=:usernamewhere everything else is onlyWHERE Username=:username. SincePDO::FETCH_ASSOConly fetches the first entry, are there possibly two entries where the Email is the username and another record where Username is the username?ifstatement is returning false, correct. The login function allows a user to put their username or email address, hence theWHEREclause in that function. Everything else I have is set to update based on the username. Both attributes haveunique constraintson them, and I don't run thepassword_verify()if more than one are returned (redundant check, now that I think about it, since I usefetch_assoc). I only have 2 records in the database for now. Very different username/email entries.