diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 2edfc81..5ce561c 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -594,16 +594,18 @@ protected function drupalCompareFiles($file1, $file2) {
    *
    * @param \Drupal\Core\Session\AccountInterface $account
    *   User object representing the user to log in.
+   * @param bool $by_email
+   *   Whether to use email for login instead of username.
    *
    * @see drupalCreateUser()
    */
-  protected function drupalLogin(AccountInterface $account) {
+  protected function drupalLogin(AccountInterface $account, $by_email = FALSE) {
     if ($this->loggedInUser) {
       $this->drupalLogout();
     }
 
     $edit = array(
-      'name' => $account->getUsername(),
+      'name' => $by_email ? $account->getEmail() : $account->getUsername(),
       'pass' => $account->pass_raw
     );
     $this->drupalPostForm('user/login', $edit, t('Log in'));
diff --git a/core/modules/user/config/install/user.settings.yml b/core/modules/user/config/install/user.settings.yml
index 8372ccd..b34b7d6 100644
--- a/core/modules/user/config/install/user.settings.yml
+++ b/core/modules/user/config/install/user.settings.yml
@@ -1,4 +1,5 @@
 anonymous: Anonymous
+user_login_method: username_only
 verify_mail: true
 notify:
   cancel_confirm: true
diff --git a/core/modules/user/config/schema/user.schema.yml b/core/modules/user/config/schema/user.schema.yml
index 627d8a6..975e82b 100644
--- a/core/modules/user/config/schema/user.schema.yml
+++ b/core/modules/user/config/schema/user.schema.yml
@@ -7,6 +7,9 @@ user.settings:
     anonymous:
       type: label
       label: 'Name'
+    user_login_method:
+      type: string
+      label: 'Login credentials'
     verify_mail:
       type: boolean
       label: 'Require email verification when a visitor creates an account'
diff --git a/core/modules/user/src/AccountSettingsForm.php b/core/modules/user/src/AccountSettingsForm.php
index b76897d..12266f4 100644
--- a/core/modules/user/src/AccountSettingsForm.php
+++ b/core/modules/user/src/AccountSettingsForm.php
@@ -143,6 +143,23 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       $form['language'] += content_translation_enable_widget('user', 'user', $form, $form_state);
     }
 
+  // User login settings.
+  $form['user_login'] = array(
+     '#type' => 'details',
+    '#title' => t('User login'),
+   );
+    $form['user_login']['user_login_method'] = array(
+      '#type' => 'radios',
+      '#title' => t('Login credentials'),
+      '#description' => t('The details users may use to identify themselves.'),
+      '#options' => array(
+        USER_LOGIN_USERNAME_ONLY => t('Username'),
+        USER_LOGIN_EMAIL_ONLY => t('E-mail address'),
+        USER_LOGIN_USERNAME_OR_EMAIL => t('Username or e-mail address'),
+      ),
+      '#default_value' => $config->get('user_login_method'),
+    );
+
     // User registration settings.
     $form['registration_cancellation'] = array(
       '#type' => 'details',
@@ -432,6 +449,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
 
     $this->config('user.settings')
       ->set('anonymous', $form_state->getValue('anonymous'))
+      ->set('user_login_method', $form_state['values']['user_login_method'])
       ->set('register', $form_state->getValue('user_register'))
       ->set('password_strength', $form_state->getValue('user_password_strength'))
       ->set('verify_mail', $form_state->getValue('user_email_verification'))
diff --git a/core/modules/user/src/Form/UserLoginForm.php b/core/modules/user/src/Form/UserLoginForm.php
index b336091..d687258 100644
--- a/core/modules/user/src/Form/UserLoginForm.php
+++ b/core/modules/user/src/Form/UserLoginForm.php
@@ -91,13 +91,14 @@ public function getFormId() {
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
     $config = $this->config('system.site');
+    $credentials = $this->config('user.settings')->get('user_login_method');
 
     // Display login form:
     $form['name'] = array(
       '#type' => 'textfield',
-      '#title' => $this->t('Username'),
+      '#title' => $credentials == USER_LOGIN_USERNAME_ONLY ? $this->t('Username') : ($credentials == USER_LOGIN_EMAIL_ONLY ? $this->t('E-mail address') : $this->t('Username or e-mail address')),
       '#size' => 60,
-      '#maxlength' => USERNAME_MAX_LENGTH,
+      '#maxlength' => $credentials == USER_LOGIN_USERNAME_ONLY ? USERNAME_MAX_LENGTH : EMAIL_MAX_LENGTH,
       '#description' => $this->t('Enter your @s username.', array('@s' => $config->get('name'))),
       '#required' => TRUE,
       '#attributes' => array(
@@ -149,12 +150,26 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
   }
 
   /**
-   * Sets an error if supplied username has been blocked.
+   * Depending on user_login_method, delegates to valid_email_address(). Sets an
+   * error if username is blocked.
    */
   public function validateName(array &$form, FormStateInterface $form_state) {
-    if (!$form_state->isValueEmpty('name') && user_is_blocked($form_state->getValue('name'))) {
-      // Blocked in user administration.
-      $form_state->setErrorByName('name', $this->t('The username %name has not been activated or is blocked.', array('%name' => $form_state->getValue('name'))));
+    if (isset($form_state['values']['name'])) {
+      $name = $form_state['values']['name'];
+      $credentials = $this->config('user.settings')->get('user_login_method');
+      if ($credentials != USER_LOGIN_USERNAME_ONLY) {
+        if ($accounts = $this->userStorage->loadByProperties(array('mail' => $name))) {
+          $name = reset($accounts)->getUsername();
+        }
+        // If can't find account, check if valid email address.
+        elseif ($credentials == USER_LOGIN_EMAIL_ONLY && !valid_email_address($name)) {
+          $form_state->setErrorByName('name', $this->t('The e-mail address %email is not valid.', array('%email' => $form_state['values']['name'])));
+        }
+      }
+      // If can find account, check if user is blocked.
+      if ($name && user_is_blocked($name)) {
+        $form_state->setErrorByName('name', $this->t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
+      }
     }
   }
 
@@ -176,8 +191,18 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st
         $form_state->set('flood_control_triggered', 'ip');
         return;
       }
-      $accounts = $this->userStorage->loadByProperties(array('name' => $form_state->getValue('name'), 'status' => 1));
-      $account = reset($accounts);
+
+      $account = FALSE;
+      $credentials = $this->config('user.settings')->get('user_login_method');
+      if ($credentials != USER_LOGIN_USERNAME_ONLY && valid_email_address($form_state['values']['name'])) {
+        $accounts = $this->userStorage->loadByProperties(array('mail' => $form_state['values']['name'], 'status' => 1));
+        $account = reset($accounts);
+      }
+      if (!$account && $credentials != USER_LOGIN_EMAIL_ONLY) {
+        $accounts = $this->userStorage->loadByProperties(array('name' => $form_state['values']['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
@@ -200,9 +225,11 @@ public function validateAuthentication(array &$form, FormStateInterface $form_st
         }
       }
       // 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);
-      $form_state->set('uid', $uid);
+      // Set $form_state['uid'] as a flag for self::validateFinal().
+      $form_state['uid'] = $this->userAuth->authenticate($account->getUsername(), $password);
+    }
+    else {
+      $form_state['uid'] = FALSE;
     }
   }
 
diff --git a/core/modules/user/src/Tests/UserLoginTest.php b/core/modules/user/src/Tests/UserLoginTest.php
index 39ffed5..4d8f5ae 100644
--- a/core/modules/user/src/Tests/UserLoginTest.php
+++ b/core/modules/user/src/Tests/UserLoginTest.php
@@ -34,6 +34,37 @@ function testLoginCacheTagsAndDestination() {
   }
 
   /**
+   * Test that login credentials work with each method.
+   */
+  function testLoginByEmail() {
+    $account = $this->drupalCreateUser(array());
+
+    // Login test with username only, the default method.
+    // Using e-mail should fail with username only method.
+    $this->drupalLogin($account);
+    // Using username should pass.
+    $this->assertFailedLogin($account, NULL, TRUE);
+
+    // Login test with e-mail only method.
+    \Drupal::config('user.settings')
+      ->set('user_login_method', USER_LOGIN_EMAIL_ONLY)
+      ->save();
+    // Using e-mail shoud pass.
+    $this->drupalLogin($account, TRUE);
+    // Using username should fail.
+    $this->assertFailedLogin($account);
+
+    // Login test with username or e-mail method.
+    \Drupal::config('user.settings')
+      ->set('user_login_method', USER_LOGIN_USERNAME_OR_EMAIL)
+      ->save();;
+    // Using e-mail should pass.
+    $this->drupalLogin($account, TRUE);
+    // Using username should pass.
+    $this->drupalLogin($account);
+  }
+
+  /**
    * Test the global login flood control.
    */
   function testGlobalLoginFloodControl() {
@@ -158,10 +189,15 @@ function testPasswordRehashOnLogin() {
    *   - Set to any value to expect an error for too many failed logins per IP
    *   .
    *   - Set to NULL to expect a failed login.
+   * @param bool $by_email
+   *   Authenticate with email instead of username.
    */
-  function assertFailedLogin($account, $flood_trigger = NULL) {
+  function assertFailedLogin(User $account, $flood_trigger = NULL, $by_email = FALSE) {
+    if ($this->loggedInUser) {
+      $this->drupalLogout();
+    }
     $edit = array(
-      'name' => $account->getUsername(),
+      'name' => $by_email ? $account->getEmail() : $account->getUsername(),
       'pass' => $account->pass_raw,
     );
     $this->drupalPostForm('user/login', $edit, t('Log in'));
@@ -176,7 +212,24 @@ function assertFailedLogin($account, $flood_trigger = NULL) {
       }
     }
     else {
-      $this->assertText(t('Unrecognized username or password. Have you forgotten your password?'));
+      switch (\Drupal::config('user.settings')->get('user_login_method')) {
+        case USER_LOGIN_USERNAME_ONLY:
+          $this->assertText(t('Sorry, unrecognized username or password. Have you forgotten your password?'));
+          break;
+
+        case USER_LOGIN_EMAIL_ONLY:
+          if (!$by_email) {
+            $this->assertText(t('The e-mail address @email is not valid.', array('@email' => $account->getUsername())));
+          }
+          else {
+            $this->assertText(t('Sorry, unrecognized e-mail address or password. Have you forgotten your password?'));
+          }
+          break;
+
+        case USER_LOGIN_USERNAME_OR_EMAIL:
+          $this->assertText(t('Sorry, unrecognized username, e-mail address or password. Have you forgotten your password?'));
+          break;
+      }
     }
   }
 }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 3d76074..b69157d 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -47,6 +47,21 @@
 const USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL = 'visitors_admin_approval';
 
 /**
+ * Users can login with username only.
+ */
+const USER_LOGIN_USERNAME_ONLY = 'username_only';
+
+/**
+ * Users can login with e-mail address only.
+ */
+const USER_LOGIN_EMAIL_ONLY = 'email_only';
+
+/**
+ * Users can login using either username or e-mail address.
+ */
+const USER_LOGIN_USERNAME_OR_EMAIL = 'username_or_email';
+
+/**
  * Implements hook_help().
  */
 function user_help($route_name, RouteMatchInterface $route_match) {
