1

Just completed a migration of a site from Drupal 7 to 10, and it's gone pretty smoothly after quite a bit of preparation. One thing seems different in a frustrating way: In the prior site on Drupal7, failed login attempts would be logged as eg "Login attempt failed for admin." or "Login attempt failed for [mis-spelled name]".

Once upgraded, it appears that failed attempts only list a username if that username exists (and there is no user called admin), otherwise these just log an ip address. Am I correct in interpreting this, and is there a simple way to replicate the old behaviour? To be clear, I find it useful to be able to display the username: my clients use this day-to-day supporting users who are getting usernames wrong.

I'll probably find a relevant hook if there's no existing solution.

1 Answer 1

3

Yes, it is correct to say that the username is logged only when there is an account matching that username. That is what UserLoginForm::validateFinal() does.

// Use $form_state->getUserInput() in the error message to guarantee
// that we send exactly what the user typed in. The value from
// $form_state->getValue() may have been modified by validation
// handlers that ran earlier than this one.
$user_input = $form_state->getUserInput();
$query = isset($user_input['name']) ? ['name' => $user_input['name']] : [];
$form_state->setErrorByName('name', $this->t('Unrecognized username or password. <a href=":password">Forgot your password?</a>', [':password' => Url::fromRoute('user.pass', [], ['query' => $query])->toString()]));
$accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name')]);
if (!empty($accounts)) {
  $this->logger('user')
    ->notice('Login attempt failed for %user.', ['%user' => $form_state->getValue('name')]);
}
else {
  // If the username entered is not a valid user,
  // only store the IP address.
  $this->logger('user')
    ->notice('Login attempt failed from %ip.', ['%ip' => $this->getRequest()->getClientIp()]);
}

To log what has been entered in the login form as username, you can add a validation handler that contains code similar to the following one.

if (!$form_state->get('uid')) {
  // The login attempt failed.
  $user_input = $form_state->getUserInput();
  $ip = Drupal::request()->getClientIp();
  \Drupal::logger('mymodule')->notice('Login attempt failed (user: "%user", IP: %ip).', ['%user' => $user_input['name'] ?? '', '%ip' => $ip]);
}

I used $form_state->getUserInput() for the same reason given in the UserLoginForm::validateFinal() comment: $form_state->getUserInput() returns exactly what has been entered in the form, while the value returned by $form_state->getValue() could have been modified by any validation handler.

1
  • That looks fantastic thanks.
    – AlexT
    Commented Jan 21 at 18:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.