diff --git a/core/modules/basic_auth/basic_auth.services.yml b/core/modules/basic_auth/basic_auth.services.yml
index be85ccc..aa13534 100644
--- a/core/modules/basic_auth/basic_auth.services.yml
+++ b/core/modules/basic_auth/basic_auth.services.yml
@@ -1,7 +1,10 @@
 services:
+  basic_auth.credentials_check_flood:
+    class: Drupal\user\Flood\CredentialsCheckFlood
+    arguments: ['@flood', '@entity.manager', '@config.factory', 'basic_auth']
   basic_auth.authentication.basic_auth:
     class: Drupal\basic_auth\Authentication\Provider\BasicAuth
-    arguments: ['@config.factory', '@user.auth', '@flood', '@entity.manager']
+    arguments: ['@config.factory', '@user.auth', '@basic_auth.credentials_check_flood', '@entity.manager']
     tags:
       - { name: authentication_provider, provider_id: 'basic_auth', priority: 100 }
   basic_auth.page_cache_request_policy.disallow_basic_auth_requests:
diff --git a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
index 641ac89..5cd34e7 100644
--- a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
+++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
@@ -12,7 +12,7 @@
 use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Flood\FloodInterface;
+use Drupal\user\Flood\CredentialsCheckFloodInterface;
 use Drupal\user\UserAuthInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
@@ -37,11 +37,11 @@ class BasicAuth implements AuthenticationProviderInterface, AuthenticationProvid
   protected $userAuth;
 
   /**
-   * The flood service.
+   * The credentials check flood service.
    *
-   * @var \Drupal\Core\Flood\FloodInterface
+   * @var \Drupal\user\Flood\CredentialsCheckFloodInterface
    */
-  protected $flood;
+  protected $credentialsCheckFlood;
 
   /**
    * The entity manager.
@@ -57,15 +57,15 @@ class BasicAuth implements AuthenticationProviderInterface, AuthenticationProvid
    *   The config factory.
    * @param \Drupal\user\UserAuthInterface $user_auth
    *   The user authentication service.
-   * @param \Drupal\Core\Flood\FloodInterface $flood
-   *   The flood service.
+   * @param \Drupal\user\Flood\CredentialsCheckFloodInterface $credentials_check_flood
+   *   The credentials check flood service.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager service.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, UserAuthInterface $user_auth, FloodInterface $flood, EntityManagerInterface $entity_manager) {
+  public function __construct(ConfigFactoryInterface $config_factory, UserAuthInterface $user_auth, CredentialsCheckFloodInterface $credentials_check_flood, EntityManagerInterface $entity_manager) {
     $this->configFactory = $config_factory;
     $this->userAuth = $user_auth;
-    $this->flood = $flood;
+    $this->credentialsCheckFlood = $credentials_check_flood;
     $this->entityManager = $entity_manager;
   }
 
@@ -82,49 +82,20 @@ public function applies(Request $request) {
    * {@inheritdoc}
    */
   public function authenticate(Request $request) {
-    $flood_config = $this->configFactory->get('user.flood');
     $username = $request->headers->get('PHP_AUTH_USER');
     $password = $request->headers->get('PHP_AUTH_PW');
-    // Flood protection: this is very similar to the user login form code.
-    // @see \Drupal\user\Form\UserLoginForm::validateAuthentication()
-    // Do not allow any login from the current user's IP if the limit has been
-    // reached. Default is 50 failed attempts allowed in one hour. This is
-    // independent of the per-user limit to catch attempts from one IP to log
-    // in to many different user accounts.  We have a reasonably high limit
-    // since there may be only one apparent IP for all users at an institution.
-    if ($this->flood->isAllowed('basic_auth.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
-      $accounts = $this->entityManager->getStorage('user')->loadByProperties(array('name' => $username, 'status' => 1));
-      $account = reset($accounts);
-      if ($account) {
-        if ($flood_config->get('uid_only')) {
-          // Register flood events based on the uid only, so they apply for any
-          // IP address. This is the most secure option.
-          $identifier = $account->id();
-        }
-        else {
-          // The default identifier is a combination of uid and IP address. This
-          // is less secure but more resistant to denial-of-service attacks that
-          // could lock out all users with public user names.
-          $identifier = $account->id() . '-' . $request->getClientIP();
-        }
-        // Don't allow login if the limit for this user has been reached.
-        // Default is to allow 5 failed attempts every 6 hours.
-        if ($this->flood->isAllowed('basic_auth.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
-          $uid = $this->userAuth->authenticate($username, $password);
-          if ($uid) {
-            $this->flood->clear('basic_auth.failed_login_user', $identifier);
-            return $this->entityManager->getStorage('user')->load($uid);
-          }
-          else {
-            // Register a per-user failed login event.
-            $this->flood->register('basic_auth.failed_login_user', $flood_config->get('user_window'), $identifier);
-          }
-        }
+    $ip = $request->getClientIP();
+
+    if ($this->credentialsCheckFlood->isAllowedHost($ip) && $this->credentialsCheckFlood->isAllowedAccount($ip, $username)) {
+      $uid = $this->userAuth->authenticate($username, $password);
+      if ($uid && !user_is_blocked($username)) {
+        $this->credentialsCheckFlood->clearAccount($ip, $username);
+        return $this->entityManager->getStorage('user')->load($uid);
       }
     }
-    // Always register an IP-based failed login event.
-    $this->flood->register('basic_auth.failed_login_ip', $flood_config->get('ip_window'));
-    return [];
+
+    // Register a failed login attempt.
+    $this->credentialsCheckFlood->register($ip, $username);
   }
 
   /**
diff --git a/core/modules/basic_auth/src/Tests/Authentication/BasicAuthTest.php b/core/modules/basic_auth/src/Tests/Authentication/BasicAuthTest.php
index e548552..6048942 100644
--- a/core/modules/basic_auth/src/Tests/Authentication/BasicAuthTest.php
+++ b/core/modules/basic_auth/src/Tests/Authentication/BasicAuthTest.php
@@ -78,9 +78,9 @@ public function testBasicAuth() {
   }
 
   /**
-   * Test the global login flood control.
+   * Test the global credentials check flood control.
    */
-  function testGlobalLoginFloodControl() {
+  function testGlobalCredentialsCheckFloodControl() {
     $this->config('user.flood')
       ->set('ip_limit', 2)
       // Set a high per-user limit out so that it is not relevant in the test.
@@ -103,9 +103,9 @@ function testGlobalLoginFloodControl() {
   }
 
   /**
-   * Test the per-user login flood control.
+   * Test the per-user credentials check flood control.
    */
-  function testPerUserLoginFloodControl() {
+  function testPerUserCredentialsCheckFloodControl() {
     $this->config('user.flood')
       // Set a high global limit out so that it is not relevant in the test.
       ->set('ip_limit', 4000)
diff --git a/core/modules/user/src/Flood/CredentialsCheckFlood.php b/core/modules/user/src/Flood/CredentialsCheckFlood.php
new file mode 100644
index 0000000..ee39760
--- /dev/null
+++ b/core/modules/user/src/Flood/CredentialsCheckFlood.php
@@ -0,0 +1,161 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Flood\CredentialsCheckFlood.
+ */
+
+namespace Drupal\user\Flood;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Flood\FloodInterface;
+
+/**
+ * Provides flood protection for login credential checks.
+ */
+class CredentialsCheckFlood implements CredentialsCheckFloodInterface {
+
+  /**
+   * The flood service.
+   *
+   * @var \Drupal\Core\Flood\FloodInterface
+   */
+  protected $flood;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * A prefix string prepended to flood events.
+   *
+   * @var string
+   */
+  protected $floodPrefix;
+
+  /**
+   * The name of the config to use.
+   *
+   * @var string
+   */
+  protected $configName;
+
+  /**
+   * Constructs a new user credentials check flood controller.
+   *
+   * @param \Drupal\Core\Flood\FloodInterface $flood
+   *   The flood service.
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory.
+   * @param string $flood_prefix
+   *   A prefix string prepended to flood events.
+   * @param string $config_name
+   *   The name of the config to use.
+   */
+  public function __construct(FloodInterface $flood, EntityManagerInterface $entity_manager, ConfigFactoryInterface $config_factory, $flood_prefix = 'user', $config_name = 'user.flood') {
+    $this->flood = $flood;
+    $this->entityManager = $entity_manager;
+    $this->configFactory = $config_factory;
+    $this->floodPrefix = $flood_prefix;
+    $this->configName = $config_name;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function register($ip, $name) {
+    $config = $this->configFactory->get($this->configName);
+
+    // Register a per-ip failed credentials check event.
+    $this->flood->register($this->floodPrefix . '.failed_login_ip', $config->get('ip_window'), $ip);
+
+    // Register a per-user failed credentials check event.
+    if ($identifier = $this->getAccountIdentifier($ip, $name)) {
+      $this->flood->register($this->floodPrefix . '.failed_login_user', $config->get('user_window'), $identifier);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearHost($ip) {
+    $this->flood->clear($this->floodPrefix . '.failed_login_ip', $ip);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clearAccount($ip, $name) {
+    if ($identifier = $this->getAccountIdentifier($ip, $name)) {
+      $this->flood->clear($this->floodPrefix . '.failed_login_user', $identifier);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isAllowedHost($ip) {
+    $config = $this->configFactory->get($this->configName);
+    return $this->flood->isAllowed($this->floodPrefix . '.failed_login_ip', $config->get('ip_limit'), $config->get('ip_window'), $ip);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isAllowedAccount($ip, $name) {
+    $allowed = TRUE;
+
+    $config = $this->configFactory->get($this->configName);
+    if ($identifier = $this->getAccountIdentifier($ip, $name)) {
+      $allowed = $this->flood->isAllowed($this->floodPrefix . '.failed_login_user', $config->get('user_limit'), $config->get('user_window'), $identifier);
+    }
+
+    return $allowed;
+  }
+
+  /**
+   * Return the identifier used to register account flood events.
+   *
+   * @param string $ip
+   *   The client ip address.
+   * @param string $name
+   *   The account name.
+   *
+   * @return string|NULL
+   *   The flood identifier name or NULL if there is no user account for the
+   *   given name.
+   */
+  protected function getAccountIdentifier($ip, $name) {
+    $config = $this->configFactory->get($this->configName);
+    $storage = $this->entityManager->getStorage('user');
+    $accounts = $storage->loadByProperties(['name' => $name]);
+    $account = reset($accounts);
+    if ($account) {
+      if ($config->get('uid_only')) {
+        // Register flood events based on the uid only, so they apply for any
+        // IP address. This is the most secure option.
+        return $account->id();
+      }
+      else {
+        // The default identifier is a combination of uid and IP address. This
+        // is less secure but more resistant to denial-of-service attacks that
+        // could lock out all users with public user names.
+        return $account->id() . '-' . $ip;
+      }
+    }
+  }
+
+}
diff --git a/core/modules/user/src/Flood/CredentialsCheckFloodInterface.php b/core/modules/user/src/Flood/CredentialsCheckFloodInterface.php
new file mode 100644
index 0000000..038cd63
--- /dev/null
+++ b/core/modules/user/src/Flood/CredentialsCheckFloodInterface.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Flood\CredentialsCheckFloodInterface.
+ */
+
+namespace Drupal\user\Flood;
+
+/**
+ * Provides a flood service tailored to login credential checks.
+ */
+interface CredentialsCheckFloodInterface {
+
+  /**
+   * Registers a new failed credentials check by the given user.
+   *
+   * @param string $ip
+   *   The client ip address.
+   * @param string $name
+   *   The account name.
+   */
+  public function register($ip, $name);
+
+  /**
+   * Clears failed credential checks from the given host.
+   *
+   * @param string $ip
+   *   The client ip address.
+   */
+  public function clearHost($ip);
+
+  /**
+   * Clears failed credential checks by the given user.
+   *
+   * @param string $ip
+   *   The client ip address.
+   * @param string $name
+   *   The account name.
+   */
+  public function clearAccount($ip, $name);
+
+  /**
+   * Whether or not a client machine is allowed to perform a credentials check.
+   *
+   * Do not allow any credential checks from the current user's IP if the limit
+   * has been reached. Default is 50 failed attempts allowed in one hour. This
+   * is independent of the per-user limit to catch attempts from one IP to log
+   * in to many different user accounts.  We have a reasonably high limit since
+   * there may be only one apparent IP for all users at an institution.
+   *
+   * @param string $ip
+   *   The client ip address.
+   *
+   * @return bool
+   *   TRUE if credentials check is allowed, FALSE otherwise.
+   */
+  public function isAllowedHost($ip);
+
+  /**
+   * Whether or not a credentials check with the given account is allowed.
+   *
+   * @param string $ip
+   *   The client ip address.
+   * @param string $name
+   *   The account name.
+   *
+   * @return bool
+   *   TRUE if credentials check is allowed, FALSE otherwise.
+   */
+  public function isAllowedAccount($ip, $name);
+
+}
diff --git a/core/modules/user/src/Form/UserLoginForm.php b/core/modules/user/src/Form/UserLoginForm.php
index 1c7a342..6ba6415 100644
--- a/core/modules/user/src/Form/UserLoginForm.php
+++ b/core/modules/user/src/Form/UserLoginForm.php
@@ -7,11 +7,10 @@
 
 namespace Drupal\user\Form;
 
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Flood\FloodInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\RendererInterface;
+use Drupal\user\Flood\CredentialsCheckFloodInterface;
 use Drupal\user\UserAuthInterface;
 use Drupal\user\UserStorageInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -22,11 +21,11 @@
 class UserLoginForm extends FormBase {
 
   /**
-   * The flood service.
+   * The credentials check flood service.
    *
-   * @var \Drupal\Core\Flood\FloodInterface
+   * @var \Drupal\user\Flood\CredentialsCheckFloodInterface
    */
-  protected $flood;
+  protected $credentialsCheckFlood;
 
   /**
    * The user storage.
@@ -52,8 +51,8 @@ class UserLoginForm extends FormBase {
   /**
    * Constructs a new UserLoginForm.
    *
-   * @param \Drupal\Core\Flood\FloodInterface $flood
-   *   The flood service.
+   * @param \Drupal\user\Flood\CredentialsCheckFloodInterface $credentials_check_flood
+   *   The credentials check flood service.
    * @param \Drupal\user\UserStorageInterface $user_storage
    *   The user storage.
    * @param \Drupal\user\UserAuthInterface $user_auth
@@ -61,8 +60,8 @@ class UserLoginForm extends FormBase {
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   The renderer.
    */
-  public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, UserAuthInterface $user_auth, RendererInterface $renderer) {
-    $this->flood = $flood;
+  public function __construct(CredentialsCheckFloodInterface $credentials_check_flood, UserStorageInterface $user_storage, UserAuthInterface $user_auth, RendererInterface $renderer) {
+    $this->credentialsCheckFlood = $credentials_check_flood;
     $this->userStorage = $user_storage;
     $this->userAuth = $user_auth;
     $this->renderer = $renderer;
@@ -73,7 +72,7 @@ public function __construct(FloodInterface $flood, UserStorageInterface $user_st
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('flood'),
+      $container->get('user.credentials_check_flood'),
       $container->get('entity.manager')->getStorage('user'),
       $container->get('user.auth'),
       $container->get('renderer')
@@ -166,40 +165,19 @@ public function validateName(array &$form, FormStateInterface $form_state) {
    */
   public function validateAuthentication(array &$form, FormStateInterface $form_state) {
     $password = trim($form_state->getValue('pass'));
-    $flood_config = $this->config('user.flood');
     if (!$form_state->isValueEmpty('name') && !empty($password)) {
-      // Do not allow any login from the current user's IP if the limit has been
-      // reached. Default is 50 failed attempts allowed in one hour. This is
-      // independent of the per-user limit to catch attempts from one IP to log
-      // in to many different user accounts.  We have a reasonably high limit
-      // since there may be only one apparent IP for all users at an institution.
-      if (!$this->flood->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
+      $ip = $this->getRequest()->getClientIP();
+      if (!$this->credentialsCheckFlood->isAllowedHost($ip)) {
         $form_state->set('flood_control_triggered', 'ip');
         return;
       }
-      $accounts = $this->userStorage->loadByProperties(array('name' => $form_state->getValue('name'), 'status' => 1));
-      $account = reset($accounts);
-      if ($account) {
-        if ($flood_config->get('uid_only')) {
-          // Register flood events based on the uid only, so they apply for any
-          // IP address. This is the most secure option.
-          $identifier = $account->id();
-        }
-        else {
-          // The default identifier is a combination of uid and IP address. This
-          // is less secure but more resistant to denial-of-service attacks that
-          // could lock out all users with public user names.
-          $identifier = $account->id() . '-' . $this->getRequest()->getClientIP();
-        }
-        $form_state->set('flood_control_user_identifier', $identifier);
 
-        // Don't allow login if the limit for this user has been reached.
-        // Default is to allow 5 failed attempts every 6 hours.
-        if (!$this->flood->isAllowed('user.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
-          $form_state->set('flood_control_triggered', 'user');
-          return;
-        }
+      $username = $form_state->getValue('name');
+      if (!$this->credentialsCheckFlood->isAllowedAccount($ip, $username)) {
+        $form_state->set('flood_control_triggered', 'user');
+        return;
       }
+
       // We are not limited by flood control, so try to authenticate.
       // Store $uid in form state as a flag for self::validateFinal().
       $uid = $this->userAuth->authenticate($form_state->getValue('name'), $password);
@@ -213,17 +191,13 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st
    * This validation function should always be the last one.
    */
   public function validateFinal(array &$form, FormStateInterface $form_state) {
-    $flood_config = $this->config('user.flood');
+    $ip = $this->getRequest()->getClientIP();
     if (!$form_state->get('uid')) {
-      // Always register an IP-based failed login event.
-      $this->flood->register('user.failed_login_ip', $flood_config->get('ip_window'));
-      // Register a per-user failed login event.
-      if ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
-        $this->flood->register('user.failed_login_user', $flood_config->get('user_window'), $flood_control_user_identifier);
-      }
+      $this->credentialsCheckFlood->register($ip, $form_state->getValue('name'));
 
       if ($flood_control_triggered = $form_state->get('flood_control_triggered')) {
         if ($flood_control_triggered == 'user') {
+          $flood_config = $this->configFactory->get('user.flood');
           $form_state->setErrorByName('name', $this->formatPlural($flood_config->get('user_limit'), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => $this->url('user.pass'))));
         }
         else {
@@ -244,10 +218,8 @@ public function validateFinal(array &$form, FormStateInterface $form_state) {
         }
       }
     }
-    elseif ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
-      // Clear past failures for this user so as not to block a user who might
-      // log in and out more than once in an hour.
-      $this->flood->clear('user.failed_login_user', $flood_control_user_identifier);
+    else {
+      $this->credentialsCheckFlood->clearAccount($ip, $form_state->getValue('name'));
     }
   }
 
diff --git a/core/modules/user/src/Tests/UserLoginTest.php b/core/modules/user/src/Tests/UserLoginTest.php
index 8a31137..91a9773 100644
--- a/core/modules/user/src/Tests/UserLoginTest.php
+++ b/core/modules/user/src/Tests/UserLoginTest.php
@@ -35,9 +35,9 @@ function testLoginCacheTagsAndDestination() {
   }
 
   /**
-   * Test the global login flood control.
+   * Test the global credentials check flood control.
    */
-  function testGlobalLoginFloodControl() {
+  function testGlobalCredentialsCheckFloodControl() {
     $this->config('user.flood')
       ->set('ip_limit', 10)
       // Set a high per-user limit out so that it is not relevant in the test.
@@ -72,9 +72,9 @@ function testGlobalLoginFloodControl() {
   }
 
   /**
-   * Test the per-user login flood control.
+   * Test the per-user credentials check flood control.
    */
-  function testPerUserLoginFloodControl() {
+  function testPerUserCredentialsCheckFloodControl() {
     $this->config('user.flood')
       // Set a high global limit out so that it is not relevant in the test.
       ->set('ip_limit', 4000)
diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml
index be4e679..2cd4fa2 100644
--- a/core/modules/user/user.services.yml
+++ b/core/modules/user/user.services.yml
@@ -48,6 +48,9 @@ services:
   user.auth:
     class: Drupal\user\UserAuth
     arguments: ['@entity.manager', '@password']
+  user.credentials_check_flood:
+    class: Drupal\user\Flood\CredentialsCheckFlood
+    arguments: ['@flood', '@entity.manager', '@config.factory', 'user']
   user.private_tempstore:
     class: Drupal\user\PrivateTempStoreFactory
     arguments: ['@keyvalue.expirable', '@lock', '@current_user', '@request_stack', '%user.tempstore.expire%']
