diff --git a/core/core.services.yml b/core/core.services.yml
index 65882af..bc51703 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -613,7 +613,6 @@ services:
     factory_method: authenticate
     factory_service: authentication
     arguments: ['@request']
-    scope: request
   asset.css.collection_renderer:
     class: Drupal\Core\Asset\CssCollectionRenderer
   asset.css.collection_optimizer:
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Access/SwitchShortcutSetAccessCheck.php b/core/modules/shortcut/lib/Drupal/shortcut/Access/SwitchShortcutSetAccessCheck.php
new file mode 100644
index 0000000..02dff71
--- /dev/null
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Access/SwitchShortcutSetAccessCheck.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\shortcut\Access\SwitchShortcutSetAccessCheck.
+ */
+
+namespace Drupal\shortcut\Access;
+
+use Drupal\Core\Access\AccessCheckInterface;
+use Drupal\Core\Access\StaticAccessCheckInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Defines a access checker for switching the shortcut set of a user account.
+ */
+class SwitchShortcutSetAccessCheck implements StaticAccessCheckInterface {
+
+  /**
+   * Contains the current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account;
+
+  /**
+   * Constructs a new SwitchShortcutSetAccessCheck.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The current user.
+   */
+  public function __construct($account) {
+    $this->account = $account;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesTo() {
+    return array('_shortcut_set_switch');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(Route $route, Request $request) {
+    $user = $request->attributes->get('user');
+
+    // Users with the 'switch shortcut sets' permission can switch their own
+    // shortcuts sets.
+    $access = $this->account->hasPermission('switch shortcut sets') && $user->id() == $this->account->id();
+
+    return $access ? static::ALLOW : static::DENY;
+  }
+
+}
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Form/SwitchShortcutSet.php b/core/modules/shortcut/lib/Drupal/shortcut/Form/SwitchShortcutSet.php
new file mode 100644
index 0000000..44eb1c5
--- /dev/null
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Form/SwitchShortcutSet.php
@@ -0,0 +1,231 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\shortcut\Form\SwitchShortcutSet.
+ */
+
+namespace Drupal\shortcut\Form;
+
+use Drupal\Core\Controller\ControllerInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormInterface;
+use Drupal\Component\Utility\String;
+use Drupal\Core\Routing\UrlGeneratorInterface;
+use Drupal\user\UserInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Builds the shortcut set switch form.
+ */
+class SwitchShortcutSet extends FormBase implements ContainerInjectionInterface, FormInterface {
+
+  /**
+   * The account the shortcut set is for.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $user;
+
+  /**
+   * The shortcut storage controller.
+   *
+   * @var \Drupal\shortcut\ShortcutSetStorageControllerInterface
+   */
+  protected $storageController;
+
+  /**
+   * The url generator.
+   *
+   * @var \Drupal\Core\Routing\UrlGeneratorInterface
+   */
+  protected $urlGenerator;
+
+  /**
+   * Constructs a SwitchShortcutSet object.
+   *
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
+   *   The url generator.
+   */
+  public function __construct(EntityManager $entity_manager, UrlGeneratorInterface $url_generator) {
+    $this->storageController = $entity_manager->getStorageController('shortcut_set');
+    $this->urlGenerator = $url_generator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager'),
+      $container->get('url_generator')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'shortcut_set_switch';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, UserInterface $user = NULL) {
+    $account = $this->getRequest()->attributes->get('_account');
+
+    $this->user = $user;
+
+    // Prepare the list of shortcut sets.
+    $options = array_map(function ($set) {
+      return String::checkPlain($set->label());
+    }, $this->storageController->loadMultiple());
+
+    $current_set = shortcut_current_displayed_set($this->user);
+
+    // Only administrators can add shortcut sets.
+    $add_access = $account->hasPermission('administer shortcuts');
+    if ($add_access) {
+      $options['new'] = $this->t('New set');
+    }
+
+    $account_is_user = $this->user->id() == $account->id();
+    if (count($options) > 1) {
+      $form['set'] = array(
+        '#type' => 'radios',
+        '#title' => $account_is_user ? $this->t('Choose a set of shortcuts to use') : $this->t('Choose a set of shortcuts for this user'),
+        '#options' => $options,
+        '#default_value' => $current_set->id(),
+      );
+
+      $form['label'] = array(
+        '#type' => 'textfield',
+        '#title' => $this->t('Label'),
+        '#title_display' => 'invisible',
+        '#description' => $this->t('The new set is created by copying items from your default shortcut set.'),
+        '#access' => $add_access,
+      );
+      $form['id'] = array(
+        '#type' => 'machine_name',
+        '#machine_name' => array(
+          'exists' => array($this, 'exists'),
+          'replace_pattern' => '[^a-z0-9-]+',
+          'replace' => '-',
+        ),
+        // This ID could be used for menu name.
+        '#maxlength' => 23,
+        '#states' => array(
+          'required' => array(
+            ':input[name="set"]' => array('value' => 'new'),
+          ),
+        ),
+        '#required' => FALSE,
+      );
+
+      if (!$account_is_user) {
+        $default_set = $this->storageController->getDefaultSet($this->user);
+        $form['new']['#description'] = $this->t('The new set is created by copying items from the %default set.', array('%default' => $default_set->label()));
+      }
+
+      $form['#attached'] = array(
+        'library' => array(array('shortcut', 'drupal.shortcut.admin')),
+      );
+
+      $form['actions'] = array('#type' => 'actions');
+      $form['actions']['submit'] = array(
+        '#type' => 'submit',
+        '#value' => $this->t('Change set'),
+      );
+    }
+    else {
+      // There is only 1 option, so output a message in the $form array.
+      $form['info'] = array(
+        '#markup' => '<p>' . $this->t('You are currently using the %set-name shortcut set.', array('%set-name' => $current_set->label())) . '</p>',
+      );
+    }
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+    if ($form_state['values']['set'] == 'new') {
+      // Check to prevent creating a shortcut set with an empty title.
+      if (trim($form_state['values']['label']) == '') {
+        form_set_error('new', $this->t('The new set label is required.'));
+      }
+      // Check to prevent a duplicate title.
+      if (shortcut_set_title_exists($form_state['values']['label'])) {
+        form_set_error('label', $this->t('The shortcut set %name already exists. Choose another name.', array('%name' => $form_state['values']['label'])));
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $account = $this->getRequest()->attributes->get('_account');
+
+    $account_is_user = $this->user->id() == $account->id();
+    if ($form_state['values']['set'] == 'new') {
+      // Save a new shortcut set with links copied from the user's default set.
+      $default_set = $this->storageController->getDefaultSet($this->user);
+      $set = $this->storageController->create(array(
+        'id' => $form_state['values']['id'],
+        'label' => $form_state['values']['label'],
+        'links' => $default_set->links,
+      ));
+      $set->save();
+      $replacements = array(
+        '%user' => $this->user->label(),
+        '%set_name' => $set->label(),
+        '@switch-url' => $this->urlGenerator->generateFromPath($this->request->attributes->get('_system_path')),
+      );
+      if ($account_is_user) {
+        // Only administrators can create new shortcut sets, so we know they have
+        // access to switch back.
+        drupal_set_message($this->t('You are now using the new %set_name shortcut set. You can edit it from this page or <a href="@switch-url">switch back to a different one.</a>', $replacements));
+      }
+      else {
+        drupal_set_message($this->t('%user is now using a new shortcut set called %set_name. You can edit it from this page.', $replacements));
+      }
+      $form_state['redirect'] = 'admin/config/user-interface/shortcut/manage/' . $set->id();
+    }
+    else {
+      // Switch to a different shortcut set.
+      $set = $this->storageController->load($form_state['values']['set']);
+      $replacements = array(
+        '%user' => $this->user->label(),
+        '%set_name' => $set->label(),
+      );
+      drupal_set_message($account_is_user ? $this->t('You are now using the %set_name shortcut set.', $replacements) : $this->t('%user is now using the %set_name shortcut set.', $replacements));
+    }
+
+    // Assign the shortcut set to the provided user account.
+    $this->storageController->assignUser($set, $this->user);
+  }
+
+  /**
+   * Determines if a shortcut set exists already.
+   *
+   * @param string $id
+   *   The set ID to check.
+   *
+   * @return bool
+   *   TRUE if the shortcut set exists, FALSE otherwise.
+   */
+  public function exists($id) {
+    $sets = $this->storageController->load($id);
+    return !empty($sets);
+  }
+
+}
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetStorageController.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetStorageController.php
index 2f17850..f28f086 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetStorageController.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetStorageController.php
@@ -7,8 +7,13 @@
 
 namespace Drupal\shortcut;
 
+use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\Config\Entity\ConfigStorageController;
-use Drupal\shortcut\ShortcutSetInterface;
+use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Defines a storage controller for shortcut entities.
@@ -16,6 +21,49 @@
 class ShortcutSetStorageController extends ConfigStorageController implements ShortcutSetStorageControllerInterface {
 
   /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a ShortcutStorageController object.
+   *
+   * @param string $entity_type
+   *   The entity type for which the instance is created.
+   * @param array $entity_info
+   *   An array of entity info for the entity type.
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   *   The config factory service.
+   * @param \Drupal\Core\Config\StorageInterface $config_storage
+   *   The config storage service.
+   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory
+   *   The entity query factory.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct($entity_type, array $entity_info, ConfigFactory $config_factory, StorageInterface $config_storage, QueryFactory $entity_query_factory, ModuleHandlerInterface $module_handler) {
+    parent::__construct($entity_type, $entity_info, $config_factory, $config_storage, $entity_query_factory);
+
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+    return new static(
+      $entity_type,
+      $entity_info,
+      $container->get('config.factory'),
+      $container->get('config.storage'),
+      $container->get('entity.query'),
+      $container->get('module_handler')
+    );
+  }
+
+  /**
    * Overrides \Drupal\config\ConfigStorageController::attachLoad().
    */
   protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
@@ -77,4 +125,24 @@ public function getAssignedToUser($account) {
   public function countAssignedUsers(ShortcutSetInterface $shortcut_set) {
     return db_query('SELECT COUNT(*) FROM {shortcut_set_users} WHERE set_name = :name', array(':name' => $shortcut_set->id()))->fetchField();
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefaultSet(AccountInterface $account) {
+    // Allow modules to return a default shortcut set name. Since we can only
+    // have one, we allow the last module which returns a valid result to take
+    // precedence. If no module returns a valid set, fall back on the site-wide
+    // default, which is the lowest-numbered shortcut set.
+    $suggestions = array_reverse($this->moduleHandler->invokeAll('shortcut_default_set', $account));
+    $suggestions[] = 'default';
+    foreach ($suggestions as $name) {
+      if ($shortcut_set = $this->load($name)) {
+        break;
+      }
+    }
+
+    return $shortcut_set;
+  }
+
 }
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetStorageControllerInterface.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetStorageControllerInterface.php
index b4e9d97..ab1e1eb 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetStorageControllerInterface.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetStorageControllerInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\shortcut;
 
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\shortcut\ShortcutSetInterface;
 
 /**
@@ -69,4 +70,16 @@ public function getAssignedToUser($account);
    *   The number of users who have this set assigned to them.
    */
   public function countAssignedUsers(ShortcutSetInterface $shortcut_set);
+
+  /**
+   * Gets the default shortcut set for a given user account.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user account whose default shortcut set will be returned.
+   *
+   * @return \Drupal\shortcut\ShortcutSetInterface
+   *   An object representing the default shortcut set.
+   */
+  public function getDefaultSet(AccountInterface $account);
+
 }
diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc
index ff31138..46717cc 100644
--- a/core/modules/shortcut/shortcut.admin.inc
+++ b/core/modules/shortcut/shortcut.admin.inc
@@ -9,169 +9,6 @@
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 
 /**
- * Form callback: builds the form for switching shortcut sets.
- *
- * @param $form
- *   An associative array containing the structure of the form.
- * @param $form_state
- *   An associative array containing the current state of the form.
- * @param $account
- *   (optional) The user account whose shortcuts will be switched. Defaults to
- *   the current logged-in user.
- *
- * @return
- *   An array representing the form definition.
- *
- * @ingroup forms
- * @see shortcut_set_switch_validate()
- * @see shortcut_set_switch_submit()
- */
-function shortcut_set_switch($form, &$form_state, $account = NULL) {
-  global $user;
-  if (!isset($account)) {
-    $account = $user;
-  }
-
-  // Prepare the list of shortcut sets.
-  $sets = entity_load_multiple('shortcut_set');
-  $current_set = shortcut_current_displayed_set($account);
-
-  $options = array();
-  foreach ($sets as $name => $set) {
-    $options[$name] = check_plain($set->label());
-  }
-
-  // Only administrators can add shortcut sets.
-  $add_access = user_access('administer shortcuts');
-  if ($add_access) {
-    $options['new'] = t('New set');
-  }
-
-  if (count($options) > 1) {
-    $form['account'] = array(
-      '#type' => 'value',
-      '#value' => $account,
-    );
-
-    $form['set'] = array(
-      '#type' => 'radios',
-      '#title' => $user->id() == $account->id() ? t('Choose a set of shortcuts to use') : t('Choose a set of shortcuts for this user'),
-      '#options' => $options,
-      '#default_value' => $current_set->id(),
-    );
-
-    $form['label'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Label'),
-      '#title_display' => 'invisible',
-      '#description' => t('The new set is created by copying items from your default shortcut set.'),
-      '#access' => $add_access,
-    );
-    $form['id'] = array(
-      '#type' => 'machine_name',
-      '#machine_name' => array(
-        'exists' => 'shortcut_set_load',
-        'source' => array('label'),
-        'replace_pattern' => '[^a-z0-9-]+',
-        'replace' => '-',
-      ),
-      // This id could be used for menu name.
-      '#maxlength' => 23,
-      '#states' => array(
-        'required' => array(
-          ':input[name="set"]' => array('value' => 'new'),
-        ),
-      ),
-      '#required' => FALSE,
-    );
-
-    if ($user->id() != $account->id()) {
-      $default_set = shortcut_default_set($account);
-      $form['new']['#description'] = t('The new set is created by copying items from the %default set.', array('%default' => $default_set->label()));
-    }
-
-    $form['#attached'] = array(
-      'library' => array(array('shortcut', 'drupal.shortcut.admin')),
-    );
-
-    $form['actions'] = array('#type' => 'actions');
-    $form['actions']['submit'] = array(
-      '#type' => 'submit',
-      '#value' => t('Change set'),
-    );
-  }
-  else {
-    // There is only 1 option, so output a message in the $form array.
-    $form['info'] = array(
-      '#markup' => '<p>' . t('You are currently using the %set-name shortcut set.', array('%set-name' => $current_set->label())) . '</p>',
-    );
-  }
-
-  return $form;
-}
-
-/**
- * Validation handler for shortcut_set_switch().
- */
-function shortcut_set_switch_validate($form, &$form_state) {
-  if ($form_state['values']['set'] == 'new') {
-    // Check to prevent creating a shortcut set with an empty title.
-    if (trim($form_state['values']['label']) == '') {
-      form_set_error('new', t('The new set label is required.'));
-    }
-    // Check to prevent a duplicate title.
-    if (shortcut_set_title_exists($form_state['values']['label'])) {
-      form_set_error('label', t('The shortcut set %name already exists. Choose another name.', array('%name' => $form_state['values']['label'])));
-    }
-  }
-}
-
-/**
- * Submit handler for shortcut_set_switch().
- */
-function shortcut_set_switch_submit($form, &$form_state) {
-  global $user;
-  $account = $form_state['values']['account'];
-
-  if ($form_state['values']['set'] == 'new') {
-    // Save a new shortcut set with links copied from the user's default set.
-    $default_set = shortcut_default_set($account);
-    $set = entity_create('shortcut_set', array(
-      'id' => $form_state['values']['id'],
-      'label' => $form_state['values']['label'],
-      'links' => $default_set->links,
-    ));
-    $set->save();
-    $replacements = array(
-      '%user' => $account->getUsername(),
-      '%set_name' => $set->label(),
-      '@switch-url' => url(current_path()),
-    );
-    if ($account->id() == $user->id()) {
-      // Only administrators can create new shortcut sets, so we know they have
-      // access to switch back.
-      drupal_set_message(t('You are now using the new %set_name shortcut set. You can edit it from this page or <a href="@switch-url">switch back to a different one.</a>', $replacements));
-    }
-    else {
-      drupal_set_message(t('%user is now using a new shortcut set called %set_name. You can edit it from this page.', $replacements));
-    }
-    $form_state['redirect'] = 'admin/config/user-interface/shortcut/manage/' . $set->id();
-  }
-  else {
-    // Switch to a different shortcut set.
-    $set = shortcut_set_load($form_state['values']['set']);
-    $replacements = array(
-      '%user' => $account->getUsername(),
-      '%set_name' => $set->label(),
-    );
-    drupal_set_message($account->id() == $user->id() ? t('You are now using the %set_name shortcut set.', $replacements) : t('%user is now using the %set_name shortcut set.', $replacements));
-  }
-
-  // Assign the shortcut set to the provided user account.
-  shortcut_set_assign_user($set, $account);
-}
-
-/**
  * Form callback: builds the form for adding a new shortcut link.
  *
  * @param $form
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index 87414f0..70bd97f 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -138,12 +138,8 @@ function shortcut_menu() {
   );
   $items['user/%user/shortcuts'] = array(
     'title' => 'Shortcuts',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('shortcut_set_switch', 1),
-    'access callback' => 'shortcut_set_switch_access',
-    'access arguments' => array(1),
+    'route_name' => 'shortcut_set_switch',
     'type' => MENU_LOCAL_TASK,
-    'file' => 'shortcut.admin.inc',
   );
 
   return $items;
diff --git a/core/modules/shortcut/shortcut.routing.yml b/core/modules/shortcut/shortcut.routing.yml
index faf495a..dd70653 100644
--- a/core/modules/shortcut/shortcut.routing.yml
+++ b/core/modules/shortcut/shortcut.routing.yml
@@ -33,6 +33,14 @@ shortcut_set_edit:
   requirements:
     _entity_access: 'shortcut_set.update'
 
+shortcut_set_switch:
+  pattern: '/user/{user}/shortcuts'
+  defaults:
+    _form: 'Drupal\shortcut\Form\SwitchShortcutSet'
+  requirements:
+    _permission: 'administer shortcuts'
+    _shortcut_set_switch: 'TRUE'
+
 shortcut_link_add_inline:
   pattern: '/admin/config/user-interface/shortcut/manage/{shortcut_set}/add-link-inline'
   defaults:
diff --git a/core/modules/shortcut/shortcut.services.yml b/core/modules/shortcut/shortcut.services.yml
index bb95f49..94ed2c1 100644
--- a/core/modules/shortcut/shortcut.services.yml
+++ b/core/modules/shortcut/shortcut.services.yml
@@ -3,3 +3,8 @@ services:
     class: Drupal\shortcut\Access\LinkDeleteAccessCheck
     tags:
       - { name: access_check }
+  access_check.shortcut.switch:
+    class: Drupal\shortcut\Access\SwitchShortcutSetAccessCheck
+    arguments: ['@current_user']
+    tags:
+      - { name: access_check }
