Increasing the bcrypt cost or moving to Argon2 applies to new accounts only, because a stored hash cannot be upgraded without the plaintext — and the plaintext is available exactly once.
if (! password_verify($plain, $user->password_hash)) {
throw new InvalidCredentials();
}
// the line most applications never write
if (password_needs_rehash($user->password_hash, PASSWORD_DEFAULT)) {
$user->password_hash = password_hash($plain, PASSWORD_DEFAULT);
$user->save();
}
password_verify reads the algorithm and cost out of the hash string, so a table can hold bcrypt and Argon2 rows simultaneously while the migration runs and nothing has to be coordinated. Without the rehash check inside the successful login path, a cost increase decided in 2019 applies to nobody who registered before it. It is four lines and it is the difference between a policy and an intention.