diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index fdde96dbf8642f4764c06e6ae18a091f17331360..8081782dacb0a7c4a328bd1f727bf3e908434d17 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -2529,8 +2529,8 @@ function install_configure_form_submit($form, &$form_state) {
   $account->name = $form_state['values']['account']['name'];
   $account->save();
   // Load global $user and perform final login tasks.
-  $user = user_load(1);
-  user_login_finalize();
+  $account = user_load(1);
+  user_login_finalize($account);
 
   // Record when this install ran.
   variable_set('install_time', $_SERVER['REQUEST_TIME']);
diff --git a/core/modules/user/lib/Drupal/user/Controller/UserController.php b/core/modules/user/lib/Drupal/user/Controller/UserController.php
index e7820194456149fdda8a01a7ff102cfaaea2e19f..eb1dbdedb81727e686eeaf500d6a3774f419db33 100644
--- a/core/modules/user/lib/Drupal/user/Controller/UserController.php
+++ b/core/modules/user/lib/Drupal/user/Controller/UserController.php
@@ -7,27 +7,38 @@
 
 namespace Drupal\user\Controller;
 
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\user\Form\UserLoginForm;
+use Symfony\Component\DependencyInjection\ContainerAware;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Request;
-use Drupal\Core\Controller\ControllerInterface;
 
 /**
  * Controller routines for user routes.
  */
-class UserController implements ControllerInterface {
+class UserController extends ContainerAware {
 
   /**
-   * Constructs an UserController object.
-   */
-  public function __construct() {
-  }
-
-  /**
-   * {@inheritdoc}
+   * Returns the user page.
+   *
+   * Displays user profile if user is logged in, or login form for anonymous
+   * users.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   *
+   * @return \Symfony\Component\HttpFoundation\RedirectResponse|array
+   *   Returns either a redirect to the user page or the render
+   *   array of the login form.
    */
-  public static function create(ContainerInterface $container) {
-    return new static();
+  public function userPage(Request $request) {
+    global $user;
+    if ($user->uid) {
+      $response = new RedirectResponse(url('user/' . $user->uid, array('absolute' => TRUE)));
+    }
+    else {
+      $response = drupal_get_form(UserLoginForm::create($this->container), $request);
+    }
+    return $response;
   }
 
   /**
diff --git a/core/modules/user/lib/Drupal/user/Form/UserLoginForm.php b/core/modules/user/lib/Drupal/user/Form/UserLoginForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..93236f1e09f6e1b75aeea5394b1edda17492b105
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Form/UserLoginForm.php
@@ -0,0 +1,243 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Form\UserLoginForm.
+ */
+
+namespace Drupal\user\Form;
+
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Controller\ControllerInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Flood\FloodInterface;
+use Drupal\Core\Form\FormInterface;
+use Drupal\user\UserStorageControllerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides a user login form.
+ */
+class UserLoginForm implements FormInterface, ControllerInterface {
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * The request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * The flood service.
+   *
+   * @var \Drupal\Core\Flood\FloodInterface
+   */
+  protected $flood;
+
+  /**
+   * The user storage controller.
+   *
+   * @var \Drupal\user\UserStorageControllerInterface
+   */
+  protected $storageController;
+
+  /**
+   * Constructs a new UserLoginForm.
+   *
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The config factory.
+   * @param \Drupal\Core\Flood\FloodInterface $flood
+   *   The flood service.
+   * @param \Drupal\user\UserStorageControllerInterface $storage_controller
+   *   The user storage controller.
+   */
+  public function __construct(ConfigFactory $config_factory, FloodInterface $flood, UserStorageControllerInterface $storage_controller) {
+    $this->configFactory = $config_factory;
+    $this->flood = $flood;
+    $this->storageController = $storage_controller;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('flood'),
+      $container->get('plugin.manager.entity')->getStorageController('user')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'user_login_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, Request $request = NULL) {
+    $this->request = $request;
+    // Display login form:
+    $form['name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Username'),
+      '#size' => 60,
+      '#maxlength' => USERNAME_MAX_LENGTH,
+      '#description' => t('Enter your @s username.', array('@s' => $this->configFactory->get('system.site')->get('name'))),
+      '#required' => TRUE,
+      '#attributes' => array(
+        'autocorrect' => 'off',
+        'autocapitalize' => 'off',
+        'spellcheck' => 'false',
+        'autofocus' => 'autofocus',
+      ),
+    );
+
+    $form['pass'] = array(
+      '#type' => 'password',
+      '#title' => t('Password'),
+      '#size' => 60,
+      '#description' => t('Enter the password that accompanies your username.'),
+      '#required' => TRUE,
+    );
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
+
+    $form['#validate'][] = array($this, 'validateName');
+    $form['#validate'][] = array($this, 'validateAuthentication');
+    $form['#validate'][] = array($this, 'validateFinal');
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $accounts = $this->storageController->load(array($form_state['uid']));
+    $account = reset($accounts)->getBCEntity();
+    $form_state['redirect'] = 'user/' . $account->id();
+
+    user_login_finalize($account);
+  }
+
+  /**
+   * Sets an error if supplied username has been blocked.
+   */
+  public function validateName(array &$form, array &$form_state) {
+    if (!empty($form_state['values']['name']) && user_is_blocked($form_state['values']['name'])) {
+      // Blocked in user administration.
+      form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
+    }
+  }
+
+  /**
+   * Checks supplied username/password against local users table.
+   *
+   * If successful, $form_state['uid'] is set to the matching user ID.
+   */
+  public function validateAuthentication(array &$form, array &$form_state) {
+    $password = trim($form_state['values']['pass']);
+    $flood_config = $this->configFactory->get('user.flood');
+    if (!empty($form_state['values']['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'))) {
+        $form_state['flood_control_triggered'] = 'ip';
+        return;
+      }
+      $accounts = $this->storageController->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
+          // 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->request->getClientIP();
+        }
+        $form_state['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['flood_control_triggered'] = 'user';
+          return;
+        }
+      }
+      // We are not limited by flood control, so try to authenticate.
+      // Set $form_state['uid'] as a flag for user_login_final_validate().
+      $form_state['uid'] = user_authenticate($form_state['values']['name'], $password);
+    }
+  }
+
+  /**
+   * Checks if user was not authenticated, or if too many logins were attempted.
+   *
+   * This validation function should always be the last one.
+   */
+  public function validateFinal(array &$form, array &$form_state) {
+    $flood_config = $this->configFactory->get('user.flood');
+    if (empty($form_state['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 (isset($form_state['flood_control_user_identifier'])) {
+        $this->flood->register('user.failed_login_user', $flood_config->get('user_window'), $form_state['flood_control_user_identifier']);
+      }
+
+      if (isset($form_state['flood_control_triggered'])) {
+        if ($form_state['flood_control_triggered'] == 'user') {
+          form_set_error('name', format_plural($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' => url('user/password'))));
+        }
+        else {
+          // We did not find a uid, so the limit is IP-based.
+          form_set_error('name', t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
+        }
+      }
+      else {
+        form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password', array('query' => array('name' => $form_state['values']['name']))))));
+        $accounts = $this->storageController->loadByProperties(array('name' => $form_state['values']['name']));
+        if (!empty($accounts)) {
+          watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
+        }
+        else {
+          // If the username entered is not a valid user,
+          // only store the IP address.
+          watchdog('user', 'Login attempt failed from %ip.', array('%ip' => $this->request->getClientIp()));
+        }
+      }
+    }
+    elseif (isset($form_state['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', $form_state['flood_control_user_identifier']);
+    }
+  }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Block/UserLoginBlock.php b/core/modules/user/lib/Drupal/user/Plugin/Block/UserLoginBlock.php
index 0c8aa2a4e10bea9e3aac943520bbd17a391348e6..0cc09b719cf385ed4978bb9e82700a5c1af4a6d3 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Block/UserLoginBlock.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Block/UserLoginBlock.php
@@ -10,6 +10,11 @@
 use Drupal\block\BlockBase;
 use Drupal\Component\Annotation\Plugin;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\user\Form\UserLoginForm;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Provides a 'User login' block.
@@ -20,7 +25,55 @@
  *   module = "user"
  * )
  */
-class UserLoginBlock extends BlockBase {
+class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The DI Container.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
+   * The request object.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * Constructs a new UserLoginBlock.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param array $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The DI Container.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, ContainerInterface $container, Request $request) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->container = $container;
+    $this->request = $request;
+  }
+
+  /**
+   * {@inheritdo}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container,
+      $container->get('request')
+    );
+  }
 
   /**
    * Overrides \Drupal\block\BlockBase::access().
@@ -33,7 +86,7 @@ public function access() {
    * {@inheritdoc}
    */
   public function build() {
-    $form = drupal_get_form('user_login_form');
+    $form = drupal_get_form(UserLoginForm::create($this->container), $this->request);
     unset($form['name']['#attributes']['autofocus']);
     unset($form['name']['#description']);
     unset($form['pass']['#description']);
diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php
index 7d22cd3820dfa6c8e2950a8e6218eefcd5dd7144..f9f6b676cea971faffa7c94437323a3110b8f4b8 100644
--- a/core/modules/user/lib/Drupal/user/RegisterFormController.php
+++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php
@@ -120,8 +120,7 @@ public function save(array $form, array &$form_state) {
     // No e-mail verification required; log in user immediately.
     elseif (!$admin && !config('user.settings')->get('verify_mail') && $account->status) {
       _user_mail_notify('register_no_approval_required', $account);
-      $form_state['uid'] = $account->uid;
-      user_login_form_submit(array(), $form_state);
+      user_login_finalize($account);
       drupal_set_message(t('Registration successful. You are now logged in.'));
       $form_state['redirect'] = '';
     }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index aecb7e7bb4bc7b279100614ed56e395b607e9e9b..74d3743c98a0333526e950e34a52e0aff7783b2f 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -850,16 +850,13 @@ function user_menu() {
   $items['user'] = array(
     'title' => 'User account',
     'title callback' => 'user_menu_title',
-    'page callback' => 'user_page',
-    'access callback' => TRUE,
-    'file' => 'user.pages.inc',
     'weight' => -10,
+    'route_name' => 'user_page',
     'menu_name' => 'account',
   );
 
   $items['user/login'] = array(
     'title' => 'Log in',
-    'access callback' => 'user_is_anonymous',
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
   // Other authentication methods may add pages below user/login/.
@@ -1150,44 +1147,6 @@ function user_page_title($account) {
   return is_object($account) ? user_format_name($account) : '';
 }
 
-/**
- * Form builder; the main user login form.
- *
- * @ingroup forms
- */
-function user_login_form($form, &$form_state) {
-  // Display login form:
-  $form['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Username'),
-    '#size' => 60,
-    '#maxlength' => USERNAME_MAX_LENGTH,
-    '#description' => t('Enter your @s username.', array('@s' => config('system.site')->get('name'))),
-    '#required' => TRUE,
-    '#attributes' => array(
-      'autocorrect' => 'off',
-      'autocapitalize' => 'off',
-      'spellcheck' => 'false',
-      'autofocus' => 'autofocus',
-    ),
-  );
-
-  $form['pass'] = array(
-    '#type' => 'password',
-    '#title' => t('Password'),
-    '#size' => 60,
-    '#description' => t('Enter the password that accompanies your username.'),
-    '#required' => TRUE,
-  );
-
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
-
-  $form['#validate'] = user_login_default_validators();
-
-  return $form;
-}
-
 /**
  * Set up a series for validators which check for blocked users,
  * then authenticate against local database, then return an error if
@@ -1210,109 +1169,6 @@ function user_login_default_validators() {
   return array('user_login_name_validate', 'user_login_authenticate_validate', 'user_login_final_validate');
 }
 
-/**
- * A FAPI validate handler. Sets an error if supplied username has been blocked.
- */
-function user_login_name_validate($form, &$form_state) {
-  if (!empty($form_state['values']['name']) && user_is_blocked($form_state['values']['name'])) {
-    // Blocked in user administration.
-    form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
-  }
-}
-
-/**
- * A validate handler on the login form. Check supplied username/password
- * against local users table. If successful, $form_state['uid']
- * is set to the matching user ID.
- */
-function user_login_authenticate_validate($form, &$form_state) {
-  $password = trim($form_state['values']['pass']);
-  $flood_config = config('user.flood');
-  $flood = Drupal::service('flood');
-  if (!empty($form_state['values']['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 (!$flood->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
-      $form_state['flood_control_triggered'] = 'ip';
-      return;
-    }
-    $account = db_query("SELECT * FROM {users} WHERE name = :name AND status = 1", array(':name' => $form_state['values']['name']))->fetchObject();
-    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->uid;
-      }
-      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->uid . '-' . Drupal::request()->getClientIP();
-      }
-      $form_state['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 (!$flood->isAllowed('user.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
-        $form_state['flood_control_triggered'] = 'user';
-        return;
-      }
-    }
-    // We are not limited by flood control, so try to authenticate.
-    // Set $form_state['uid'] as a flag for user_login_final_validate().
-    $form_state['uid'] = user_authenticate($form_state['values']['name'], $password);
-  }
-}
-
-/**
- * The final validation handler on the login form.
- *
- * Sets a form error if user has not been authenticated, or if too many
- * logins have been attempted. This validation function should always
- * be the last one.
- */
-function user_login_final_validate($form, &$form_state) {
-  $flood_config = config('user.flood');
-  $flood = Drupal::service('flood');
-  if (empty($form_state['uid'])) {
-    // Always register an IP-based failed login event.
-    $flood->register('user.failed_login_ip', $flood_config->get('ip_window'));
-    // Register a per-user failed login event.
-    if (isset($form_state['flood_control_user_identifier'])) {
-      $flood->register('user.failed_login_user', $flood_config->get('user_window'), $form_state['flood_control_user_identifier']);
-    }
-
-    if (isset($form_state['flood_control_triggered'])) {
-      if ($form_state['flood_control_triggered'] == 'user') {
-        form_set_error('name', format_plural($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' => url('user/password'))));
-      }
-      else {
-        // We did not find a uid, so the limit is IP-based.
-        form_set_error('name', t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
-      }
-    }
-    else {
-      form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password', array('query' => array('name' => $form_state['values']['name']))))));
-      if (user_load_by_name($form_state['values']['name'])) {
-        watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
-      }
-      else {
-        // If the username entered is not a valid user,
-        // only store the IP address.
-        watchdog('user', 'Login attempt failed from %ip.', array('%ip' => Drupal::request()->getClientIp()));
-      }
-    }
-  }
-  elseif (isset($form_state['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.
-    $flood->clear('user.failed_login_user', $form_state['flood_control_user_identifier']);
-  }
-}
-
 /**
  * Try to validate the user's login credentials locally.
  *
@@ -1345,18 +1201,22 @@ function user_authenticate($name, $password) {
 }
 
 /**
- * Finalize the login process. Must be called when logging in a user.
+ * Finalizes the login process and logs in a user.
  *
- * The function records a watchdog message about the new session, saves the
- * login timestamp, calls hook_user_login(), and generates a new session.
+ * The function logs in the user, records a watchdog message about the new
+ * session, saves the login timestamp, calls hook_user_login(), and generates a
+ * new session.
  *
- * @param array $edit
- *   The array of form values submitted by the user.
+ * The global $user object is replaced with the passed in account.
+ *
+ * @param \Drupal\Core\Session\AccountInterface $account
+ *   The account to log in.
  *
  * @see hook_user_login()
  */
-function user_login_finalize(&$edit = array()) {
+function user_login_finalize(AccountInterface $account) {
   global $user;
+  $user = $account;
   watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
   // Update the user table timestamp noting user has logged in.
   // This is also used to invalidate one-time login links.
@@ -1374,19 +1234,6 @@ function user_login_finalize(&$edit = array()) {
   module_invoke_all('user_login', $user);
 }
 
-/**
- * Submit handler for the login form. Load $user object and perform standard login
- * tasks. The user is then redirected to the My Account page. Setting the
- * destination in the query string overrides the redirect.
- */
-function user_login_form_submit($form, &$form_state) {
-  global $user;
-  $user = user_load($form_state['uid']);
-  $form_state['redirect'] = 'user/' . $user->uid;
-
-  user_login_finalize($form_state);
-}
-
 /**
  * Implements hook_user_login().
  */
@@ -2329,21 +2176,6 @@ function user_modules_uninstalled($modules) {
   drupal_container()->get('user.data')->delete($modules);
 }
 
-/**
- * Helper function to rewrite the destination to avoid redirecting to login page after login.
- *
- * Third-party authentication modules may use this function to determine the
- * proper destination after a user has been properly logged in.
- */
-function user_login_destination() {
-  $destination = drupal_get_destination();
-  // Modules may provide login pages under the "user/login/" path prefix.
-  if (preg_match('@^user/login(/.*|)$@', $destination['destination'])) {
-    $destination['destination'] = 'user';
-  }
-  return $destination;
-}
-
 /**
  * Saves visitor information as a cookie so it can be reused.
  *
diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc
index 5025c17f0ed192a53c386d44207c71c0aeaf418a..f5f164fa38eecbd57dd640e02f91c085592475a9 100644
--- a/core/modules/user/user.pages.inc
+++ b/core/modules/user/user.pages.inc
@@ -53,10 +53,9 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
         // First stage is a confirmation form, then login
         if ($action == 'login') {
           // Set the new user.
-          $user = $account;
           // user_login_finalize() also updates the login timestamp of the
           // user, which invalidates further use of the one-time login link.
-          user_login_finalize();
+          user_login_finalize($account);
           watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp));
           drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'));
           // Let the user's password be changed without the current password check.
@@ -324,19 +323,3 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') {
   }
   throw new AccessDeniedHttpException();
 }
-
-/**
- * Access callback for path /user.
- *
- * Displays user profile if user is logged in, or login form for anonymous
- * users.
- */
-function user_page() {
-  global $user;
-  if ($user->uid) {
-    return new RedirectResponse(url('user/' . $user->uid, array('absolute' => TRUE)));
-  }
-  else {
-    return drupal_get_form('user_login_form');
-  }
-}
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index 3d52545468fffb4cda693fa922f55503b23339eb..10ec95df87394cee2bc0b0cd767e30365c804d3d 100644
--- a/core/modules/user/user.routing.yml
+++ b/core/modules/user/user.routing.yml
@@ -74,3 +74,17 @@ user_pass:
     _form: '\Drupal\user\Form\UserPasswordForm'
   requirements:
     _access: 'TRUE'
+
+user_page:
+  pattern: '/user'
+  defaults:
+    _content: '\Drupal\user\Controller\UserController::userPage'
+  requirements:
+    _access: 'TRUE'
+
+user_login:
+  pattern: '/user/login'
+  defaults:
+    _form: '\Drupal\user\Form\UserLoginForm'
+  requirements:
+    _access: 'TRUE'