I've spent a few days troubleshooting some password validation failures in Laravel 9. Password testperson
resolves to hash $2y$10$5xc/wAmNCKV.YhpWOfyNoetCj/r3Fs5TyAskgZuIF/LEItWfm7rPW
. A direct query of the corresponding database table confirms that this is the correct hash value. However Laravel's authentication infrastructure rejects this password and refuses authentication.
This is not common. I have multiple passwords that parse correctly. For example, the password eo
resolves to $2y$10$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2
, and Laravel validates the password. The same mechanism creates both user records, although they have different permissions (indicated by a boolean value on the record).
I found a bug in the function password_verify
, which was determined to be returning false negatives in this Stack Overflow question and this Treehouse thread.
Specifically, this is the stack in Laravel where this failure point occurs:
login
The route calls \Illuminate\Foundation\Auth\AuthenticatesUsers::login
via the controller class. login
Method calls \Illuminate\Foundation\Auth\AuthenticatesUsers::attemptLogin
. attemptLogin
method calls the attempt
method of the controller guard object. \Illuminate\Auth\SessionGuard::attempt
Calls \Illuminate\Auth\SessionGuard::hasValidCredentials
. \Illuminate\Auth\SessionGuard::hasValidCredentials
Calls the validateCredentials
method on the guard provider object. Illuminate\Auth\EloquentUserProvider::validateCredentials
Call the check
method on its hasher object. Illuminate\Hashing\HashManager::check
Calls the check
method on its driver. Illuminate\Hashing\BcryptHasher::check
Calls Illuminate\Hashing\AbstractHasher::check
. Illuminate\Hashing\AbstractHasher::check
Calls password_verify
. After unwinding the entire stack, I run the following code in the login
method of the login controller:
$provider = $this->guard()->getProvider(); $credentials = $this->credentials($request); $user = $provider->retrieveByCredentials($credentials); $password_unhashed = $request['password']; $password_hashed = $user->getAuthPassword(); $password_verify = password_verify($password_unhashed, $password_hashed); logger('attemping login', compact('password_verify','password_unhashed','password_hashed'));
Dump this context:
{ "password_verify": false, "password_unhashed": "testperson", "password_hashed": "yxc/wAmNCKV.YhpWOfyNoetCj/r3Fs5TyAskgZuIF/LEItWfm7rPW" }
If I put that password into a SELECT users WHERE password=
query, I get the users I expect.
How is this going? How can I solve this problem?
P粉4640820612024-03-29 11:37:34
I think your assertion that the hash you provided is the "testperson" hash is actually false. Since hashes are one-way, I can't tell you where the hash shown comes from. NOTE: It works on PHP 7.4, but I don't think it will work on PHP 8 and above since the salt option in passwd_hash() is deprecated.
10, "salt" => substr($testhash, 7, 22)); $pwhash = password_hash($password, PASSWORD_BCRYPT, $options); echo $pwhash."\n"; $salt = substr($pwhash, 0, 29); echo $salt."\n"; $cryptpw = crypt($password, $salt); echo $cryptpw."\n"; if (password_verify($password, $cryptpw)) { echo("Verified.\n"); } else { echo("NOT Verified.\n"); } if (password_needs_rehash($cryptpw, PASSWORD_BCRYPT, $options)) { echo("Needs rehash.\n"); } else { echo("Doesn't need rehash.\n"); } /* testperson results... yxc/wAmNCKV.YhpWOfyNoeVNPMEcYrxepQeFAssFoAaIYs4WLmgZO yxc/wAmNCKV.YhpWOfyNoe yxc/wAmNCKV.YhpWOfyNoeVNPMEcYrxepQeFAssFoAaIYs4WLmgZO Verified. Doesn't need rehash. eo results... y$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2 y$uNWYvMVmagIwQ2eXnVKLCO y$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2 Verified. Doesn't need rehash. */ ?>