Problem/Motivation

In UserLoginForm, first we check if the user name matches a blocked user via an entity query.

Then we check if the user name matches a not-blocked user before checking flood control by username, via another entity query.

Once the user has passed flood control, we then call UserAuth::authenticate() with the username and password - this runs the second entity query again to locate the user by username that we've already done.

Steps to reproduce

Watch database queries (via Drupal's performance testing framework + Gander or just have a look at the test coverage changes.

Proposed resolution

Add UserAuth::authenticateAccount($account, $password) to save looking up the user twice, then use it in UserLoginForm.

Because the authentication validator checks if users are active (and exist) before trying to authenticate them, we only need to check if they're blocked if they fail to validate to show a different validation message. IMO this validation message is a bit questionable, but trying to keep functionality the same.

This is stacked on #3410419: Only clear flood attempts when necessary during user login due to affecting the same lines of test coverage, but the fixes are independent otherwise.

If the login fails, fallback to checking if the user is blocked to maintain the same message in that case.

Deprecate User::validateName()

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3410582

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

catch created an issue. See original summary.

catch’s picture

Another one:

UserLoginForm::validateName() does exactly the same checks as UserLoginForm::validateAuthentication() except that the latter doesn't set the same error message if it can't find an account. If we move the validation message around, we save yet another identical entity query.

catch’s picture

Status: Active » Needs work
catch’s picture

Issue summary: View changes
Status: Needs work » Needs review
catch’s picture

Issue summary: View changes
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Applied the MR and verified I could still login. Test updates show the coverage so think this is good.

catch’s picture

Status: Reviewed & tested by the community » Needs review

Pushed an additional commit to deprecate ::validateName() instead of removing it, and removed the call to user_is_blocked() by just checking status on the entity, this removes an extra query on a failed login too.

Also opened #3410707: Optimize UserAuthenticationController by remove duplicate entity queries as a follow-up, doing it here would double the MR size since UserLoginController more or less copies the form logic 1-1, so we need to make all the same changes there more or less.

Back to needs review for those changes.

catch’s picture

Issue summary: View changes
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Deprecation seems fine, still can login at least.

heddn made their first commit to this issue’s fork.

heddn’s picture

Leaving at RTBC after docs fix.

claudiu.cristea’s picture

Status: Reviewed & tested by the community » Needs work

As we add a new public method let’s strict type all params and the return

Prashant.c made their first commit to this issue’s fork.

prashant.c’s picture

Status: Needs work » Needs review
prashant.c’s picture

Attempting to make a contribution here, I've modified aspects concerning strict types in both function parameters and return values. After local testing, I can confirm that the login functionality is functioning as expected following these adjustments. Updating the status to NR.

Thanks

catch’s picture

Issue tags: +Needs change record

We need a change record for the UserAuthInterface method addition too, should probably be a separate change record to the form validation deprecation.

claudiu.cristea’s picture

Set to NW because of "Needs change record" tag

prashant.c’s picture

I attempted to create a change record https://www.drupal.org/node/3411040 (Draft) as mentioned in #17. It may require a lot of changes.

Kindly review, thankyou.

smustgrave’s picture

Status: Needs review » Needs work

MR has a test failure.

Have not re-reviewed.

heddn’s picture

Do we need to add the interface and the string strict typing here? Given the test failures, that feels like more disruptive then this was originally scoped.

catch’s picture

The existing method on the interface definitely shouldn't have type hints added in this issue, that's out of scope (and would have to be implemented completely differently to how it is in the MR due to bc).

catch’s picture

Status: Needs work » Needs review

Reverted the out of scope changes to the methods we're not adding here, tightened up type hinting on the new method, also made the new method return bool instead of bool/int since we already have the uid available before we call it.

heddn’s picture

Status: Needs review » Reviewed & tested by the community

I fixed one small issue with a doc block. But that was so small I doubt it blocks from re-RTBCing. LGTM.

needs-review-queue-bot’s picture

Status: Reviewed & tested by the community » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

catch’s picture

Status: Needs work » Reviewed & tested by the community

Rebased. Just a couple of merge conflicts in the assertions. #3396196: Separate cache operations from database queries in OpenTelemetry and assertions will allow all the assertions in these tests to use assertSame() which will make it much easier to see the impact of changes like this.

catch’s picture

Rebased again after #3396196: Separate cache operations from database queries in OpenTelemetry and assertions which now makes it much easier to see what the effect on the database query count is.

quietone’s picture

Status: Reviewed & tested by the community » Needs work

I'm triaging RTBC issues. I read the IS, the comments and the MR. I didn't find any unanswered questions or other work to do.

The diff no longer applies, setting to needs work. Also, I left some comments in the MR.

catch’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs change record

Rebased, accepted the suggestions, and updated the comment. Since all those changes were minor, moving back to RTBC.

Also removing the 'needs change record' tag since that's done now.

joseph.olstad’s picture

Nice work on this optimization, greatly appreciated!

slashrsm’s picture

catch’s picture

longwave’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record updates

The change records need a bit of work.

https://www.drupal.org/node/3411040 says we are adding a new interface, but the interface already exists; we are only adding a new method to it.

https://www.drupal.org/node/3410706 doesn't really have enough information. We should explain briefly why ::validateName() is deprecated - the code is merged into ::validateAuthentication(), which isn't clear - and what to do if you were overriding the login form and calling this before.

Saving issue credits.

catch’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs change record updates

Updated both CRs.

alexpott’s picture

Version: 11.x-dev » 10.3.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed 589b8fe870 to 11.x and 95cc37fbfa to 10.3.x. Thanks!

  • alexpott committed 95cc37fb on 10.3.x
    Issue #3410582 by catch, heddn, Prashant.c, smustgrave, claudiu.cristea...

  • alexpott committed 589b8fe8 on 11.x
    Issue #3410582 by catch, heddn, Prashant.c, smustgrave, claudiu.cristea...
alexpott’s picture

alexpott changed the visibility of the branch 3410582-optimize-user-logins to hidden.

alexpott changed the visibility of the branch 3410582-optimize-user-logins to active.

berdir’s picture

This seems problematic.

It breaks https://git.drupalcode.org/project/mail_login/-/blob/8.x-2.x/src/AuthDec... completely because that implements the interface directly as a decorator.

It also breaks the intent of that API because that and other modules (including our custom implementation) used that to allow logging in with the user e-mail and not only as the username. The new method doesn't allow that, because the user entity was already loaded.

Seems like maybe the whole thing should go the other way and maybe this API should check blocked status?

joseph.olstad’s picture

Status: Fixed » Needs work

Based on feedback from @Berdir
something overlooked here.

catch’s picture

I had a quick look at putting the blocked logic on UserAuth::authenticate() but that runs into problems with the $uid/FALSE return value.

There are three reasons the method could return FALSE:
1. The username doesn't exist
2. The password is wrong
3. The user is blocked.

Then on top of that, we've got the extra flood control checking which can prevent login even if none of the above are true, although all that logic is in the form still.

For the login form, we currently tell people if the user is blocked - so to get this information we need to have a loaded user, but we can't get that information from a uid/FALSE return value. If we wanted to load the user outside this method and check if they're blocked, we'd need the uid, but you only get that if the auth was successful, so it would have to run the entity query again.

This makes me think we maybe need a new interface, it could be called UserAuthenticationInterface, and it probably needs to return a value object like UserAuthenticationResult - with ::getAccount() ::isBlocked() methods at least.

berdir’s picture

I don't have a full picture yet, but having a new interface with new methods makes sense to me, then we can check for that and trigger a deprecation + keep the old logic for those sites.

catch’s picture

Status: Needs work » Needs review

This doesn't cleanly revert so I've made a revert MR. Moving direct to RTBC because it's only a couple of small merge conflicts in StandardPerformanceTest so should be good as long as gitlab comes back green.

catch’s picture

  • catch committed d0b0d33b on 10.3.x
    Revert: Issue #3410582 by catch, Prashant.c, heddn, alexpott, smustgrave...

  • catch committed 05c2f783 on 11.x
    Revert: Issue #3410582 by catch, Prashant.c, heddn, alexpott, smustgrave...
catch’s picture

Status: Needs review » Needs work

catch changed the visibility of the branch 3410582-revert to hidden.

catch’s picture

Status: Needs work » Needs review

I started to look at re-rolling #2431357: Extract a reusable UserAuthFlood service and reuse it in UserLoginForm and BasicAuth and UserAuthenticationController but found a couple of issues - it's very far behind and much, much bigger scope than this issue, but more importantly, it would make the mail_login bc break potentially worse by ignoring the old service and using a brand new one, meaning logging in with e-mail would silently fail to work.

However thinking about the various ways the bc break could be ameliorated resulted in an idea - new interface UserAuthenticationInterface with two new methods: ::lookupAccount() and ::authenticateAccount(). mail_login then would only need to override ::lookupAccount() and ::authenticateAccount() could fall through to the interface.

Pushed up changes to the original MR - reverting the revert, then refactoring a bit to make the deprecation more graceful per the above.

This should make things slightly easier, or at least no-worse, for #2431357: Extract a reusable UserAuthFlood service and reuse it in UserLoginForm and BasicAuth and UserAuthenticationController too.

catch’s picture

Because we're deprecating the old interface we now need to incorporate #3410707: Optimize UserAuthenticationController by remove duplicate entity queries and #3410707: Optimize UserAuthenticationController by remove duplicate entity queries here to avoid triggering deprecation errors from those classes.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

catch’s picture

Status: Needs work » Needs review

Rebased.

catch’s picture

Updated the CR as well.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

catch’s picture

Status: Needs work » Needs review
Issue tags: +no-needs-review-bot

Pretty sure the MR is mergable, it might be the needs review bot not liking the revert of the revert.

catch’s picture

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Circled back on this one and appears all threads and feedback have been addressed.

catch’s picture

Version: 10.3.x-dev » 11.x-dev
alexpott’s picture

Version: 11.x-dev » 10.3.x-dev
Status: Reviewed & tested by the community » Fixed

@catch that's a really neat way of addressing the problem. We still support the old interface till Drupal 12 but we allow us to optimise using the new interface. Great idea. We can work out how to clean this up during the 11.x cycle.

I think we could have a follow-up to deprecate UserAuthInterface and remove it from UserAuth.

Committed and pushed 31edb2aa23 to 11.x and 16082d0849 to 10.3.x. Thanks!

  • alexpott committed 16082d08 on 10.3.x
    Issue #3410582 by catch, Prashant.c, heddn, alexpott, smustgrave,...

  • alexpott committed 31edb2aa on 11.x
    Issue #3410582 by catch, Prashant.c, heddn, alexpott, smustgrave,...

  • alexpott committed 006b8174 on 10.3.x
    Revert "Issue #3410582 by catch, Prashant.c, heddn, alexpott, smustgrave...

  • alexpott committed 4df46ba4 on 11.x
    Revert "Issue #3410582 by catch, Prashant.c, heddn, alexpott, smustgrave...
alexpott’s picture

Status: Fixed » Needs work

Oops I didn't spend enough time reviewing \Drupal\user\Controller\UserAuthenticationController. This did not implement the BC correctly. It still needs to call $this->userAuth->authenticate() when the injected $this->userAuth is the old interface - otherwise any of the existing decorations are going to be broken.

catch’s picture

Status: Needs work » Reviewed & tested by the community

Good spot, that would have been annoying. Added the extra interface check - kept it in the one if clause so that it's easier to remove.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I think we still need to change things to prevent double authenticating on failure.

catch’s picture

Status: Needs work » Needs review

Applied the changes, although they didn't quite work, but close enough after some tidying.

smustgrave’s picture

Status: Needs review » Needs work

Left a comment on the MR.

Checking the Change records https://www.drupal.org/node/3410706 when is this deprecated to be removed? May answer a question on the MR.

catch’s picture

Status: Needs work » Needs review

Applied the suggestion on the MR and replied to the other thread.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Thanks, sorry for not catching that before.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 1916b7863e to 11.x and 2076c3d9fe to 10.3.x. Thanks!

  • alexpott committed 2076c3d9 on 10.3.x
    Issue #3410582 by catch, Prashant.c, heddn, alexpott, smustgrave,...

  • alexpott committed 1916b786 on 11.x
    Issue #3410582 by catch, Prashant.c, heddn, alexpott, smustgrave,...
catch’s picture

Tagging this for release highlights - it's not much by itself but combined with other performance improvements could make up part of a decent paragraph.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

anybody’s picture

Seems like we have a bug and BC break here, please see #3456738: BC break in login auth changes from #3444978
Updating to Drupal 10.3.0 currently breaks logins with certain login related contrib modules.

Thank you.