diff --git a/core/modules/user/config/install/user.mail.yml b/core/modules/user/config/install/user.mail.yml
index f8e41ce..6b215b5 100644
--- a/core/modules/user/config/install/user.mail.yml
+++ b/core/modules/user/config/install/user.mail.yml
@@ -4,6 +4,12 @@ cancel_confirm:
 password_reset:
   body: "[user:display-name],\n\nA request to reset the password for your account has been made at [site:name].\n\nYou may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.\n\n--  [site:name] team"
   subject: 'Replacement login information for [user:display-name] at [site:name]'
+mail_change_notification:
+  body: "[user:display-name],\n\nA request to change your email address has been made at [site:name]. In order to complete the change you will need to follow the instructions sent to your new email address within one day."
+  subject: 'Email change information for [user:display-name] at [site:name]'
+mail_change_verification:
+  body: "[user:display-name],\n\nA request to change your email address has been made at [site:name]. You need to verify the change by clicking on the link below or copying and pasting it in your browser:\n\n[user:mail-change-login-url]\n\nThis is a one-time URL, so it can be used only once. It expires after one day. If not used, your email address at [site:name] will not change."
+  subject: 'Email change information for [user:display-name] at [site:name]'
 register_admin_created:
   body: "[user:display-name],\n\nA site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it into your browser:\n\n[user:one-time-login-url]\n\nThis link can only be used once to log in and will lead you to a page where you can set your password.\n\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\n\nusername: [user:name]\npassword: Your password\n\n--  [site:name] team"
   subject: 'An administrator created an account for you at [site:name]'
diff --git a/core/modules/user/config/install/user.settings.yml b/core/modules/user/config/install/user.settings.yml
index 8372ccd..44e0ccf 100644
--- a/core/modules/user/config/install/user.settings.yml
+++ b/core/modules/user/config/install/user.settings.yml
@@ -3,6 +3,8 @@ verify_mail: true
 notify:
   cancel_confirm: true
   password_reset: true
+  mail_change_notification: true
+  mail_change_verification: true
   status_activated: true
   status_blocked: false
   status_canceled: false
diff --git a/core/modules/user/config/schema/user.schema.yml b/core/modules/user/config/schema/user.schema.yml
index 627d8a6..d027c7b 100644
--- a/core/modules/user/config/schema/user.schema.yml
+++ b/core/modules/user/config/schema/user.schema.yml
@@ -20,6 +20,12 @@ user.settings:
         password_reset:
           type: boolean
           label: 'Notify user when password reset'
+        mail_change_notification:
+          type: boolean
+          label: 'Notify user when email changes'
+        mail_change_verification:
+          type: boolean
+          label: 'Require email verification when a user changes their email address'
         status_activated:
           type: boolean
           label: 'Notify user when account is activated'
@@ -61,6 +67,12 @@ user.mail:
   password_reset:
     type: mail
     label: 'Password recovery'
+  mail_change_notification:
+    type: mail
+    label: 'Change mail notification'
+  mail_change_verification:
+    type: mail
+    label: 'Change mail verification'
   register_admin_created:
     type: mail
     label: 'Account created by administrator'
diff --git a/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php
index 8b0149e..b99a0b1 100644
--- a/core/modules/user/src/AccountForm.php
+++ b/core/modules/user/src/AccountForm.php
@@ -386,13 +386,32 @@ protected function flagViolations(EntityConstraintViolationListInterface $violat
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    $account = $this->getEntity($form_state);
+    $new_mail = $form_state->getValue('mail');
+    $old_mail = $account->getEmail();
+
+    if (!$account->isNew() && $old_mail !== $new_mail && !$this->currentUser()->hasPermission('administer users')) {
+
+      // Send a verification to the new email address.
+      $account->setEmail($new_mail);
+      if (_user_mail_notify('mail_change_verification', $account, NULL)) {
+        // Send notification email to the old email address.
+        $account->setEmail($old_mail);
+        _user_mail_notify('mail_change_notification', $account);
+      }
+
+      // The user's email address will be updated after verification.
+      $form_state->setValue('mail', $old_mail);
+
+      drupal_set_message($this->t('Your email address needs to be validated. Further instructions have been sent to your new email address.'), 'warning');
+    }
+
     parent::submitForm($form, $form_state);
 
-    $user = $this->getEntity($form_state);
     // If there's a session set to the users id, remove the password reset tag
     // since a new password was saved.
-    if (isset($_SESSION['pass_reset_'. $user->id()])) {
-      unset($_SESSION['pass_reset_'. $user->id()]);
+    if (isset($_SESSION['pass_reset_'. $account->id()])) {
+      unset($_SESSION['pass_reset_'. $account->id()]);
     }
   }
 }
diff --git a/core/modules/user/src/AccountSettingsForm.php b/core/modules/user/src/AccountSettingsForm.php
index b76897d..fb8fac2 100644
--- a/core/modules/user/src/AccountSettingsForm.php
+++ b/core/modules/user/src/AccountSettingsForm.php
@@ -201,7 +201,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     );
     // These email tokens are shared for all settings, so just define
     // the list once to help ensure they stay in sync.
-    $email_token_help = $this->t('Available variables are: [site:name], [site:url], [user:display-name], [user:account-name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:cancel-url].');
+    $email_token_help = $this->t('Available variables are: [site:name], [site:url], [user:display-name], [user:account-name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:mail-change-login-url], [user:cancel-url].');
 
     $form['email_admin_created'] = array(
       '#type' => 'details',
@@ -303,6 +303,50 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#rows' => 12,
     );
 
+    $form['email_email_change_notification'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Email change notification'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#description' => $this->t("Edit the email messages sent to users' old email address when the email address is changed.") . ' ' . $email_token_help,
+      '#group' => 'email',
+      '#weight' => 11,
+    );
+    $form['email_email_change_notification']['user_email_change_notification_subject'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Subject'),
+      '#default_value' => $mail_config->get('mail_change_notification.subject'),
+      '#maxlength' => 180,
+    );
+    $form['email_email_change_notification']['user_email_change_notification_body'] = array(
+      '#type' => 'textarea',
+      '#title' => $this->t('Body'),
+      '#default_value' => $mail_config->get('mail_change_notification.body'),
+      '#rows' => 12,
+    );
+
+    $form['email_email_change_verification'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Email change verification'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#description' => $this->t("Edit the email messages sent to users' new email address when the email address is changed.") . ' ' . $email_token_help,
+      '#group' => 'email',
+      '#weight' => 13,
+    );
+    $form['email_email_change_verification']['user_email_change_verification_subject'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Subject'),
+      '#default_value' => $mail_config->get('mail_change_verification.subject'),
+      '#maxlength' => 180,
+    );
+    $form['email_email_change_verification']['user_email_change_verification_body'] = array(
+      '#type' => 'textarea',
+      '#title' => $this->t('Body'),
+      '#default_value' => $mail_config->get('mail_change_verification.body'),
+      '#rows' => 12,
+    );
+
     $form['email_activated'] = array(
       '#type' => 'details',
       '#title' => $this->t('Account activation'),
@@ -445,6 +489,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       ->set('cancel_confirm.subject', $form_state->getValue('user_mail_cancel_confirm_subject'))
       ->set('password_reset.body', $form_state->getValue('user_mail_password_reset_body'))
       ->set('password_reset.subject', $form_state->getValue('user_mail_password_reset_subject'))
+      ->set('mail_change_notification.subject', $form_state->getValue('user_email_change_notification_subject'))
+      ->set('mail_change_notification.body', $form_state->getValue('user_email_change_notification_body'))
+      ->set('mail_change_verification.subject', $form_state->getValue('user_email_change_verification_subject'))
+      ->set('mail_change_verification.body', $form_state->getValue('user_email_change_verification_body'))
       ->set('register_admin_created.body', $form_state->getValue('user_mail_register_admin_created_body'))
       ->set('register_admin_created.subject', $form_state->getValue('user_mail_register_admin_created_subject'))
       ->set('register_no_approval_required.body', $form_state->getValue('user_mail_register_no_approval_required_body'))
diff --git a/core/modules/user/src/Controller/ChangeEmailController.php b/core/modules/user/src/Controller/ChangeEmailController.php
new file mode 100644
index 0000000..3984b90
--- /dev/null
+++ b/core/modules/user/src/Controller/ChangeEmailController.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Controller\UserController.
+ */
+
+namespace Drupal\user\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Controller routines for email change routes.
+ */
+class ChangeEmailController extends ControllerBase {
+
+  /**
+   * Returns the user email change page.
+   *
+   * @param int $uid
+   *   UID of user requesting reset.
+   * @param int $timestamp
+   *   The current timestamp.
+   * @param string $new_mail
+   *   The user's new email address.
+   * @param string $hash
+   *   Login link hash.
+   *
+   * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
+   *   The form structure or a redirect response.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   If the timestamp passed is in the future.
+   */
+  public function changeEmailPage($uid, $timestamp, $new_mail, $hash) {
+    $account = \Drupal::entityManager()->getStorage('user')->load($uid);
+
+    // We need to set the new email here to validate the hash correctly,
+    // which is created using the new mail adress. We only save the account
+    // if the hash matches.
+    $account->setEmail($new_mail);
+    $timeout = 24 * 60 * 60;
+
+    if ($timestamp < REQUEST_TIME) {
+      if (REQUEST_TIME - $timestamp > $timeout) {
+        drupal_set_message($this->t('You have tried to use a one-time email address change link for %account that has expired -- your change of email address was not completed. Please visit your account edit page if you wish to attempt the change again.', array('%account' => $account->name)), 'error');
+      }
+      elseif ($this->currentUser()->id() && $this->currentUser()->id() != $account->id()) {
+        drupal_set_message($this->t('You are currently logged in as %user, and are attempting to confirm an email address change for %account, which is not allowed. Please log in as %account and initiate a new change of email request.', array('%user' => $user->name, '%account' => $account->name)), 'error');
+      }
+      elseif ($hash != user_pass_rehash($account, $timestamp)) {
+        drupal_set_message($this->t('There was a problem validating the used link. Please visit your account edit page and retry changing your email address.'), 'error');
+      }
+      elseif ($timestamp > $account->getLastLoginTime() && $timestamp < REQUEST_TIME) {
+        $account->save();
+        drupal_set_message($this->t('Your email address has been changed to %mail.', array('%mail' => $new_mail)));
+      }
+    }
+    else {
+      // Deny access if the timestamp passed is in the future.
+      throw new AccessDeniedHttpException();
+    }
+
+    return $this->redirect('user.page');
+  }
+
+  /**
+   * Generates a unique URL for a one time email change confirmation.
+   *
+   * @param object $account
+   *   An object containing the user account.
+   * @param array $options
+   *   (optional) A keyed array of settings. Supported options are:
+   *   - langcode: A language code to be used when generating locale-sensitive
+   *    URLs. If langcode is NULL the users preferred language is used.
+   *
+   * @return
+   *   A unique URL that provides a one-time email change confirmation for the
+   *   user.
+   */
+  public static function changeEmailUrl($account, $options = []) {
+    $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
+    $url_options = array('absolute' => TRUE, 'language' => \Drupal::languageManager()->getLanguage($langcode));
+    return \Drupal::url('user.change_email', [
+      'uid' => $account->id(),
+      'timestamp' => REQUEST_TIME,
+      'new_mail' => $account->getEmail(),
+      'hash' => user_pass_rehash($account, REQUEST_TIME),
+    ], $url_options);
+  }
+
+}
diff --git a/core/modules/user/src/Controller/UserController.php b/core/modules/user/src/Controller/UserController.php
index 772884b..d3d18f2 100644
--- a/core/modules/user/src/Controller/UserController.php
+++ b/core/modules/user/src/Controller/UserController.php
@@ -71,7 +71,7 @@ public static function create(ContainerInterface $container) {
   }
 
   /**
-   * Returns the user password reset page.
+   * Provides a callback for user email change page.
    *
    * @param int $uid
    *   UID of user requesting reset.
@@ -112,7 +112,6 @@ public function resetPass($uid, $timestamp, $hash) {
     // The current user is not logged in, so check the parameters.
     // Time out, in seconds, until login URL expires.
     $timeout = $config->get('password_reset_timeout');
-    $current = REQUEST_TIME;
 
     /* @var \Drupal\user\UserInterface $user */
     $user = $this->userStorage->load($uid);
@@ -120,11 +119,11 @@ public function resetPass($uid, $timestamp, $hash) {
     // Verify that the user exists and is active.
     if ($user && $user->isActive()) {
       // No time out for first time login.
-      if ($user->getLastLoginTime() && $current - $timestamp > $timeout) {
+      if ($user->getLastLoginTime() && REQUEST_TIME - $timestamp > $timeout) {
         drupal_set_message($this->t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'error');
         return $this->redirect('user.pass');
       }
-      elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= $current) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) {
+      elseif ($user->isAuthenticated() && ($timestamp >= $user->getLastLoginTime()) && ($timestamp <= REQUEST_TIME) && Crypt::hashEquals($hash, user_pass_rehash($user, $timestamp))) {
         $expiration_date = $user->getLastLoginTime() ? $this->dateFormatter->format($timestamp + $timeout) : NULL;
         return $this->formBuilder()->getForm('Drupal\user\Form\UserPasswordResetForm', $user, $expiration_date, $timestamp, $hash);
       }
@@ -210,7 +209,7 @@ public function confirmCancel(UserInterface $user, $timestamp = 0, $hashed_pass
         return batch_process('');
       }
       else {
-        drupal_set_message(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'error');
+        drupal_set_message($this->t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'error');
         return $this->redirect('entity.user.cancel_form', ['user' => $user->id()], ['absolute' => TRUE]);
       }
     }
diff --git a/core/modules/user/src/Tests/UserEditedOwnAccountTest.php b/core/modules/user/src/Tests/UserEditedOwnAccountTest.php
index 017ca85..df05bec 100644
--- a/core/modules/user/src/Tests/UserEditedOwnAccountTest.php
+++ b/core/modules/user/src/Tests/UserEditedOwnAccountTest.php
@@ -10,7 +10,7 @@
 use Drupal\simpletest\WebTestBase;
 
 /**
- * Tests user edited own account can still log in.
+ * Tests the user edit form for the current user.
  *
  * @group user
  */
@@ -23,6 +23,9 @@ class UserEditedOwnAccountTest extends WebTestBase {
    */
   public static $modules = array('user_form_test');
 
+  /**
+   * Tests user edited own account can still log in.
+   */
   function testUserEditedOwnAccount() {
     // Change account setting 'Who can register accounts?' to Administrators
     // only.
@@ -34,7 +37,8 @@ function testUserEditedOwnAccount() {
 
     // Change own username.
     $edit = array();
-    $edit['name'] = $this->randomMachineName();
+    $name = $this->randomMachineName();
+    $edit['name'] = $name;
     $this->drupalPostForm('user/' . $account->id() . '/edit', $edit, t('Save'));
 
     // Log out.
@@ -43,5 +47,20 @@ function testUserEditedOwnAccount() {
     // Set the new name on the user account and attempt to log back in.
     $account->name = $edit['name'];
     $this->drupalLogin($account);
+
+    // Set a new email address for the user account.
+    $new_email = $this->randomMachineName() . '@example.com';
+    $edit = array(
+      'current_pass' => $account->pass_raw,
+      'mail' => $new_email,
+    );
+    $this->drupalPostForm('user/' . $account->id() . '/edit', $edit, t('Save'));
+
+    $site_name = $this->config('system.site')->get('name');
+    $user_mail = $this->drupalGetMails(array(
+      'to' => $new_email,
+      'subject' => "Email change information for $name at $site_name",
+    ));
+    $this->assertTrue(count($user_mail), 'A verification email was sent to the user.');
   }
 }
diff --git a/core/modules/user/src/Tests/UserTokenReplaceTest.php b/core/modules/user/src/Tests/UserTokenReplaceTest.php
index b7877d8..92008fb 100644
--- a/core/modules/user/src/Tests/UserTokenReplaceTest.php
+++ b/core/modules/user/src/Tests/UserTokenReplaceTest.php
@@ -12,6 +12,7 @@
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\simpletest\WebTestBase;
 use Drupal\user\Entity\User;
+use Drupal\user\ChangeEmailController;
 
 /**
  * Generates text using placeholders for dummy content to check user token
@@ -134,6 +135,7 @@ function testUserTokenReplacement() {
     // Generate login and cancel link.
     $tests = array();
     $tests['[user:one-time-login-url]'] = user_pass_reset_url($account);
+    $tests['[user:mail-change-login-url]'] = ChangeEmailController::changeEmailUrl($account);
     $tests['[user:cancel-url]'] = user_cancel_url($account);
 
     // Generate tokens with interface language.
diff --git a/core/modules/user/user.install b/core/modules/user/user.install
index 91a908e..ad523f8 100644
--- a/core/modules/user/user.install
+++ b/core/modules/user/user.install
@@ -1,5 +1,7 @@
 <?php
 
+use Symfony\Component\Yaml\Yaml;
+
 /**
  * @file
  * Install, update and uninstall functions for the user module.
@@ -85,3 +87,18 @@ function user_install() {
     ))
     ->save();
 }
+
+
+/**
+ * Updates config for change mail notifications.
+ */
+function user_update_8002() {
+  $mail_settings = Yaml::parse(file_get_contents(__DIR__ . '/config/install/user.mail.yml'));
+
+  $mail_config = \Drupal::service('config.factory')->getEditable('user.mail');
+  $mail_config->set('mail_change_notification.body', $mail_settings['mail_change_notification']['body']);
+  $mail_config->set('mail_change_notification.subject', $mail_settings['mail_change_notification']['subject']);
+  $mail_config->set('mail_change_verification.body', $mail_settings['mail_change_verification']['body']);
+  $mail_config->set('mail_change_verification.subject', $mail_settings['mail_change_verification']['body']);
+  $mail_config->save();
+}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 3d76074..0877422 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -17,6 +17,7 @@
 use Drupal\user\Entity\User;
 use Drupal\user\RoleInterface;
 use Drupal\user\UserInterface;
+use Drupal\user\Controller\ChangeEmailController;
 
 /**
  * @file
@@ -577,13 +578,12 @@ function user_user_logout($account) {
  *   they can change their password.
  */
 function user_pass_reset_url($account, $options = array()) {
-  $timestamp = REQUEST_TIME;
   $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
   return \Drupal::url('user.reset',
     array(
       'uid' => $account->id(),
-      'timestamp' => $timestamp,
-      'hash' => user_pass_rehash($account, $timestamp),
+      'timestamp' => REQUEST_TIME,
+      'hash' => user_pass_rehash($account, REQUEST_TIME),
     ),
     array(
       'absolute' => TRUE,
@@ -940,6 +940,7 @@ function user_mail($key, &$message, $params) {
  *   properties:
  *   - login: The UNIX timestamp of the user's last login.
  *   - pass: The hashed account login password.
+ *   - email: The user's email address.
  * @param array $options
  *   A keyed array of settings and flags to control the token replacement
  *   process. See \Drupal\Core\Utility\Token::replace().
@@ -947,6 +948,7 @@ function user_mail($key, &$message, $params) {
 function user_mail_tokens(&$replacements, $data, $options) {
   if (isset($data['user'])) {
     $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user'], $options);
+    $replacements['[user:mail-change-login-url]'] = ChangeEmailController::changeEmailUrl($data['user'], $options);
     $replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options);
   }
 }
@@ -1176,6 +1178,8 @@ function user_role_revoke_permissions($rid, array $permissions = array()) {
  *   - 'register_pending_approval': Welcome message, user pending admin
  *     approval.
  *   - 'password_reset': Password recovery request.
+ *   - 'mail_change_notification': Email change notification.
+ *   - 'mail_change_verification': Email change verification.
  *   - 'status_activated': Account activated.
  *   - 'status_blocked': Account blocked.
  *   - 'cancel_confirm': Account cancellation request.
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index 6eea7ec..47677fd 100644
--- a/core/modules/user/user.routing.yml
+++ b/core/modules/user/user.routing.yml
@@ -150,3 +150,14 @@ user.reset:
   options:
     _maintenance_access: TRUE
     no_cache: TRUE
+
+user.change_email:
+  path: 'user/change-mail/{uid}/{timestamp}/{new_mail}/{hash}'
+  defaults:
+    _controller: '\Drupal\user\Controller\ChangeEmailController::changeEmailPage'
+    _title: 'Change email address'
+  requirements:
+    _access: 'TRUE'
+  options:
+    _maintenance_access: TRUE
+    no_cache: TRUE
