diff --git a/core/authorize.php b/core/authorize.php
index 0d61004466c5b971b288097e7fdfdc227cb42d27..35277f6b379822c21e9ddaa767a2b21607c5d7a4 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -47,10 +47,17 @@
  * The killswitch in settings.php overrides all else, otherwise, the user must
  * have access to the 'administer software updates' permission.
  *
+ * @param \Symfony\Component\HttpFoundation\Request $request
+ *  The incoming request.
+ *
  * @return bool
  *   TRUE if the current user can run authorize.php, and FALSE if not.
  */
-function authorize_access_allowed() {
+function authorize_access_allowed(Request $request) {
+  $account = \Drupal::service('authentication')->authenticate($request);
+  if ($account) {
+    \Drupal::currentUser()->setAccount($account);
+  }
   return Settings::get('allow_authorize_operations', TRUE) && \Drupal::currentUser()->hasPermission('administer software updates');
 }
 
@@ -79,7 +86,7 @@ function authorize_access_allowed() {
 $show_messages = TRUE;
 
 $response = new Response();
-if (authorize_access_allowed()) {
+if (authorize_access_allowed($request)) {
   // Load both the Form API and Batch API.
   require_once __DIR__ . '/includes/form.inc';
   require_once __DIR__ . '/includes/batch.inc';
diff --git a/core/core.services.yml b/core/core.services.yml
index 6681e7daa7108c53d929c0ca322ed8787ce7d5f1..4e42b3ee8784dfca2ce2927e18b5f81583901443 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -733,11 +733,6 @@ services:
     tags:
       - { name: route_enhancer }
       - { name: event_subscriber }
-  route_enhancer.authentication:
-    class: Drupal\Core\Routing\Enhancer\AuthenticationEnhancer
-    tags:
-      - { name: route_enhancer, priority: 1000 }
-    arguments: ['@authentication', '@current_user']
   route_enhancer.entity:
     class: Drupal\Core\Entity\Enhancer\EntityRouteEnhancer
     tags:
@@ -1110,15 +1105,14 @@ services:
       - { name: service_collector, tag: authentication_provider, call: addProvider }
   authentication_subscriber:
     class: Drupal\Core\EventSubscriber\AuthenticationSubscriber
+    arguments: ['@authentication', '@current_user']
     tags:
       - { name: event_subscriber }
-    arguments: ['@authentication']
   account_switcher:
     class: Drupal\Core\Session\AccountSwitcher
     arguments: ['@current_user', '@session_handler.write_safe']
   current_user:
     class: Drupal\Core\Session\AccountProxy
-    arguments: ['@authentication', '@request_stack']
   session_configuration:
     class: Drupal\Core\Session\SessionConfiguration
     arguments: ['%session.storage.options%']
diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php
index 3bc40d8f7efbc3b07b291bbe4485146440b86ea7..288603079741a5d7a3eff49f6558e66b8d227d44 100644
--- a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php
+++ b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php
@@ -8,28 +8,24 @@
 namespace Drupal\Core\Authentication;
 
 use Drupal\Core\Routing\RouteMatch;
-use Drupal\Core\Session\AnonymousUserSession;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
 
 /**
  * Manager for authentication.
  *
  * On each request, let all authentication providers try to authenticate the
  * user. The providers are iterated according to their priority and the first
- * provider detecting credentials for its method will become the triggered
- * provider. No further provider will get triggered.
+ * provider detecting credentials for its method wins. No further provider will
+ * get triggered.
  *
- * If no provider was triggered the lowest-priority provider is assumed to
- * be responsible. If no provider set an active user then the user is set to
- * anonymous.
+ * If no provider set an active user then the user is set to anonymous.
  */
-class AuthenticationManager implements AuthenticationProviderInterface, AuthenticationManagerInterface {
+class AuthenticationManager implements AuthenticationProviderInterface, AuthenticationProviderFilterInterface, AuthenticationProviderChallengeInterface {
 
   /**
    * Array of all registered authentication providers, keyed by ID.
    *
-   * @var array
+   * @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
    */
   protected $providers;
 
@@ -43,16 +39,45 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti
   /**
    * Sorted list of registered providers.
    *
-   * @var array
+   * @var \Drupal\Core\Authentication\AuthenticationProviderInterface[]
    */
   protected $sortedProviders;
 
   /**
-   * Id of the provider that authenticated the user.
+   * List of providers which implement the filter interface.
+   *
+   * @var \Drupal\Core\Authentication\AuthenticationProviderFilterInterface[]
+   */
+  protected $filters;
+
+  /**
+   * List of providers which implement the challenge interface.
+   *
+   * @var \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface[]
+   */
+  protected $challengers;
+
+  /**
+   * List of providers which are allowed on routes with no _auth option.
    *
-   * @var string
+   * @var string[]
    */
-  protected $triggeredProviderId = '';
+  protected $globalProviders;
+
+  /**
+   * Constructs an authentication manager.
+   *
+   * @todo Revisit service construction. Especially write a custom compiler pass
+   *   which is capable of collecting, sorting and injecting all providers
+   *   (including global/vs non global), filters and challengers on compile
+   *   time in https://www.drupal.org/node/2432585.
+   *
+   * @param array $global_providers
+   *   List of global providers, keyed by the provier ID.
+   */
+  public function __construct($global_providers = ['cookie' => TRUE]) {
+    $this->globalProviders = $global_providers;
+  }
 
   /**
    * Adds a provider to the array of registered providers.
@@ -72,139 +97,176 @@ public function addProvider(AuthenticationProviderInterface $provider, $id, $pri
     $this->providerOrders[$priority][$id] = $provider;
     // Force the builders to be re-sorted.
     $this->sortedProviders = NULL;
+
+    if ($provider instanceof AuthenticationProviderFilterInterface) {
+      $this->filters[$id] = $provider;
+    }
+    if ($provider instanceof AuthenticationProviderChallengeInterface) {
+      $this->challengers[$id] = $provider;
+    }
   }
 
   /**
    * {@inheritdoc}
    */
   public function applies(Request $request) {
-    return TRUE;
+    return (bool) $this->getProvider($request);
   }
 
   /**
    * {@inheritdoc}
    */
   public function authenticate(Request $request) {
-    $account = NULL;
+    $provider_id = $this->getProvider($request);
+    return $this->providers[$provider_id]->authenticate($request);
+  }
 
-    // Iterate the allowed providers.
-    foreach ($this->filterProviders($this->getSortedProviders(), $request) as $provider_id => $provider) {
-      if ($provider->applies($request)) {
-        // Try to authenticate with this provider, skipping all others.
-        $account = $provider->authenticate($request);
-        $this->triggeredProviderId = $provider_id;
-        break;
-      }
-    }
+  /**
+   * {@inheritdoc}
+   */
+  public function appliesToRoutedRequest(Request $request, $authenticated) {
+    $result = FALSE;
 
-    // No provider returned a valid account, so set the user to anonymous.
-    if (!$account) {
-      $account = new AnonymousUserSession();
+    if ($authenticated) {
+      $result = $this->applyFilter($request, $authenticated, $this->getProvider($request));
     }
-
-    // No provider was fired, so assume the one with the least priority
-    // should have.
-    if (!$this->triggeredProviderId) {
-      $this->triggeredProviderId = $this->defaultProviderId();
+    else {
+      foreach ($this->getSortedProviders() as $provider_id => $provider) {
+        if ($this->applyFilter($request, $authenticated, $provider_id)) {
+          $result = TRUE;
+          break;
+        }
+      }
     }
 
-    // Save the authenticated account and the provider that supplied it
-    //  for later access.
-    $request->attributes->set('_authentication_provider', $this->triggeredProviderId);
+    return $result;
+  }
 
-    return $account;
+  /**
+   * {@inheritdoc}
+   */
+  public function challengeException(Request $request, \Exception $previous) {
+    $provider_id = $this->getChallenger($request);
+    if ($provider_id) {
+      return $this->challengers[$provider_id]->challengeException($request, $previous);
+    }
   }
 
   /**
-   * Returns the default provider ID.
+   * Returns the id of the authentication provider for a request.
    *
-   * The default provider is the one with the lowest registered priority.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The incoming request.
    *
-   * @return string
-   *   The ID of the default provider.
+   * @return string|NULL
+   *   The id of the first authentication provider which applies to the request.
+   *   If no application detects appropriate credentials, then NULL is returned.
    */
-  public function defaultProviderId() {
-    $providers = $this->getSortedProviders();
-    $provider_ids = array_keys($providers);
-    return end($provider_ids);
+  protected function getProvider(Request $request) {
+    foreach ($this->getSortedProviders() as $provider_id => $provider) {
+      if ($provider->applies($request)) {
+        return $provider_id;
+      }
+    }
   }
 
   /**
-   * Returns the sorted array of authentication providers.
+   * Returns the id of the challenge provider for a request.
    *
-   * @return array
-   *   An array of authentication provider objects.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The incoming request.
+   *
+   * @return string|NULL
+   *   The id of the first authentication provider which applies to the request.
+   *   If no application detects appropriate credentials, then NULL is returned.
    */
-  public function getSortedProviders() {
-    if (!isset($this->sortedProviders)) {
-      // Sort the builders according to priority.
-      krsort($this->providerOrders);
-      // Merge nested providers from $this->providers into $this->sortedProviders.
-      $this->sortedProviders = array();
-      foreach ($this->providerOrders as $providers) {
-        $this->sortedProviders = array_merge($this->sortedProviders, $providers);
+  protected function getChallenger(Request $request) {
+    if (!empty($this->challengers)) {
+      foreach ($this->getSortedProviders($request, FALSE) as $provider_id => $provider) {
+        if (isset($this->challengers[$provider_id]) && !$provider->applies($request) && $this->applyFilter($request, FALSE, $provider_id)) {
+          return $provider_id;
+        }
       }
     }
-    return $this->sortedProviders;
   }
 
   /**
-   * Filters a list of providers and only return those allowed on the request.
+   * Checks whether a provider is allowed on the given request.
    *
-   * @param \Drupal\Core\Authentication\AuthenticationProviderInterface[] $providers
-   *   An array of authentication provider objects.
-   * @param Request $request
-   *   The request object.
+   * If no filter is registered for the given provider id, the default filter
+   * is applied.
    *
-   * @return \Drupal\Core\Authentication\AuthenticationProviderInterface[]
-   *   The filtered array authentication provider objects.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The incoming request.
+   * @param bool $authenticated
+   *   Whether or not the request is authenticated.
+   * @param string $provider_id
+   *   The id of the authentication provider to check access for.
+   *
+   * @return bool
+   *   TRUE if provider is allowed, FALSE otherwise.
    */
-  protected function filterProviders(array $providers, Request $request) {
-    $route = RouteMatch::createFromRequest($request)->getRouteObject();
-    $allowed_providers = array();
-    if ($route && $route->hasOption('_auth')) {
-      $allowed_providers = $route->getOption('_auth');
+  protected function applyFilter(Request $request, $authenticated, $provider_id) {
+    if (isset($this->filters[$provider_id])) {
+      $result = $this->filters[$provider_id]->appliesToRoutedRequest($request, $authenticated);
     }
-    elseif ($default_provider = $this->defaultProviderId()) {
-      // @todo Mirrors the defective behavior of AuthenticationEnhancer and
-      // restricts the list of allowed providers to the default provider if no
-      // _auth was specified on the current route.
-      //
-      // This restriction will be removed by https://www.drupal.org/node/2286971
-      // See also https://www.drupal.org/node/2283637
-      $allowed_providers = array($default_provider);
+    else {
+      $result = $this->defaultFilter($request, $provider_id);
     }
 
-    return array_intersect_key($providers, array_flip($allowed_providers));
+    return $result;
   }
 
   /**
-   * Cleans up the authentication.
+   * Default implementation of the provider filter.
    *
-   * Allow the triggered provider to clean up before the response is sent, e.g.
-   * trigger a session commit.
+   * Checks whether a provider is allowed as per the _auth option on a route. If
+   * the option is not set or if the request did not match any route, only
+   * providers from the global provider set are allowed.
+   *
+   * If no filter is registered for the given provider id, the default filter
+   * is applied.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The request object.
+   *   The incoming request.
+   * @param string $provider_id
+   *   The id of the authentication provider to check access for.
    *
-   * @see \Drupal\Core\Authentication\Provider\Cookie::cleanup()
+   * @return bool
+   *   TRUE if provider is allowed, FALSE otherwise.
    */
-  public function cleanup(Request $request) {
-    if (empty($this->providers[$this->triggeredProviderId])) {
-      return;
+  protected function defaultFilter(Request $request, $provider_id) {
+    $route = RouteMatch::createFromRequest($request)->getRouteObject();
+    $has_auth_option = isset($route) && $route->hasOption('_auth');
+
+    if ($has_auth_option) {
+      return in_array($provider_id, $route->getOption('_auth'));
+    }
+    else {
+      return isset($this->globalProviders[$provider_id]);
     }
-    $this->providers[$this->triggeredProviderId]->cleanup($request);
   }
 
   /**
-   * {@inheritdoc}
+   * Returns the sorted array of authentication providers.
+   *
+   * @todo Replace with a list of providers sorted during compile time in
+   *   https://www.drupal.org/node/2432585.
+   *
+   * @return \Drupal\Core\Authentication\AuthenticationProviderInterface[]
+   *   An array of authentication provider objects.
    */
-  public function handleException(GetResponseForExceptionEvent $event) {
-    foreach ($this->filterProviders($this->getSortedProviders(), $event->getRequest()) as $provider) {
-      if ($provider->handleException($event) === TRUE) {
-        break;
+  protected function getSortedProviders() {
+    if (!isset($this->sortedProviders)) {
+      // Sort the builders according to priority.
+      krsort($this->providerOrders);
+      // Merge nested providers from $this->providers into $this->sortedProviders.
+      $this->sortedProviders = array();
+      foreach ($this->providerOrders as $providers) {
+        $this->sortedProviders = array_merge($this->sortedProviders, $providers);
       }
     }
+    return $this->sortedProviders;
   }
 
 }
diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationManagerInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationManagerInterface.php
deleted file mode 100644
index 0b4c0ff331f6c4df2113f96de37d04553d4257af..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/Authentication/AuthenticationManagerInterface.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains Drupal\Core\Authentication\AuthenticationManagerInterface.
- */
-
-namespace Drupal\Core\Authentication;
-
-/**
- * Defines an interface for authentication managers.
- */
-interface AuthenticationManagerInterface extends AuthenticationProviderInterface {
-
-  /**
-   * Returns the service id of the default authentication provider.
-   *
-   * @return string
-   *   The service id of the default authentication provider.
-   */
-  public function defaultProviderId();
-
-}
diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..506a4ae09e007f7fefd496e8b7b5de00def92061
--- /dev/null
+++ b/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface
+ */
+
+namespace Drupal\Core\Authentication;
+
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Generate a challenge when access is denied for unauthenticated users.
+ *
+ * On a 403 (access denied), if there are no credentials on the request, some
+ * authentication methods (e.g. basic auth) require that a challenge is sent to
+ * the client.
+ */
+interface AuthenticationProviderChallengeInterface {
+
+  /**
+   * Constructs an exception which is used to generate the challenge.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   *   The request.
+   * @var \Exception $exception
+   *   The previous exception.
+   *
+   * @return \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface|NULL
+   *   An exception to be used in order to generate an authentication challenge.
+   */
+  public function challengeException(Request $request, \Exception $previous);
+
+}
diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationProviderFilterInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationProviderFilterInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..604a02b81c4a81b8a45968ca409fcdf4315fbc7e
--- /dev/null
+++ b/core/lib/Drupal/Core/Authentication/AuthenticationProviderFilterInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Authentication\AuthenticationProviderFilterInterface
+ */
+
+namespace Drupal\Core\Authentication;
+
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Restrict authentication methods to a subset of the site.
+ *
+ * Some authentication methods should not be available throughout a whole site.
+ * E.g., there are good reasons to restrict insecure methods like HTTP basic
+ * auth or an URL token authentication method to API-only routes.
+ */
+interface AuthenticationProviderFilterInterface {
+
+  /**
+   * Checks whether the authentication method is allowed on a given route.
+   *
+   * While authentication itself is run before routing, this method is called
+   * after routing, hence RouteMatch is available and can be used to inspect
+   * route options.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request.
+   * @param bool $authenticated
+   *   Whether or not the request is authenticated.
+   *
+   * @return bool
+   *   TRUE if an authentication method is allowed on the request, otherwise
+   *   FALSE.
+   */
+  public function appliesToRoutedRequest(Request $request, $authenticated);
+
+}
diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php
index bc8e10c11ad0f62a30ab3d4c2fe390d0210f06ba..38536e1876a413c0ff5d3d584f69970da985fe29 100644
--- a/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php
+++ b/core/lib/Drupal/Core/Authentication/AuthenticationProviderInterface.php
@@ -8,7 +8,6 @@
 namespace Drupal\Core\Authentication;
 
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
 
 /**
  * Interface for authentication providers.
@@ -16,52 +15,27 @@
 interface AuthenticationProviderInterface {
 
   /**
-   * Declares whether the provider applies to a specific request or not.
+   * Checks whether suitable authentication credentials are on the request.
    *
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   The request object.
    *
    * @return bool
-   *   TRUE if the provider applies to the passed request, FALSE otherwise.
+   *   TRUE if authentication credentials suitable for this provider are on the
+   *   request, FALSE otherwise.
    */
   public function applies(Request $request);
 
   /**
    * Authenticates the user.
    *
-   * @param \Symfony\Component\HttpFoundation\Request|null $request
+   * @param \Symfony\Component\HttpFoundation\Request|NULL $request
    *   The request object.
    *
-   * @return \Drupal\Core\Session\AccountInterface|null
+   * @return \Drupal\Core\Session\AccountInterface|NULL
    *   AccountInterface - in case of a successful authentication.
    *   NULL - in case where authentication failed.
    */
   public function authenticate(Request $request);
 
-  /**
-   * Performs cleanup tasks at the end of a request.
-   *
-   * Allow the authentication provider to clean up before the response is sent.
-   * This is uses for instance in \Drupal\Core\Authentication\Provider\Cookie to
-   * ensure the session gets committed.
-   *
-   * @param Request $request
-   *   The request object.
-   */
-  public function cleanup(Request $request);
-
-  /**
-   * Handles an exception.
-   *
-   * In case exception has happened we allow authentication providers react.
-   * Used in \Drupal\Core\Authentication\Provider\BasicAuth to set up headers to
-   * prompt login.
-   *
-   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
-   *
-   * @return bool
-   *   TRUE - exception handled. No need to run through other providers.
-   *   FALSE - no actions have been done. Run through other providers.
-   */
-  public function handleException(GetResponseForExceptionEvent $event);
 }
diff --git a/core/lib/Drupal/Core/Authentication/Provider/Cookie.php b/core/lib/Drupal/Core/Authentication/Provider/Cookie.php
index e87d9201a915eed76f4ee613d039424f00043aad..7db5d9b3f2b36306ab2cb7ae5440d8278b2fc3a4 100644
--- a/core/lib/Drupal/Core/Authentication/Provider/Cookie.php
+++ b/core/lib/Drupal/Core/Authentication/Provider/Cookie.php
@@ -8,20 +8,36 @@
 namespace Drupal\Core\Authentication\Provider;
 
 use Drupal\Core\Authentication\AuthenticationProviderInterface;
-use Drupal\Core\Session\SessionManagerInterface;
+use Drupal\Core\Session\SessionConfigurationInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
 
 /**
  * Cookie based authentication provider.
  */
 class Cookie implements AuthenticationProviderInterface {
 
+  /**
+   * The session configuration.
+   *
+   * @var \Drupal\Core\Session\SessionConfigurationInterface
+   */
+  protected $sessionConfiguration;
+
+  /**
+   * Constructs a new cookie authentication provider.
+   *
+   * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
+   *   The session configuration.
+   */
+  public function __construct(SessionConfigurationInterface $session_configuration) {
+    $this->sessionConfiguration = $session_configuration;
+  }
+
   /**
    * {@inheritdoc}
    */
   public function applies(Request $request) {
-    return $request->hasSession();
+    return $request->hasSession() && $this->sessionConfiguration->hasSession($request);
   }
 
   /**
@@ -29,7 +45,7 @@ public function applies(Request $request) {
    */
   public function authenticate(Request $request) {
     if ($request->getSession()->start()) {
-      // @todo Remove global in https://www.drupal.org/node/2286971
+      // @todo Remove global in https://www.drupal.org/node/2228393
       global $_session_user;
       return $_session_user;
     }
@@ -37,16 +53,4 @@ public function authenticate(Request $request) {
     return NULL;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function cleanup(Request $request) {
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function handleException(GetResponseForExceptionEvent $event) {
-    return FALSE;
-  }
 }
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index eb841b29dda7f51ea74328a566d92a051358ed6f..be9821d9cacf7da838edd9b7fee91ded2c8dbf75 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -685,6 +685,11 @@ protected function initializeContainer($rebuild = FALSE) {
     $this->containerNeedsDumping = FALSE;
     $session_manager_started = FALSE;
     if (isset($this->container)) {
+      // Save the id of the currently logged in user.
+      if ($this->container->initialized('current_user')) {
+        $current_user_id = $this->container->get('current_user')->id();
+      }
+
       // If there is a session manager, close and save the session.
       if ($this->container->initialized('session_manager')) {
         $session_manager = $this->container->get('session_manager');
@@ -731,6 +736,11 @@ protected function initializeContainer($rebuild = FALSE) {
         }
       }
     }
+
+    if (!empty($current_user_id)) {
+      $this->container->get('current_user')->setInitialAccountId($current_user_id);
+    }
+
     \Drupal::setContainer($this->container);
 
     // If needs dumping flag was set, dump the container.
diff --git a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
index 80f77962f67f6f0921a93629090fbe471ea79186..6fd0b9997c4ec39fc21cdb9875cf7a6f0b18b519 100644
--- a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
@@ -7,71 +7,139 @@
 
 namespace Drupal\Core\EventSubscriber;
 
+use Drupal\Core\Authentication\AuthenticationProviderFilterInterface;
+use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
 use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 use Symfony\Component\HttpKernel\KernelEvents;
-use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
-use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
-use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
  * Authentication subscriber.
  *
- * Trigger authentication and cleanup during the request.
+ * Trigger authentication during the request.
  */
 class AuthenticationSubscriber implements EventSubscriberInterface {
 
   /**
    * Authentication provider.
    *
-   * @var AuthenticationProviderInterface
+   * @var \Drupal\Core\Authentication\AuthenticationProviderInterface
    */
   protected $authenticationProvider;
 
   /**
-   * Keep authentication manager as private variable.
+   * Authentication provider filter.
+   *
+   * @var \Drupal\Core\Authentication\AuthenticationProviderFilterInterface|NULL
+   */
+  protected $filter;
+
+  /**
+   * Authentication challenge provider.
    *
-   * @param AuthenticationProviderInterface $authentication_manager
-   *   The authentication manager.
+   * @var \Drupal\Core\Authentication\AuthenticationProviderChallengeInterface|NULL
    */
-  public function __construct(AuthenticationProviderInterface $authentication_provider) {
+  protected $challengeProvider;
+
+  /**
+   * Account proxy.
+   *
+   * @var \Drupal\Core\Session\AccountProxyInterface
+   */
+  protected $accountProxy;
+
+  /**
+   * Constructs an authentication subscriber.
+   *
+   * @param \Drupal\Core\Authentication\AuthenticationProviderInterface $authentication_provider
+   *   An authentication provider.
+   * @param \Drupal\Core\Session\AccountProxyInterface $account_proxy
+   *   Account proxy.
+   */
+  public function __construct(AuthenticationProviderInterface $authentication_provider, AccountProxyInterface $account_proxy) {
     $this->authenticationProvider = $authentication_provider;
+    $this->filter = ($authentication_provider instanceof AuthenticationProviderFilterInterface) ? $authentication_provider : NULL;
+    $this->challengeProvider = ($authentication_provider instanceof AuthenticationProviderChallengeInterface) ? $authentication_provider : NULL;
+    $this->accountProxy = $account_proxy;
   }
 
   /**
-   * Triggers authentication clean up on response.
+   * Authenticates user on request.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   The request event.
    *
-   * @see \Drupal\Core\Authentication\AuthenticationProviderInterface::cleanup()
+   * @see \Drupal\Core\Authentication\AuthenticationProviderInterface::authenticate()
    */
-  public function onRespond(FilterResponseEvent $event) {
-    if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
+  public function onKernelRequestAuthenticate(GetResponseEvent $event) {
+    if ($event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
       $request = $event->getRequest();
-      $this->authenticationProvider->cleanup($request);
+      if ($this->authenticationProvider->applies($request)) {
+        $account = $this->authenticationProvider->authenticate($request);
+        if ($account) {
+          $this->accountProxy->setAccount($account);
+        }
+      }
     }
   }
 
   /**
-   * Pass exception handling to authentication manager.
+   * Denies access if authentication provider is not allowed on this route.
    *
-   * @param GetResponseForExceptionEvent $event
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   The request event.
    */
-  public function onException(GetResponseForExceptionEvent $event) {
-    if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
-      $this->authenticationProvider->handleException($event);
+  public function onKernelRequestFilterProvider(GetResponseEvent $event) {
+    if (isset($this->filter) && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
+      $request = $event->getRequest();
+      if ($this->authenticationProvider->applies($request) && !$this->filter->appliesToRoutedRequest($request, TRUE)) {
+        throw new AccessDeniedHttpException();
+      }
     }
   }
 
   /**
-   * {@inheritdoc}
+   * Respond with a challenge on access denied exceptions if appropriate.
    *
-   * The priority for request must be higher than the highest event subscriber
-   * accessing the current user.
-   * The priority for the response must be as low as possible allowing e.g the
-   * Cookie provider to send all relevant session data to the user.
+   * On a 403 (access denied), if there are no credentials on the request, some
+   * authentication methods (e.g. basic auth) require that a challenge is sent
+   * to the client.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+   *   The exception event.
+   */
+  public function onExceptionSendChallenge(GetResponseForExceptionEvent $event) {
+    if (isset($this->challengeProvider) && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
+      $request = $event->getRequest();
+      $exception = $event->getException();
+      if ($exception instanceof AccessDeniedHttpException && !$this->authenticationProvider->applies($request) && (!isset($this->filter) || $this->filter->appliesToRoutedRequest($request, FALSE))) {
+        $challenge_exception = $this->challengeProvider->challengeException($request, $exception);
+        if ($challenge_exception) {
+          $event->setException($challenge_exception);
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
-    $events[KernelEvents::RESPONSE][] = ['onRespond', 0];
-    $events[KernelEvents::EXCEPTION][] = ['onException', 75];
+    // The priority for authentication must be higher than the highest event
+    // subscriber accessing the current user. Especially it must be higher than
+    // LanguageRequestSubscriber as LanguageManager accesses the current user if
+    // the language module is enabled.
+    $events[KernelEvents::REQUEST][] = ['onKernelRequestAuthenticate', 300];
+
+    // Access check must be performed after routing.
+    $events[KernelEvents::REQUEST][] = ['onKernelRequestFilterProvider', 31];
+    $events[KernelEvents::EXCEPTION][] = ['onExceptionSendChallenge', 75];
     return $events;
   }
+
 }
diff --git a/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php
index e2e7137df5d4d5432b09f8c69829420d0d7d651c..d096531b75e911adca4a32309971093a7d17fc2d 100644
--- a/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/SpecialAttributesRouteSubscriber.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Core\EventSubscriber;
 
-use Drupal\Component\Utility\String;
 use Drupal\Core\Routing\RouteBuildEvent;
 use Drupal\Core\Routing\RouteSubscriberBase;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -25,7 +24,6 @@ protected function alterRoutes(RouteCollection $collection) {
     $special_variables = array(
       'system_path',
       '_legacy',
-      '_authentication_provider',
       '_raw_variables',
       RouteObjectInterface::ROUTE_OBJECT,
       RouteObjectInterface::ROUTE_NAME,
diff --git a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php
index 93a31c31b7796ff6e15071f0dec73d7148cd7f48..1cf937c4f3ca0fc3e746fd9b29624974ffdff381 100644
--- a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php
+++ b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php
@@ -88,10 +88,6 @@ public function getContext() {
   public function matchRequest(Request $request) {
     $parameters = $this->chainRouter->matchRequest($request);
     $request->attributes->add($parameters);
-    // Trigger a session start and authentication by accessing any property of
-    // the current user.
-    // @todo This will be removed in https://www.drupal.org/node/2229145.
-    $this->account->id();
     $this->checkAccess($request);
     // We can not return $parameters because the access check can change the
     // request attributes.
diff --git a/core/lib/Drupal/Core/Routing/Enhancer/AuthenticationEnhancer.php b/core/lib/Drupal/Core/Routing/Enhancer/AuthenticationEnhancer.php
deleted file mode 100644
index b1639ec074111fdad7603f3d96ee21169c8f2658..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/Routing/Enhancer/AuthenticationEnhancer.php
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Routing\Enhancer\AuthenticationEnhancer.
- */
-
-namespace Drupal\Core\Routing\Enhancer;
-
-use Drupal\Core\Authentication\AuthenticationManagerInterface;
-use Drupal\Core\Session\AccountProxyInterface;
-use Drupal\Core\Session\AnonymousUserSession;
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\Routing\Route;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-
-/**
- * Authentication cleanup for incoming routes.
- *
- * The authentication system happens before routing, so all authentication
- * providers will attempt to authorize a user. However, not all routes allow
- * all authentication mechanisms. Instead, we check if the used provider is
- * valid for the matched route and if not, force the user to anonymous.
- */
-class AuthenticationEnhancer implements RouteEnhancerInterface {
-
-  /**
-   * The authentication manager.
-   *
-   * @var \Drupal\Core\Authentication\AuthenticationManager
-   */
-  protected $manager;
-
-  /**
-   * The current user service.
-   *
-   * @var \Drupal\Core\Session\AccountProxyInterface
-   */
-  protected $currentUser;
-
-  /**
-   * Constructs a AuthenticationEnhancer object.
-   *
-   * @param \Drupal\Core\Authentication\AuthenticationManagerInterface $manager
-   *   The authentication manager.
-   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
-   *   The current user service.
-   */
-  function __construct(AuthenticationManagerInterface $manager, AccountProxyInterface $current_user) {
-    $this->manager = $manager;
-    $this->currentUser = $current_user;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function enhance(array $defaults, Request $request) {
-    $auth_provider_triggered = $request->attributes->get('_authentication_provider');
-    if (!empty($auth_provider_triggered)) {
-      $route = isset($defaults[RouteObjectInterface::ROUTE_OBJECT]) ? $defaults[RouteObjectInterface::ROUTE_OBJECT] : NULL;
-
-      $auth_providers = ($route && $route->getOption('_auth')) ? $route->getOption('_auth') : array($this->manager->defaultProviderId());
-      // If the request was authenticated with a non-permitted provider,
-      // force the user back to anonymous.
-      if (!in_array($auth_provider_triggered, $auth_providers)) {
-        $anonymous_user = new AnonymousUserSession();
-
-        $this->currentUser->setAccount($anonymous_user);
-      }
-    }
-    return $defaults;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function applies(Route $route) {
-    return TRUE;
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Session/AccountProxy.php b/core/lib/Drupal/Core/Session/AccountProxy.php
index 2aa8fa5e466a3bf9458b655a6792789210a31941..eae71e7731db84450748fcd79ed8961f417e53f4 100644
--- a/core/lib/Drupal/Core/Session/AccountProxy.php
+++ b/core/lib/Drupal/Core/Session/AccountProxy.php
@@ -7,9 +7,6 @@
 
 namespace Drupal\Core\Session;
 
-use Drupal\Core\Authentication\AuthenticationManagerInterface;
-use Symfony\Component\HttpFoundation\RequestStack;
-
 /**
  * A proxied implementation of AccountInterface.
  *
@@ -23,20 +20,6 @@
  */
 class AccountProxy implements AccountProxyInterface {
 
-  /**
-   * The current request.
-   *
-   * @var \Symfony\Component\HttpFoundation\RequestStack
-   */
-  protected $requestStack;
-
-  /**
-   * The authentication manager.
-   *
-   * @var \Drupal\Core\Authentication\AuthenticationManagerInterface
-   */
-  protected $authenticationManager;
-
   /**
    * The instantiated account.
    *
@@ -45,17 +28,11 @@ class AccountProxy implements AccountProxyInterface {
   protected $account;
 
   /**
-   * Constructs a new AccountProxy.
+   * Initial account id.
    *
-   * @param \Drupal\Core\Authentication\AuthenticationManagerInterface $authentication_manager
-   *   The authentication manager.
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The request object used for authenticating.
+   * @var int
    */
-  public function __construct(AuthenticationManagerInterface $authentication_manager, RequestStack $requestStack) {
-    $this->authenticationManager = $authentication_manager;
-    $this->requestStack = $requestStack;
-  }
+  protected $initialAccountId;
 
   /**
    * {@inheritdoc}
@@ -75,10 +52,17 @@ public function setAccount(AccountInterface $account) {
    */
   public function getAccount() {
     if (!isset($this->account)) {
-      // Use the master request to prevent subrequests authenticating to a
-      // different user.
-      $this->setAccount($this->authenticationManager->authenticate($this->requestStack->getMasterRequest()));
+      if ($this->initialAccountId) {
+        // After the container is rebuilt, DrupalKernel sets the initial
+        // account to the id of the logged in user. This is necessary in order
+        // to refresh the user account reference here.
+        $this->account = $this->loadUserEntity($this->initialAccountId);
+      }
+      else {
+        $this->account = new AnonymousUserSession();
+      }
     }
+
     return $this->account;
   }
 
@@ -187,5 +171,38 @@ public function getLastAccessedTime() {
     return $this->getAccount()->getLastAccessedTime();
   }
 
-}
+  /**
+   * {@inheritdoc}
+   */
+  public function setInitialAccountId($account_id) {
+    if (isset($this->account)) {
+      throw new \LogicException('AccountProxyInterface::setInitialAccountId() cannot be called after an account was set on the AccountProxy');
+    }
+
+    $this->initialAccountId = $account_id;
+  }
+
+  /**
+   * Load a user entity.
+   *
+   * The entity manager requires additional initialization code and cache
+   * clearing after the list of modules is changed. Therefore it is necessary to
+   * retrieve it as late as possible.
+   *
+   * Because of serialization issues it is currently not possible to inject the
+   * container into the AccountProxy. Thus it is necessary to retrieve the
+   * entity manager statically.
+   *
+   * @see https://www.drupal.org/node/2430447
+   *
+   * @param int $account_id
+   *   The id of an account to load.
+   *
+   * @return \Drupal\Core\Session\AccountInterface|NULL
+   *   An account or NULL if none is found.
+   */
+  protected function loadUserEntity($account_id) {
+    return \Drupal::entityManager()->getStorage('user')->load($account_id);
+  }
 
+}
diff --git a/core/lib/Drupal/Core/Session/AccountProxyInterface.php b/core/lib/Drupal/Core/Session/AccountProxyInterface.php
index 017bd8b080a823ab999a8e6ce076fe8b0931f914..7ac9758202ed0d334948c31c2eaad79c866cd63c 100644
--- a/core/lib/Drupal/Core/Session/AccountProxyInterface.php
+++ b/core/lib/Drupal/Core/Session/AccountProxyInterface.php
@@ -37,5 +37,15 @@ public function setAccount(AccountInterface $account);
    */
   public function getAccount();
 
-}
+  /**
+   * Sets the id of the initial account.
+   *
+   * Never use this method, its sole purpose is to work around weird effects
+   * during mid-request container rebuilds.
+   *
+   * @param int $account_id
+   *   The id of the initial account.
+   */
+  public function setInitialAccountId($account_id);
 
+}
diff --git a/core/lib/Drupal/Core/Session/SessionHandler.php b/core/lib/Drupal/Core/Session/SessionHandler.php
index 81877f9b3a48b5ebae767d09e051b9c17c494f04..8d9aa960eef8bb0b3046f197aba2dc660bc117fc 100644
--- a/core/lib/Drupal/Core/Session/SessionHandler.php
+++ b/core/lib/Drupal/Core/Session/SessionHandler.php
@@ -64,7 +64,7 @@ public function open($save_path, $name) {
    * {@inheritdoc}
    */
   public function read($sid) {
-    // @todo Remove global in https://www.drupal.org/node/2286971
+    // @todo Remove global in https://www.drupal.org/node/2228393
     global $_session_user;
 
     // Handle the case of first time visitors and clients that don't store
diff --git a/core/lib/Drupal/Core/Session/SessionManager.php b/core/lib/Drupal/Core/Session/SessionManager.php
index 706e7ab20ad3a20cb1b3998e45a7e306217d0be5..7c3ac21b5b8a8bea062085cc064f4be7918874ef 100644
--- a/core/lib/Drupal/Core/Session/SessionManager.php
+++ b/core/lib/Drupal/Core/Session/SessionManager.php
@@ -121,7 +121,7 @@ public function start() {
     }
 
     if (empty($result)) {
-      // @todo Remove global in https://www.drupal.org/node/2286971
+      // @todo Remove global in https://www.drupal.org/node/2228393
       global $_session_user;
       $_session_user = new AnonymousUserSession();
 
diff --git a/core/lib/Drupal/Core/StackMiddleware/Session.php b/core/lib/Drupal/Core/StackMiddleware/Session.php
index f92e778b8287498406106686d19afd17beb8ed98..e0ed0e4e236fc1121f388e517c7f1b3f090216e8 100644
--- a/core/lib/Drupal/Core/StackMiddleware/Session.php
+++ b/core/lib/Drupal/Core/StackMiddleware/Session.php
@@ -54,7 +54,9 @@ public function __construct(HttpKernelInterface $http_kernel, $service_name = 's
    */
   public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
     if ($type === self::MASTER_REQUEST && PHP_SAPI !== 'cli') {
-      $request->setSession($this->container->get($this->sessionServiceName));
+      $session = $this->container->get($this->sessionServiceName);
+      $session->start();
+      $request->setSession($session);
     }
 
     $result = $this->httpKernel->handle($request, $type, $catch);
diff --git a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
index 2fed1ed71b216254b7572df506e926fec1e07bc3..c2b8483ffb4d6f6d69f6a2aa48456da49fc0e973 100644
--- a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
+++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php
@@ -7,21 +7,20 @@
 
 namespace Drupal\basic_auth\Authentication\Provider;
 
-use \Drupal\Component\Utility\String;
+use Drupal\Component\Utility\String;
 use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Flood\FloodInterface;
 use Drupal\user\UserAuthInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
 use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
-use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 
 /**
  * HTTP Basic authentication provider.
  */
-class BasicAuth implements AuthenticationProviderInterface {
+class BasicAuth implements AuthenticationProviderInterface, AuthenticationProviderChallengeInterface {
 
   /**
    * The config factory.
@@ -131,25 +130,12 @@ public function authenticate(Request $request) {
   /**
    * {@inheritdoc}
    */
-  public function cleanup(Request $request) {}
-
-  /**
-   * {@inheritdoc}
-   */
-  public function handleException(GetResponseForExceptionEvent $event) {
-    $exception = $event->getException();
-    if (\Drupal::currentUser()->isAnonymous() && $exception instanceof AccessDeniedHttpException) {
-      if (!$this->applies($event->getRequest())) {
-        $site_name = $this->configFactory->get('system.site')->get('name');
-        global $base_url;
-        $challenge = String::format('Basic realm="@realm"', array(
-          '@realm' => !empty($site_name) ? $site_name : $base_url,
-        ));
-        $event->setException(new UnauthorizedHttpException($challenge, 'No authentication credentials provided.', $exception));
-      }
-      return TRUE;
-    }
-    return FALSE;
+  public function challengeException(Request $request, \Exception $previous) {
+    $site_name = $this->configFactory->get('system.site')->get('name');
+    $challenge = String::format('Basic realm="@realm"', array(
+      '@realm' => !empty($site_name) ? $site_name : 'Access restricted',
+    ));
+    return new UnauthorizedHttpException($challenge, 'No authentication credentials provided.', $previous);
   }
 
 }
diff --git a/core/modules/block/src/Tests/BlockLanguageTest.php b/core/modules/block/src/Tests/BlockLanguageTest.php
index d8e9f1889eeffd55850b62fe6d9bd9e634338836..c3f87a115f1cbba66a939821d46e83d02cc67e7f 100644
--- a/core/modules/block/src/Tests/BlockLanguageTest.php
+++ b/core/modules/block/src/Tests/BlockLanguageTest.php
@@ -149,6 +149,10 @@ public function testMultipleLanguageTypes() {
     $this->drupalGet('node', ['query' => ['language' => 'fr']]);
     $this->assertText('Powered by Drupal', 'The body of the block appears on the page.');
 
+    // Re-login in order to clear the interface language stored in the session.
+    $this->drupalLogout();
+    $this->drupalLogin($this->adminUser);
+
     // Content language does not depend on session/request arguments.
     // It will fall back on English (site default) and not display the block.
     $this->drupalGet('en');
diff --git a/core/modules/locale/tests/modules/early_translation_test/src/Auth.php b/core/modules/locale/tests/modules/early_translation_test/src/Auth.php
index afb5ea1685f554b2484a1f0d3244604e0f97b612..12da491c612bee7b5006e77294798deac61b8f7d 100644
--- a/core/modules/locale/tests/modules/early_translation_test/src/Auth.php
+++ b/core/modules/locale/tests/modules/early_translation_test/src/Auth.php
@@ -10,7 +10,6 @@
 use Drupal\Core\Authentication\AuthenticationProviderInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
 
 /**
  * Test authentication provider.
@@ -53,16 +52,4 @@ public function authenticate(Request $request) {
     return NULL;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function cleanup(Request $request) {}
-
-  /**
-   * {@inheritdoc}
-   */
-  public function handleException(GetResponseForExceptionEvent $event) {
-    return FALSE;
-  }
-
 }
diff --git a/core/modules/rest/rest.services.yml b/core/modules/rest/rest.services.yml
index 14e4ab219b660b20ca9ba0e6ab14ced3dbe3f2b4..bfba7bb5688ea17bb28aaf97fc441f0eeed77c1f 100644
--- a/core/modules/rest/rest.services.yml
+++ b/core/modules/rest/rest.services.yml
@@ -11,6 +11,7 @@ services:
     arguments: [rest]
   access_check.rest.csrf:
     class: Drupal\rest\Access\CSRFAccessCheck
+    arguments: ['@session_configuration']
     tags:
       - { name: access_check }
   rest.link_manager:
diff --git a/core/modules/rest/src/Access/CSRFAccessCheck.php b/core/modules/rest/src/Access/CSRFAccessCheck.php
index 563a0a4cdec22b1e99d506400f7f864cffbbf534..2e39c61d1b49de80a09330cb2412e967b8e2b45d 100644
--- a/core/modules/rest/src/Access/CSRFAccessCheck.php
+++ b/core/modules/rest/src/Access/CSRFAccessCheck.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Access\AccessCheckInterface;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Session\SessionConfigurationInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -18,6 +19,23 @@
  */
 class CSRFAccessCheck implements AccessCheckInterface {
 
+  /**
+   * The session configuration.
+   *
+   * @var \Drupal\Core\Session\SessionConfigurationInterface
+   */
+  protected $sessionConfiguration;
+
+  /**
+   * Constructs a new rest CSRF access check.
+   *
+   * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
+   *   The session configuration.
+   */
+  public function __construct(SessionConfigurationInterface $session_configuration) {
+    $this->sessionConfiguration = $session_configuration;
+  }
+
   /**
    * Implements AccessCheckInterface::applies().
    */
@@ -54,7 +72,6 @@ public function applies(Route $route) {
    */
   public function access(Request $request, AccountInterface $account) {
     $method = $request->getMethod();
-    $cookie = $request->attributes->get('_authentication_provider') == 'cookie';
 
     // This check only applies if
     // 1. this is a write operation
@@ -62,7 +79,7 @@ public function access(Request $request, AccountInterface $account) {
     // 3. the request comes with a session cookie.
     if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))
       && $account->isAuthenticated()
-      && $cookie
+      && $this->sessionConfiguration->hasSession($request)
     ) {
       $csrf_token = $request->headers->get('X-CSRF-Token');
       if (!\Drupal::csrfToken()->validate($csrf_token, 'rest')) {
@@ -72,4 +89,5 @@ public function access(Request $request, AccountInterface $account) {
     // Let other access checkers decide if the request is legit.
     return AccessResult::allowed()->setCacheable(FALSE);
   }
+
 }
diff --git a/core/modules/rest/src/Tests/AuthTest.php b/core/modules/rest/src/Tests/AuthTest.php
index c8a8e3b81c13c33d2372179a1edef77d11094e72..09537c8ff1b8ca0b5b1992407bf63d4de205a9b9 100644
--- a/core/modules/rest/src/Tests/AuthTest.php
+++ b/core/modules/rest/src/Tests/AuthTest.php
@@ -56,15 +56,15 @@ public function testRead() {
     // Try to read the resource with session cookie authentication, which is
     // not enabled and should not work.
     $this->httpRequest($entity->urlInfo(), 'GET', NULL, $this->defaultMimeType);
-    $this->assertResponse('401', 'HTTP response code is 401 when the request is authenticated but not authorized.');
+    $this->assertResponse('403', 'HTTP response code is 403 when the request was authenticated by the wrong authentication provider.');
 
     // Ensure that cURL settings/headers aren't carried over to next request.
     unset($this->curlHandle);
 
     // Now read it with the Basic authentication which is enabled and should
     // work.
-    $this->basicAuthGet($entity->urlInfo(), $account->getUsername(), $account->pass_raw);
-    $this->assertResponse('200', 'HTTP response code is 200 for successfully authorized requests.');
+    $this->basicAuthGet($entity->urlInfo(), $account->getUsername(), $account->pass_raw, $this->defaultMimeType);
+    $this->assertResponse('200', 'HTTP response code is 200 for successfully authenticated requests.');
     $this->curlClose();
   }
 
@@ -80,11 +80,16 @@ public function testRead() {
    *   The user name to authenticate with.
    * @param string $password
    *   The password.
+   * @param string $mime_type
+   *   The MIME type for the Accept header.
    *
    * @return string
    *   Curl output.
    */
-  protected function basicAuthGet(Url $url, $username, $password) {
+  protected function basicAuthGet(Url $url, $username, $password, $mime_type = NULL) {
+    if (!isset($mime_type)) {
+      $mime_type = $this->defaultMimeType;
+    }
     $out = $this->curlExec(
       array(
         CURLOPT_HTTPGET => TRUE,
@@ -92,6 +97,7 @@ protected function basicAuthGet(Url $url, $username, $password) {
         CURLOPT_NOBODY => FALSE,
         CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
         CURLOPT_USERPWD => $username . ':' . $password,
+        CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
       )
     );
 
diff --git a/core/modules/rest/src/Tests/CsrfTest.php b/core/modules/rest/src/Tests/CsrfTest.php
index d44d7876f3cc8e5869673cd8078fb45a48862b7e..a686f448049aeeaf985710bc28b5a585686c1d1e 100644
--- a/core/modules/rest/src/Tests/CsrfTest.php
+++ b/core/modules/rest/src/Tests/CsrfTest.php
@@ -61,9 +61,6 @@ protected function setUp() {
    * Tests that CSRF check is not triggered for Basic Auth requests.
    */
   public function testBasicAuth() {
-    // Login so the session cookie is sent in addition to the basic auth header.
-    $this->drupalLogin($this->account);
-
     $curl_options = $this->getCurlOptions();
     $curl_options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
     $curl_options[CURLOPT_USERPWD] = $this->account->getUsername() . ':' . $this->account->pass_raw;
diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml
index 50405b56dec5c89abcc3d157e9fafdc5d904a10d..4bee86981823a50da0bb385425ac87f74b1de126 100644
--- a/core/modules/user/user.services.yml
+++ b/core/modules/user/user.services.yml
@@ -17,6 +17,7 @@ services:
       - { name: access_check, applies_to: _user_is_logged_in }
   authentication.cookie:
     class: Drupal\Core\Authentication\Provider\Cookie
+    arguments: ['@session_configuration']
     tags:
       - { name: authentication_provider, priority: 0 }
   cache_context.user:
diff --git a/core/tests/Drupal/Tests/Core/Authentication/AuthenticationManagerTest.php b/core/tests/Drupal/Tests/Core/Authentication/AuthenticationManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..370cc8cdc59ea0ed3a19c9c8f9f30e8561101956
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Authentication/AuthenticationManagerTest.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Authentication\AuthenticationManagerTest.
+ */
+
+namespace Drupal\Tests\Core\Authentication;
+
+use Drupal\Core\Authentication\AuthenticationManager;
+use Drupal\Core\Authentication\AuthenticationProviderFilterInterface;
+use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Cmf\Component\Routing\RouteObjectInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Authentication\AuthenticationManager
+ * @group Authentication
+ */
+class AuthenticationManagerTest extends UnitTestCase {
+
+  /**
+   * @covers ::defaultFilter
+   * @covers ::applyFilter
+   *
+   * @dataProvider providerTestDefaultFilter
+   */
+  public function testDefaultFilter($applies, $has_route, $auth_option, $provider_id, $global_providers = ['cookie' => TRUE]) {
+    $authentication_manager = new AuthenticationManager($global_providers);
+    $auth_provider = $this->getMock('Drupal\Core\Authentication\AuthenticationProviderInterface');
+    $authentication_manager->addProvider($auth_provider, 'authentication.' . $provider_id);
+
+    $request = new Request();
+    if ($has_route) {
+      $route = new Route('/example');
+      if ($auth_option) {
+        $route->setOption('_auth', $auth_option);
+      }
+      $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, $route);
+    }
+
+    $this->assertSame($applies, $authentication_manager->appliesToRoutedRequest($request, FALSE));
+  }
+
+  /**
+   * @covers ::applyFilter
+   */
+  public function testApplyFilterWithFilterprovider() {
+    $authentication_manager = new AuthenticationManager();
+    $auth_provider = $this->getMock('Drupal\Tests\Core\Authentication\TestAuthenticationProviderInterface');
+    $authentication_manager->addProvider($auth_provider, 'authentication.filtered');
+
+    $auth_provider->expects($this->once())
+      ->method('appliesToRoutedRequest')
+      ->willReturn(TRUE);
+
+    $request = new Request();
+    $this->assertTrue($authentication_manager->appliesToRoutedRequest($request, FALSE));
+  }
+
+  /**
+   * Provides data to self::testDefaultFilter().
+   */
+  public function providerTestDefaultFilter() {
+    $data = [];
+    // No route, cookie is global, should apply.
+    $data[] = [TRUE, FALSE, [], 'cookie'];
+    // No route, cookie is not global, should not apply.
+    $data[] = [FALSE, FALSE, [], 'cookie', ['other' => TRUE]];
+    // Route, no _auth, cookie is global, should apply.
+    $data[] = [TRUE, TRUE, [], 'cookie'];
+    // Route, no _auth, cookie is not global, should not apply.
+    $data[] = [FALSE, TRUE, [], 'cookie', ['other' => TRUE]];
+    // Route, with _auth and non-matching provider, should not apply.
+    $data[] = [FALSE, TRUE, ['basic_auth'], 'cookie'];
+    // Route, with _auth and matching provider should not apply.
+    $data[] = [TRUE, TRUE, ['basic_auth'], 'basic_auth'];
+    return $data;
+  }
+
+}
+
+/**
+ * Helper interface to mock two interfaces at once.
+ */
+interface TestAuthenticationProviderInterface extends AuthenticationProviderFilterInterface, AuthenticationProviderInterface {}
diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php
index 1c90a626ba39a053969d2aa91d7eb94b2be5fb43..4b9405514aedf264c3e6806c28643c3d198b79dc 100644
--- a/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php
+++ b/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php
@@ -46,7 +46,6 @@ public function providerTestOnRouteBuildingInvalidVariables() {
     $routes = array();
     $routes[] = array(new Route('/test/{system_path}'));
     $routes[] = array(new Route('/test/{_legacy}'));
-    $routes[] = array(new Route('/test/{_authentication_provider}'));
     $routes[] = array(new Route('/test/{' . RouteObjectInterface::ROUTE_OBJECT . '}'));
     $routes[] = array(new Route('/test/{' . RouteObjectInterface::ROUTE_NAME . '}'));
     $routes[] = array(new Route('/test/{_content}'));