skip to Main Content

I’ve been spending a few days troubleshooting a failure of certain passwords to validate in Laravel 9. The password testperson resolves to the hash $2y$10$5xc/wAmNCKV.YhpWOfyNoetCj/r3Fs5TyAskgZuIF/LEItWfm7rPW. A direct query on the corresponding database table confirms that this is the correct hash. Yet Laravel’s authentication infrastructure rejects this password and denies authentication.

This is not universal. I have multiple passwords that are resolving correctly. For example, the password eo resolves to $2y$10$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2, and Laravel authenticates that password. The same mechanism creates both of these user records, though they have different permissions (indicated by boolean values on the record).

I tracked down the bug to the function password_verify, which was identified as returning false negatives in this Stack Overflow question and this Treehouse thread.

Specifically, here is the stack in Laravel that gets down to this failure point:

  • The login route calls IlluminateFoundationAuthAuthenticatesUsers::login via the controller class.
  • The login method calls IlluminateFoundationAuthAuthenticatesUsers::attemptLogin.
  • The attemptLogin method calls the attempt method of the controller’s guard object.
  • IlluminateAuthSessionGuard::attempt calls IlluminateAuthSessionGuard::hasValidCredentials.
  • IlluminateAuthSessionGuard::hasValidCredentials calls the validateCredentials method on the guard’s provider object.
  • IlluminateAuthEloquentUserProvider::validateCredentials calls the check method on its hasher object.
  • IlluminateHashingHashManager::check calls the check method on its driver.
  • IlluminateHashingBcryptHasher::check calls IlluminateHashingAbstractHasher::check.
  • IlluminateHashingAbstractHasher::check calls password_verify.

After unwinding this entire stack, I ran 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'));

That dumps this context:

{
"password_verify": false,
"password_unhashed": "testperson",
"password_hashed": "$2y$10$5xc/wAmNCKV.YhpWOfyNoetCj/r3Fs5TyAskgZuIF/LEItWfm7rPW"
}

And if I put that password into a SELECT users WHERE password= query, I get the user that I’m expecting.

What’s going on here? And how do I get around this?

2

Answers


  1. Chosen as BEST ANSWER

    I have a call to Hash::make in the observer for the user class. I discovered that it was running even though it wasn't supposed to, resulting in a duplicate hash.


  2. I think your assertion that the hash you provided is a hash of ‘testperson’ is in fact false. Since hashing is one-way, I can’t tell you what the hash you showed is derived from. NOTE: This runs on PHP 7.4, but I don’t think it will work on PHP 8 and beyond because of the deprecation of the salt option in password_hash().

    <?php
    //$testhash = '$2y$10$5xc/wAmNCKV.YhpWOfyNoetCj/r3Fs5TyAskgZuIF/LEItWfm7rPW';
    $testhash = '$2y$10$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2';
    //$password = "testperson";
    $password = "eo";
    $options = array("cost" => 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...
    $2y$10$5xc/wAmNCKV.YhpWOfyNoeVNPMEcYrxepQeFAssFoAaIYs4WLmgZO
    $2y$10$5xc/wAmNCKV.YhpWOfyNoe
    $2y$10$5xc/wAmNCKV.YhpWOfyNoeVNPMEcYrxepQeFAssFoAaIYs4WLmgZO
    Verified.
    Doesn't need rehash.
    
    eo results...
    $2y$10$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2
    $2y$10$uNWYvMVmagIwQ2eXnVKLCO
    $2y$10$uNWYvMVmagIwQ2eXnVKLCOAK1QFQdcRtxbvlghf.Xpg0U1w.N./N2
    Verified.
    Doesn't need rehash.
    */
    ?>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search