Laravel's Hash facade wraps bcrypt (or Argon2) hashing — a one-way transformation that can never be reversed to recover the original password, which is exactly why verification works by re-hashing the input and comparing hashes, not by decrypting anything.
Hashing a password
use Illuminate\Support\Facades\Hash;
$hashedPassword = Hash::make('user-plain-text-password');
The resulting hash includes a randomly generated salt baked directly into its own output string — this is exactly why hashing the same plain-text password twice produces two visibly different hash strings, and it's expected, correct behavior, not a bug.
Verifying a password against its stored hash
if (Hash::check('user-entered-password', $user->password)) {
// password matches
}
Hash::check() works by hashing the entered password using the same algorithm and the salt already embedded in the stored hash, then comparing the two resulting hash strings — the original plain-text password is never recovered or decrypted at any point in this process, since bcrypt has no decrypt operation at all.
How this is used during login (mostly automatic via Auth::attempt)
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// logged in
}
Auth::attempt() already calls Hash::check() internally against the stored password — manually calling Hash::make()/Hash::check() directly is really only needed outside the standard login flow, such as when requiring a password re-confirmation for a sensitive action.
Checking if a hash needs rehashing (after an algorithm or cost-factor upgrade)
if (Hash::needsRehash($user->password)) {
$user->password = Hash::make($plainPassword);
$user->save();
}
needsRehash() detects whether a stored hash was created with an older algorithm or a lower cost factor than the application's current configuration — genuinely useful for gradually upgrading a large existing user base's password hashes to a stronger setting, without forcing every user to reset their password at once.
Configuring the hashing algorithm and cost factor
// config/hashing.php
'driver' => 'bcrypt',
'bcrypt' => [
'rounds' => 12,
],
Increasing rounds makes each hash operation slower and more computationally expensive — deliberately so, since this is precisely what makes a brute-force attack against stolen password hashes impractically slow; the trade-off is a marginally slower login for genuine users, generally an acceptable cost for the security gained.
Why never storing a plain-text password matters even internally
Even code that has direct database access should never be able to read a user's actual password — hashing (rather than encryption, which is reversible with the right key) is what guarantees this, since there is no possible operation, key, or access level that can recover the original password from a bcrypt hash, protecting users even in the event of a full database compromise.
Never rolling a custom hashing function instead
Writing custom password-hashing logic (like a single round of md5() or sha256()) is a serious security mistake — these general-purpose hash functions are deliberately fast, which makes them practical to brute-force at scale, unlike bcrypt and Argon2, which are deliberately slow and tunable specifically to resist that kind of attack.