firstly I have tried my best to find a definitive answer on this. Secondly, my code appears to work, but I want to confirm I am doing this in an efficient manner and not leaving myself open to security breaches.
Firstly, I use PHP password_hash when adding user to admin table;
$stmt = $dbh->prepare("INSERT INTO admin (username, password) VALUES (:username, :password)");
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
$password = password_hash('password', PASSWORD_DEFAULT);
Secondly, when a user attempts to login, I retrieve users from the admin table by matching the username ONLY, as I couldn't see a way to check the hash during the query (this is the part I am unsure if there is a better way), and also define the $password variable from the POST input;
$stmt = $dbh->prepare("SELECT * FROM admin WHERE username = :username");
$stmt->bindParam(':username', $username);
$username = $_POST['username'];
// define $password for use in password verify
$password = $_POST['password'];
Thirdly, if there is a result from the query, I run password_verify on the user input to check for a match, and then I branch depending on true or false.
if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
if (password_verify($password, $row['password'])) {
session_start();
foreach ($row as $user) {
$_SESSION['user'] = $row['id'];
}
} else {
$errors = true;
}
header('location: leads.php');
}
else {
$errors = true;
}
I know there are many different ways to hash / secure passwords, but using the native password_hash functions is the way I decided to go, my question is have I done it right / is there a better way?
Thanks in advance.
Basically, your code looks quite okay, except the things already mentioned. As you ask for improvements, especially performance related ones, here we go:
username
If your table gets a lot of entries, remove the index from username
, add a new column called hash
with an index and rewrite your insert and select like this:
INSERT INTO admin (username, password, hash) VALUES (:username, :password, crc32(username))
SELECT * FROM admin WHERE username = :username AND hash=crc32(:username)
I assume, you use MySQL, so adding LIMIT 1
to your query helps the optimizer and stops searching after the row is found.
Avoiding the foreach
-loop is also possible, if you work with just one row.
BTW: header('location: leads.php');
should read header('Location: leads.php');
and using absolute paths makes things more robust.