diff --git a/core/core.services.yml b/core/core.services.yml
index 7b0e996..cfa1f52 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1100,7 +1100,7 @@ services:
     class: Drupal\Core\Access\CsrfAccessCheck
     tags:
       - { name: access_check, applies_to: _csrf_token, needs_incoming_request: TRUE }
-    arguments: ['@csrf_token']
+    arguments: ['@csrf_token', '@session_configuration']
   maintenance_mode:
     class: Drupal\Core\Site\MaintenanceMode
     arguments: ['@state', '@current_user']
@@ -1249,7 +1249,7 @@ services:
     class: Drupal\Core\Access\RouteProcessorCsrf
     tags:
       - { name: route_processor_outbound }
-    arguments: ['@csrf_token']
+    arguments: ['@csrf_token', '@session_configuration', '@request_stack']
   transliteration:
     class: Drupal\Core\Transliteration\PhpTransliteration
     arguments: [null, '@module_handler']
diff --git a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
index c72b116..ecf005b 100644
--- a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
+++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
@@ -4,6 +4,8 @@
 
 use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Session\SessionConfigurationInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -24,13 +26,19 @@ class CsrfAccessCheck implements RoutingAccessInterface {
   protected $csrfToken;
 
   /**
+   * @var \Drupal\Core\Session\SessionConfigurationInterface
+   */
+  protected $sessionConfiguration;
+
+  /**
    * Constructs a CsrfAccessCheck object.
    *
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    *   The CSRF token generator.
    */
-  public function __construct(CsrfTokenGenerator $csrf_token) {
+  public function __construct(CsrfTokenGenerator $csrf_token, SessionConfigurationInterface $session_configuration) {
     $this->csrfToken = $csrf_token;
+    $this->sessionConfiguration = $session_configuration;
   }
 
   /**
@@ -54,7 +62,9 @@ public function access(Route $route, Request $request, RouteMatchInterface $rout
       $path = str_replace("{{$param}}", $value, $path);
     }
 
-    if ($this->csrfToken->validate($request->query->get('token'), $path)) {
+    // Per https://www.drupal.org/node/2319205 anonymous users do not need
+    // CSRF checking.
+    if (!$this->sessionConfiguration->hasSession($request) || $this->csrfToken->validate($request->query->get('token'), $path)) {
       $result = AccessResult::allowed();
     }
     else {
diff --git a/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
index ce0a261..707a61f 100644
--- a/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
+++ b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
@@ -4,6 +4,8 @@
 
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
+use Drupal\Core\Session\SessionConfigurationInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -19,20 +21,34 @@ class RouteProcessorCsrf implements OutboundRouteProcessorInterface {
   protected $csrfToken;
 
   /**
+   * @var \Drupal\Core\Session\SessionConfigurationInterface
+   */
+  protected $sessionConfiguration;
+
+  /**
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
    * Constructs a RouteProcessorCsrf object.
    *
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    *   The CSRF token generator.
    */
-  function __construct(CsrfTokenGenerator $csrf_token) {
+  function __construct(CsrfTokenGenerator $csrf_token, SessionConfigurationInterface $session_configuration, RequestStack $request_stack) {
     $this->csrfToken = $csrf_token;
+    $this->sessionConfiguration = $session_configuration;
+    $this->requestStack = $request_stack;
   }
 
   /**
    * {@inheritdoc}
    */
   public function processOutbound($route_name, Route $route, array &$parameters, BubbleableMetadata $bubbleable_metadata = NULL) {
-    if ($route->hasRequirement('_csrf_token')) {
+    // Per https://www.drupal.org/node/2319205 anonymous users do not need
+    // CSRF checking.
+    if ($route->hasRequirement('_csrf_token') && $this->sessionConfiguration->hasSession($this->requestStack->getCurrentRequest())) {
       $path = ltrim($route->getPath(), '/');
       // Replace the path parameters with values from the parameters array.
       foreach ($parameters as $param => $value) {
diff --git a/core/modules/menu_link_content/tests/src/Kernel/MenuLinkContentCacheabilityBubblingTest.php b/core/modules/menu_link_content/tests/src/Kernel/MenuLinkContentCacheabilityBubblingTest.php
index c7689d9..f220341 100644
--- a/core/modules/menu_link_content/tests/src/Kernel/MenuLinkContentCacheabilityBubblingTest.php
+++ b/core/modules/menu_link_content/tests/src/Kernel/MenuLinkContentCacheabilityBubblingTest.php
@@ -53,13 +53,14 @@ public function testOutboundPathAndRouteProcessing() {
     $request = Request::create('/');
     $request->attributes->set(RouteObjectInterface::ROUTE_NAME, '<front>');
     $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/'));
+    // Fake a started session.
+    $request->cookies->add(['SESS' . substr(hash('sha256', $this->getDatabasePrefix()), 0, 32) => '']);
     $request_stack->push($request);
     $request_context->fromRequest($request);
 
     $menu_tree = \Drupal::menuTree();
     $renderer = \Drupal::service('renderer');
 
-
     $default_menu_cacheability = (new BubbleableMetadata())
       ->setCacheMaxAge(Cache::PERMANENT)
       ->setCacheTags(['config:system.menu.tools'])
diff --git a/core/modules/system/src/Tests/Common/UrlTest.php b/core/modules/system/src/Tests/Common/UrlTest.php
index 062e3e4..e2c1f46 100644
--- a/core/modules/system/src/Tests/Common/UrlTest.php
+++ b/core/modules/system/src/Tests/Common/UrlTest.php
@@ -43,6 +43,9 @@ function testLinkXSS() {
    * Tests that #type=link bubbles outbound route/path processors' metadata.
    */
   function testLinkBubbleableMetadata() {
+    // Fake a started session.
+    \Drupal::request()->cookies->add(['SESS' . substr(hash('sha256', $this->getDatabasePrefix()), 0, 32) => '']);
+
     $cases = [
       ['Regular link', 'internal:/user', [], ['contexts' => [], 'tags' => [], 'max-age' => Cache::PERMANENT], []],
       ['Regular link, absolute', 'internal:/user', ['absolute' => TRUE], ['contexts' => ['url.site'], 'tags' => [], 'max-age' => Cache::PERMANENT], []],
diff --git a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
index 3ed4e84..462f69d 100644
--- a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
@@ -29,11 +29,9 @@ class CsrfAccessCheckTest extends UnitTestCase {
   protected $accessCheck;
 
   /**
-   * The mock route match.
-   *
-   * @var \Drupal\Core\RouteMatch\RouteMatchInterface|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Core\Session\SessionConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject
    */
-  protected $routeMatch;
+  protected $sessionConfiguration;
 
   protected function setUp() {
     $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
@@ -41,14 +39,18 @@ protected function setUp() {
       ->getMock();
 
     $this->routeMatch = $this->getMock('Drupal\Core\Routing\RouteMatchInterface');
-
-    $this->accessCheck = new CsrfAccessCheck($this->csrfToken);
+    $this->sessionConfiguration = $this->getMock('Drupal\Core\Session\SessionConfigurationInterface');
+    $this->accessCheck = new CsrfAccessCheck($this->csrfToken, $this->sessionConfiguration);
   }
 
   /**
    * Tests the access() method with a valid token.
    */
   public function testAccessTokenPass() {
+    $this->sessionConfiguration->expects($this->once())
+      ->method('hasSession')
+      ->willReturn(TRUE);
+
     $this->csrfToken->expects($this->once())
       ->method('validate')
       ->with('test_query', 'test-path/42')
@@ -65,9 +67,30 @@ public function testAccessTokenPass() {
   }
 
   /**
+   * Tests the access() method for anonymous users.
+   */
+  public function testAccessTokenAnonymousPass() {
+    $this->sessionConfiguration->expects($this->once())
+      ->method('hasSession')
+      ->willReturn(FALSE);
+    $this->routeMatch->expects($this->once())
+      ->method('getRawParameters')
+      ->will($this->returnValue(array('node' => 42)));
+
+    $route = new Route('/test-path/{node}', array(), array('_csrf_token' => 'TRUE'));
+    $request = Request::create('/test-path/42?token=test_query');
+    $this->csrfToken->expects($this->never())
+      ->method('validate');
+    $this->assertEquals(AccessResult::allowed()->setCacheMaxAge(0), $this->accessCheck->access($route, $request, $this->routeMatch));
+  }
+
+  /**
    * Tests the access() method with an invalid token.
    */
   public function testAccessTokenFail() {
+    $this->sessionConfiguration->expects($this->once())
+      ->method('hasSession')
+      ->willReturn(TRUE);
     $this->csrfToken->expects($this->once())
       ->method('validate')
       ->with('test_query', 'test-path')
diff --git a/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php b/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
index d1f8f9e..40ca702 100644
--- a/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
@@ -27,18 +27,33 @@ class RouteProcessorCsrfTest extends UnitTestCase {
    */
   protected $processor;
 
+  /**
+   * @var \Drupal\Core\Session\SessionConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $sessionConfiguration;
+
   protected function setUp() {
     $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
       ->disableOriginalConstructor()
       ->getMock();
-
-    $this->processor = new RouteProcessorCsrf($this->csrfToken);
+    $this->sessionConfiguration = $this->getMock('Drupal\Core\Session\SessionConfigurationInterface');
+    $request_stack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack');
+    // The number this is called differs between tests and is completely
+    // irrelevant, the sessionConfiguration mock object will have the exact
+    // number of calls.
+    $request_stack->expects($this->atMost(1))
+      ->method('getCurrentRequest')
+      ->willReturn($this->getMock('Symfony\Component\HttpFoundation\Request'));
+
+    $this->processor = new RouteProcessorCsrf($this->csrfToken, $this->sessionConfiguration, $request_stack);
   }
 
   /**
  * Tests the processOutbound() method with no _csrf_token route requirement.
  */
   public function testProcessOutboundNoRequirement() {
+     $this->sessionConfiguration->expects($this->never())
+      ->method('hasSession');
     $this->csrfToken->expects($this->never())
       ->method('get');
 
@@ -58,6 +73,9 @@ public function testProcessOutboundNoRequirement() {
    * Tests the processOutbound() method with a _csrf_token route requirement.
    */
   public function testProcessOutbound() {
+    $this->sessionConfiguration->expects($this->once())
+      ->method('hasSession')
+      ->willReturn(TRUE);
     $route = new Route('/test-path', array(), array('_csrf_token' => 'TRUE'));
     $parameters = array();
 
@@ -77,9 +95,30 @@ public function testProcessOutbound() {
   }
 
   /**
+   * Tests the processOutbound() method for anonymous users.
+   */
+  public function testProcessOutboundForAnonymous() {
+    $this->sessionConfiguration->expects($this->once())
+      ->method('hasSession')
+      ->willReturn(FALSE);
+    $this->csrfToken->expects($this->never())
+      ->method('get');
+    $route = new Route('/test-path', array(), array('_csrf_token' => 'TRUE'));
+    $parameters = array();
+
+    $bubbleable_metadata = new BubbleableMetadata();
+    $this->processor->processOutbound('test', $route, $parameters, $bubbleable_metadata);
+    $this->assertEmpty($parameters);
+    $this->assertEquals((new BubbleableMetadata()), $bubbleable_metadata);
+  }
+
+  /**
    * Tests the processOutbound() method with a dynamic path and one replacement.
    */
   public function testProcessOutboundDynamicOne() {
+    $this->sessionConfiguration->expects($this->once())
+      ->method('hasSession')
+      ->willReturn(TRUE);
     $route = new Route('/test-path/{slug}', array(), array('_csrf_token' => 'TRUE'));
     $parameters = array('slug' => 100);
 
@@ -99,6 +138,9 @@ public function testProcessOutboundDynamicOne() {
    * Tests the processOutbound() method with two parameter replacements.
    */
   public function testProcessOutboundDynamicTwo() {
+    $this->sessionConfiguration->expects($this->once())
+      ->method('hasSession')
+      ->willReturn(TRUE);
     $route = new Route('{slug_1}/test-path/{slug_2}', array(), array('_csrf_token' => 'TRUE'));
     $parameters = array('slug_1' => 100, 'slug_2' => 'test');
 
