Do not email the existing password. Generate a high-entropy token, store only a hash of that token, give it a short expiry time, and invalidate it after a successful reset.
<?php
$selector = bin2hex(random_bytes(8));
$token = random_bytes(32);
$tokenHash = hash('sha256', $token);
$expiresAt = (new DateTimeImmutable('+30 minutes'))->format('Y-m-d H:i:s');
$stmt = $pdo->prepare(
'INSERT INTO password_resets (member_id, selector, token_hash, expires_at)
VALUES (:member_id, :selector, :token_hash, :expires_at)'
);
$stmt->execute([
':member_id' => $memberId,
':selector' => $selector,
':token_hash' => $tokenHash,
':expires_at' => $expiresAt,
]);
$resetUrl = $baseUrl
. '?selector=' . rawurlencode($selector)
. '&token=' . bin2hex($token);
?>
When the member opens the reset URL, locate the row by selector, check the expiry, compare the submitted token hash with hash_equals(), then store the new password with password_hash() and delete the reset row.
https://www.plus2net.com/demo/signup/activepassword.php?ak=edc7a57cc03c4005cdaec0fe15d9cfa7&userid=sampleid
SELECT userid FROM plus_key WHERE pkey='$ak' and userid='$userid' and time > '$tm' and status='pending'
If the record is found ( valid request ) then we will display a form asking the user to enter the new password.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.