Reader Stacks

Password Hashing and Verification in Laravel

The Hash facade, why bcrypt/argon2 hashes look different every time for the same password, and the correct way to verify a login without ever decrypting anything.

Password Hashing and Verification in Laravel

Passwords should never be encrypted (reversible) — they should be hashed (one-way). Laravel's Hash facade defaults to bcrypt, with argon2id available as a configurable alternative, and both are designed so the plaintext password can never be recovered from the stored hash, only verified against it.

Hashing a password

use Illuminate\Support\Facades\Hash;

$hashed = Hash::make($request->password);

With Eloquent's hashed cast (Laravel 10+), this happens automatically whenever the attribute is set — no manual Hash::make() call needed in the controller:

protected function casts(): array
{
    return [
        'password' => 'hashed',
    ];
}

Why the same password produces a different hash every time

Both bcrypt and argon2 include a random salt baked into the output string itself, so hashing "password123" twice produces two different-looking hashes — this is intentional and prevents identical passwords from producing identical hashes across different user rows (which would otherwise leak who shares a password).

Verifying a login — never re-hash and compare strings

if (Hash::check($request->password, $user->password)) {
    // correct password
}

Hash::check() re-derives the hash using the same salt embedded in the stored value and compares in constant time — this is the only correct way to verify a password. Comparing Hash::make($input) === $storedHash directly will fail even for the correct password, because a fresh Hash::make() call generates a new random salt each time.

Checking if a hash needs rehashing

If you ever change the hashing algorithm or cost factor in config/hashing.php, existing stored hashes stay valid (Laravel identifies the algorithm from the hash's own prefix) but won't benefit from the new setting until rehashed:

if (Hash::needsRehash($user->password)) {
    $user->update(['password' => $request->password]); // rehashes under the hashed cast
}

This is typically checked once, right after a successful login (you already have the plaintext password at that moment) — never proactively against stored hashes, since you can't rehash a password you don't have in plaintext.

Topics: Authentication & Access Control