ldap->isDirectoryAccount($user) && Auth::guard('web')->validate(['email' => $user->email, 'password' => $password])) { $this->rehashIfStale($user, $password); return true; } $identity = $this->ldap->attempt($user->email, $password, $user); if ($identity === null) { return false; } $this->ldap->stamp($user, $identity); return true; } /** * Re-hash a password stored under weaker settings than this * installation now uses. * * Laravel does this inside SessionGuard::attempt(), which neither * caller uses -- they verify and then hand the account to SignIn, * which calls Auth::login(). Neither re-hashes, so without this an * account keeps whatever cost it was created under forever, and * raising BCRYPT_ROUNDS would quietly apply to new accounts only. * * That is not hypothetical: every account the v1 migration carries * across arrives as `$2y$08$…`, because v1 hashed at cost 8, and would * otherwise stay four times cheaper to attack than an account created * here. * * **Only ever called on the local branch.** On the directory branch the * submitted plaintext is the *LDAP* password and the local hash is a * placeholder nobody holds; writing the directory credential into it * would mint a second way into the account that keeps working after * LDAP is switched off. */ private function rehashIfStale(User $user, string $password): void { $guard = Auth::guard('web'); // getProvider() is on SessionGuard rather than on the StatefulGuard // contract. This guard is a SessionGuard in every configuration this // application ships; the check is here so a custom driver degrades // to "no re-hash" instead of a fatal on the login path. if (! $guard instanceof SessionGuard) { return; } // No-ops unless the hasher says the stored digest needs it, so this // costs an already-current account nothing. $guard->getProvider()->rehashPasswordIfRequired($user, ['password' => $password]); } }