-
Notifications
You must be signed in to change notification settings - Fork 11.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[11.x] Rehash user passwords when validating credentials #48665
Conversation
Does this approach update |
It'll only update when the password is rehashed, which will happen once. |
@valorin can we potentially use |
Oooh, that's a great idea! I'd like to check with the Laravel team to make sure it's a pattern they want, but I'd love to do it if we can. 😁 |
Yeah, that's probably a better approach as to not break implementations that rely on the current version of the Besides, I doubt anyone has a reason to change the field name from |
Converting this to draft as it depends on the status of #48673, which applies the rehashing to Laravel 10 in a backwards compatible way. If that PR is merged, then changes will be required to clean up and finish the upgrade. |
Naive question - where in the code are the criteria for checking whether an upgrade is needed? Is it keyed on any hash with bcrypt cost less than the current standard, or something else? |
It first runs this to check if it needs to rehash:
The rehash is triggered if the hashing configuration is changed, which is defined in the application configuration and needs to be manually changed in the app to be triggered. Any changes to that will trigger a rehash though, even if you go down. but it's not something you change randomly. So only apps that specifically change it will have rehashing triggered. |
Reason 1: A company starts a PHP project in 2006 before Composer even existed. There are millions of PHP code without automation tests. In 2017 the company starts using Laravel side-by-side with the original application. It's not a brand new database. You have to comply with a database that was modelled in 2006. Reason 2: A government contract with local regulations requires the "storage layer which will hold citizen information MUST be defined using the country's local language" Reason 3: A 2019 Laravel project that migrated from Bcrypt to Argon2id decided to create a new column, rehash upon login and keep both hashes in parallel during rollout until they were confident in dropoping the bcrypt hash, but now the project just runs with a I could probably remember of another reason, but I think I've made my point 😅 |
I thing we need to give config key in auth.php for developers to enable rehashing or not |
Just adding my two cents: I think it's a no-brainer that this should be in the default Laravel authentication scaffolding. It's a very simple way to ensure security best-practices are being followed, which greatly benefits the Laravel ecosystem as a whole. |
@valorin guess we can move this out of draft now? |
I've got some changes to make before it can go out of draft. Been a busy few weeks, but I'll try and get them done this week. 🤞 |
The Session guard's attempt() method is a better place to apply rehashing than the validateCredentials() method on the provider. The latter shouldn't have side-effects, as per it's name.
c0d5157
to
cce60ba
Compare
This is good to review and (hopefully) merge into 11.x. 🤞
I've tested this change with both Breeze and Jetstream and they both successfully rehashed the password on login. Something interesting I noticed is |
* | ||
* @param \Illuminate\Contracts\Auth\Authenticatable $user | ||
* @param array $credentials | ||
* @return string|null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to recheck the return type docblocks on the three concrete versions of this method, the contract one is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! Fixed. 👍
@valorin what is the behavior of this when the |
@taylorotwell Yep, just confirmed with Breeze. The password is rehashed on login and the user is successfully logged in, while other sessions are ended. There is no way to avoid the other sessions being ended, but it makes sense that it's triggered by a login rather than just randomly happening, and the user should hopefully associate the two events and not be concerned. It'll also only happen once per user, so won't be a continuous issue. |
Co-authored-by: Chrysanthos <48060191+chrysanthos@users.noreply.github.com>
@valorin would this also work with older apps using Laravel UI instead of Breeze? |
Yep, it should work after upgrading to 11. 👍 From what I can see in the code, UI uses |
This is required to be compatible with the rehashing password feature here laravel/framework#48665
This is required to be compatible with the rehashing password feature here laravel/framework#48665
This is required to be compatible with the rehashing password feature here laravel/framework#48665
Summary
As was discovered after updating bcrypt rounds from 10 to 12 in laravel/laravel#6245 and #48494, user passwords are not currently being rehashed during login when the hashing configuration has changed. This should be considered a security risk as rehashing passwords is an important part of the authentication process. Rehashing passwords during login ensures updates to the hashing configuration, such as increasing rounds/cost, is applied to existing hashes when the plaintext password is available during login.
This PR implements rehashing directly within the Eloquent and Database User Providers, making it a part of the core framework authentication system. The rehashing will happen automatically when user credentials are validated by
*UserProvider::validateCredentials()
, which is called by theAuth::attempt()
helper. This helper is used by both Breeze and Fortify/Jetstream to authenticate users, and is the recommended method in the Laravel docs. This ensures that with Laravel 11, any apps using the authentication system will be properly rehashing passwords.I modelled the new
DatabaseUserProvider::rehashUserPassword()
method on the existingrehashUserPassword()
method, to ensure it is compatible for apps that don't use Eloquent.Breaking Changes
Since rehashing needs to update the database, it needs to know the name of the password attribute on the user model. There is not an existing way to retrieve this information, so I had to add the
getAuthPasswordName()
method to theAuthenticatable
contract. I've implemented it within theAuthenticatable
trait andGenericUser
so most apps won't be impacted, but apps that don't use or extend these two, or have a different password attribute, will need implement this method.This is the reason I'm targeting Laravel 11. I'd love to get it into 10, but I can't see a way without breaking some obscure configuration. It's not a critical security risk, so it doesn't require an immediate fix.
Upgrade Impacts
Patching Laravel 10
Since I know it'll be a question asked many times, this change cannot be directly patched in Laravel 10 due to the BC break.
If you use Laravel Breeze, you can easily patch it to perform rehashing as it copies auth code to the user's app. This isn't directly possible within Jetstream/Fortify, as that code is within the package. There is no point patching these now when a better fix is coming in v11.
Patching Breeze Instructions
\App\Http\Requests\Auth\LoginRequest
fileauthenticate()
methodRateLimiter::clear($this->throttleKey());
and add the following:The method should look something like this: