diff --git a/core/core.services.yml b/core/core.services.yml
index 3d63c2fb27..b3ca4b0361 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -711,8 +711,8 @@ services:
     tags:
       - { name: persist }
   current_route_match:
-     class: Drupal\Core\Routing\CurrentRouteMatch
-     arguments: ['@request_stack']
+    class: Drupal\Core\Routing\CurrentRouteMatch
+    arguments: ['@request_stack']
   event_dispatcher:
     class: Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher
     arguments: ['@service_container']
@@ -792,10 +792,10 @@ services:
     calls:
       - [setContainer, ['@service_container']]
   http_middleware.cors:
-     class: Asm89\Stack\Cors
-     arguments: ['%cors.config%']
-     tags:
-       - { name: http_middleware, priority: 250 }
+    class: Asm89\Stack\Cors
+    arguments: ['%cors.config%']
+    tags:
+      - { name: http_middleware, priority: 250 }
   psr7.http_foundation_factory:
     class: Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory
   psr7.http_message_factory:
@@ -981,12 +981,12 @@ services:
     class: Drupal\Core\Path\PathValidator
     arguments: ['@router', '@router.no_access_checks', '@current_user', '@path_processor_manager']
 
-# The argument to the hashing service defined in services.yml, to the
-# constructor of PhpassHashedPassword is the log2 number of iterations for
-# password stretching.
-# @todo increase by 1 every Drupal version in order to counteract increases in
-# the speed and power of computers available to crack the hashes. The current
-# password hashing method was introduced in Drupal 7 with a log2 count of 15.
+  # The argument to the hashing service defined in services.yml, to the
+  # constructor of PhpassHashedPassword is the log2 number of iterations for
+  # password stretching.
+  # @todo increase by 1 every Drupal version in order to counteract increases in
+  # the speed and power of computers available to crack the hashes. The current
+  # password hashing method was introduced in Drupal 7 with a log2 count of 15.
   password:
     class: Drupal\Core\Password\PhpassHashedPassword
     arguments: [16]
@@ -1215,7 +1215,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']
   access_check.header.csrf:
     class: Drupal\Core\Access\CsrfRequestHeaderAccessCheck
     arguments: ['@session_configuration', '@csrf_token']
@@ -1371,7 +1371,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']
@@ -1454,7 +1454,7 @@ services:
       - [setContainer, ['@service_container']]
       - [setStandalone, ['\Laminas\Feed\Writer\StandaloneExtensionManager']]
     arguments: ['feed.writer.']
-# Laminas Feed reader plugins. Plugin instances should not be shared.
+  # Laminas Feed reader plugins. Plugin instances should not be shared.
   feed.reader.dublincoreentry:
     class: Laminas\Feed\Reader\Extension\DublinCore\Entry
     shared: false
@@ -1485,7 +1485,7 @@ services:
   feed.reader.podcastfeed:
     class: Laminas\Feed\Reader\Extension\Podcast\Feed
     shared: false
-# Laminas Feed writer plugins. Plugins should be set as prototype scope.
+  # Laminas Feed writer plugins. Plugins should be set as prototype scope.
   feed.writer.atomrendererfeed:
     class: Laminas\Feed\Writer\Extension\Atom\Renderer\Feed
     shared: false
diff --git a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
index 48e75acb58..ba92a1190a 100644
--- a/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
+++ b/core/lib/Drupal/Core/Access/CsrfAccessCheck.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Routing\Access\AccessInterface as RoutingAccessInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\SessionConfigurationInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -23,14 +24,24 @@ class CsrfAccessCheck implements RoutingAccessInterface {
    */
   protected $csrfToken;
 
+  /**
+   * The Session configuration interface.
+   *
+   * @var \Drupal\Core\Session\SessionConfigurationInterface
+   */
+  protected $sessionConfiguration;
+
   /**
    * Constructs a CsrfAccessCheck object.
    *
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    *   The CSRF token generator.
+   * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
+   *   The session configuration.
    */
-  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 +65,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 d0aec30185..1f1a1f5dcb 100644
--- a/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
+++ b/core/lib/Drupal/Core/Access/RouteProcessorCsrf.php
@@ -6,6 +6,8 @@
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface;
+use Drupal\Core\Session\SessionConfigurationInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -20,21 +22,43 @@ class RouteProcessorCsrf implements OutboundRouteProcessorInterface, TrustedCall
    */
   protected $csrfToken;
 
+  /**
+   * The Session configuration interface.
+   *
+   * @var \Drupal\Core\Session\SessionConfigurationInterface
+   */
+  protected $sessionConfiguration;
+
+  /**
+   * The Request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
   /**
    * Constructs a RouteProcessorCsrf object.
    *
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    *   The CSRF token generator.
+   * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
+   *   The session configuration.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
    */
-  public function __construct(CsrfTokenGenerator $csrf_token) {
+  public 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 c783d852df..397ab4c38d 100644
--- a/core/modules/menu_link_content/tests/src/Kernel/MenuLinkContentCacheabilityBubblingTest.php
+++ b/core/modules/menu_link_content/tests/src/Kernel/MenuLinkContentCacheabilityBubblingTest.php
@@ -56,6 +56,8 @@ 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);
 
diff --git a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
index 324d95b72d..1c45fb12e7 100644
--- a/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/CsrfAccessCheckTest.php
@@ -35,6 +35,13 @@ class CsrfAccessCheckTest extends UnitTestCase {
    */
   protected $routeMatch;
 
+  /**
+   * The mock session configuration.
+   *
+   * @var \Drupal\Core\Session\SessionConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $sessionConfiguration;
+
   protected function setUp(): void {
     $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
       ->disableOriginalConstructor()
@@ -42,13 +49,18 @@ protected function setUp() {
 
     $this->routeMatch = $this->createMock('Drupal\Core\Routing\RouteMatchInterface');
 
-    $this->accessCheck = new CsrfAccessCheck($this->csrfToken);
+    $this->sessionConfiguration = $this->createMock('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')
@@ -64,6 +76,27 @@ public function testAccessTokenPass() {
     $this->assertEquals(AccessResult::allowed()->setCacheMaxAge(0), $this->accessCheck->access($route, $request, $this->routeMatch));
   }
 
+  /**
+   * 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));
+  }
+
   /**
    * @covers ::access
    */
diff --git a/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php b/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
index cc552fcc5b..702f6374cd 100644
--- a/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
+++ b/core/tests/Drupal/Tests/Core/Access/RouteProcessorCsrfTest.php
@@ -28,18 +28,41 @@ class RouteProcessorCsrfTest extends UnitTestCase {
    */
   protected $processor;
 
+  /**
+   * The Session configuration interface.
+   *
+   * @var \Drupal\Core\Session\SessionConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $sessionConfiguration;
+
+
   protected function setUp(): void {
     $this->csrfToken = $this->getMockBuilder('Drupal\Core\Access\CsrfTokenGenerator')
       ->disableOriginalConstructor()
       ->getMock();
 
     $this->processor = new RouteProcessorCsrf($this->csrfToken);
+    $this->sessionConfiguration = $this->createMock('Drupal\Core\Session\SessionConfigurationInterface');
+
+    $request_stack = $this->createMock('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->createMock('Symfony\Component\HttpFoundation\Request'));
+
+    $this->processor = new RouteProcessorCsrf($this->csrfToken, $this->sessionConfiguration, $request_stack);
   }
 
   /**
- * Tests the processOutbound() method with no _csrf_token route requirement.
- */
+   * 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');
 
@@ -59,6 +82,10 @@ 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', [], ['_csrf_token' => 'TRUE']);
     $parameters = [];
 
@@ -77,10 +104,34 @@ public function testProcessOutbound() {
     $this->assertEquals((new BubbleableMetadata())->setAttachments(['placeholders' => [$placeholder => $placeholder_render_array]]), $bubbleable_metadata);
   }
 
+  /**
+   * 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}', [], ['_csrf_token' => 'TRUE']);
     $parameters = ['slug' => 100];
 
@@ -100,6 +151,10 @@ 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}', [], ['_csrf_token' => 'TRUE']);
     $parameters = ['slug_1' => 100, 'slug_2' => 'test'];
 
