diff --git a/core/core.services.yml b/core/core.services.yml
index 1ee07fe..3dd2765 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1100,6 +1100,11 @@ services:
     tags:
       - { name: access_check, applies_to: _csrf_token, needs_incoming_request: TRUE }
     arguments: ['@csrf_token']
+  access_check.header.csrf:
+    class: Drupal\Core\Access\CsrfRequestHeaderAccessCheck
+    arguments: ['@session_configuration']
+    tags:
+      - { name: access_check }
   maintenance_mode:
     class: Drupal\Core\Site\MaintenanceMode
     arguments: ['@state', '@current_user']
diff --git a/core/modules/rest/src/Access/CSRFAccessCheck.php b/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php
similarity index 82%
rename from core/modules/rest/src/Access/CSRFAccessCheck.php
rename to core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php
index c6dc04f..d9cab5b 100644
--- a/core/modules/rest/src/Access/CSRFAccessCheck.php
+++ b/core/lib/Drupal/Core/Access/CsrfRequestHeaderAccessCheck.php
@@ -1,9 +1,7 @@
 <?php
 
-namespace Drupal\rest\Access;
+namespace Drupal\Core\Access;
 
-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;
@@ -12,7 +10,7 @@
 /**
  * Access protection against CSRF attacks.
  */
-class CSRFAccessCheck implements AccessCheckInterface {
+class CsrfRequestHeaderAccessCheck implements AccessCheckInterface {
 
   /**
    * The session configuration.
@@ -36,8 +34,13 @@ public function __construct(SessionConfigurationInterface $session_configuration
    */
   public function applies(Route $route) {
     $requirements = $route->getRequirements();
+    // Check for current requirement _csrf_request_header_token and deprecated
+    // REST requirement.
+    // @todo Remove _access_rest_csrf in Drupal 9.0.0.
+    $applicable_requirements = ['_csrf_request_header_token', '_access_rest_csrf'];
+    $requirement_keys = array_keys($requirements);
 
-    if (array_key_exists('_access_rest_csrf', $requirements)) {
+    if (array_intersect($applicable_requirements, $requirement_keys)) {
       if (isset($requirements['_method'])) {
         // There could be more than one method requirement separated with '|'.
         $methods = explode('|', $requirements['_method']);
@@ -77,7 +80,7 @@ public function access(Request $request, AccountInterface $account) {
       && $this->sessionConfiguration->hasSession($request)
     ) {
       $csrf_token = $request->headers->get('X-CSRF-Token');
-      if (!\Drupal::csrfToken()->validate($csrf_token, 'rest')) {
+      if (!\Drupal::csrfToken()->validate($csrf_token, 'header')) {
         return AccessResult::forbidden()->setCacheMaxAge(0);
       }
     }
diff --git a/core/modules/rest/rest.routing.yml b/core/modules/rest/rest.routing.yml
index 843dee9..2fdb1d7 100644
--- a/core/modules/rest/rest.routing.yml
+++ b/core/modules/rest/rest.routing.yml
@@ -1,6 +1,9 @@
+# @deprecated This path is deprectated use the system.csrftoken route from the
+# system module instead.
+# @todo Remove this route in Drupal 9.0.0.
 rest.csrftoken:
   path: '/rest/session/token'
   defaults:
-    _controller: '\Drupal\rest\RequestHandler::csrfToken'
+    _controller: '\Drupal\system\Controller\CSRFTokenController::csrfToken'
   requirements:
     _access: 'TRUE'
diff --git a/core/modules/rest/rest.services.yml b/core/modules/rest/rest.services.yml
index c702719..594c76c 100644
--- a/core/modules/rest/rest.services.yml
+++ b/core/modules/rest/rest.services.yml
@@ -8,11 +8,9 @@ services:
       - { name: cache.bin }
     factory: cache_factory:get
     arguments: [rest]
+  # @todo Remove this service in Drupal 9.0.0.
   access_check.rest.csrf:
-    class: Drupal\rest\Access\CSRFAccessCheck
-    arguments: ['@session_configuration']
-    tags:
-      - { name: access_check }
+    alias: access_check.header.csrf
   rest.link_manager:
     class: Drupal\rest\LinkManager\LinkManager
     arguments: ['@rest.link_manager.type', '@rest.link_manager.relation']
diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index b7cb630..df4c566 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -137,16 +137,6 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
   }
 
   /**
-   * Generates a CSRF protecting session token.
-   *
-   * @return \Symfony\Component\HttpFoundation\Response
-   *   The response object.
-   */
-  public function csrfToken() {
-    return new Response(\Drupal::csrfToken()->get('rest'), 200, array('Content-Type' => 'text/plain'));
-  }
-
-  /**
    * Renders a resource response.
    *
    * Serialization can invoke rendering (e.g., generating URLs), but the
diff --git a/core/modules/rest/src/Routing/ResourceRoutes.php b/core/modules/rest/src/Routing/ResourceRoutes.php
index 5d3ae30..8aedfba 100644
--- a/core/modules/rest/src/Routing/ResourceRoutes.php
+++ b/core/modules/rest/src/Routing/ResourceRoutes.php
@@ -91,7 +91,7 @@ protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_
       $methods = $route->getMethods();
       // Only expose routes where the method is enabled in the configuration.
       if ($methods && ($method = $methods[0]) && $supported_formats = $rest_resource_config->getFormats($method)) {
-        $route->setRequirement('_access_rest_csrf', 'TRUE');
+        $route->setRequirement('_csrf_request_header_token', 'TRUE');
 
         // Check that authentication providers are defined.
         if (empty($rest_resource_config->getAuthenticationProviders($method))) {
diff --git a/core/modules/rest/src/Tests/CsrfTest.php b/core/modules/rest/src/Tests/CsrfTest.php
index f98abad..d9d6f0c 100644
--- a/core/modules/rest/src/Tests/CsrfTest.php
+++ b/core/modules/rest/src/Tests/CsrfTest.php
@@ -87,7 +87,7 @@ public function testCookieAuth() {
     $this->assertFalse(entity_load_multiple($this->testEntityType, NULL, TRUE), 'No entity has been created in the database.');
 
     // Create an entity with the CSRF token.
-    $token = $this->drupalGet('rest/session/token');
+    $token = $this->drupalGet('session/token');
     $curl_options[CURLOPT_HTTPHEADER][] = "X-CSRF-Token: $token";
     $this->curlExec($curl_options);
     $this->assertResponse(201);
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index 4779b6b..f7cf3dd 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -95,7 +95,7 @@ protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) {
     }
     if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))) {
       // GET the CSRF token first for writing requests.
-      $token = $this->drupalGet('rest/session/token');
+      $token = $this->drupalGet('session/token');
     }
 
     $url = $this->buildUrl($url);
diff --git a/core/modules/system/src/Controller/CSRFTokenController.php b/core/modules/system/src/Controller/CSRFTokenController.php
new file mode 100644
index 0000000..66eda7b
--- /dev/null
+++ b/core/modules/system/src/Controller/CSRFTokenController.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\system\Controller;
+
+use Drupal\Core\Access\CsrfTokenGenerator;
+use Drupal\Core\Controller\ControllerBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Returns responses for CSRF token routes.
+ */
+class CSRFTokenController extends ControllerBase {
+
+  /**
+   * The CSRF token manager.
+   *
+   * @var \Drupal\Core\Access\CsrfTokenGenerator
+   */
+  protected $tokenGenerator;
+
+  /**
+   * Constructs a new CsrfTokenGenerator object.
+   *
+   * @param \Drupal\Core\Access\CsrfTokenGenerator $token_generator
+   *   The CSRF token manager.
+   */
+  public function __construct(CsrfTokenGenerator $token_generator) {
+    $this->tokenGenerator = $token_generator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('csrf_token')
+    );
+  }
+
+  /**
+   * Returns a CSRF protecting session token.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   The response object.
+   */
+  public function csrfToken() {
+    return new Response($this->tokenGenerator->get('header'), 200, array('Content-Type' => 'text/plain'));
+  }
+
+}
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 547b083..9449847 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -492,3 +492,10 @@ system.entity_autocomplete:
     _controller: '\Drupal\system\Controller\EntityAutocompleteController::handleAutocomplete'
   requirements:
     _access: 'TRUE'
+
+system.csrftoken:
+  path: '/session/token'
+  defaults:
+    _controller: '\Drupal\system\Controller\CSRFTokenController::csrfToken'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml b/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml
new file mode 100644
index 0000000..c830f58
--- /dev/null
+++ b/core/modules/system/tests/modules/csrf_test/csrf_test.info.yml
@@ -0,0 +1,6 @@
+name: CSRF test
+type: module
+description: 'Support testing protecting routes with CSRF token.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/csrf_test/csrf_test.routing.yml b/core/modules/system/tests/modules/csrf_test/csrf_test.routing.yml
new file mode 100644
index 0000000..4a4cf1e
--- /dev/null
+++ b/core/modules/system/tests/modules/csrf_test/csrf_test.routing.yml
@@ -0,0 +1,18 @@
+# Tests CSRF Header Token protection.
+csrf_test.protected:
+  path: csrf/protected
+  defaults:
+    _controller: '\Drupal\csrf_test\Controller\TestController::testMethod'
+  requirements:
+    _csrf_request_header_token: 'TRUE'
+    _method: 'POST'
+# Tests deprecated _access_rest_csrf protection.
+# This originally was in the REST module but now is supported in Core/lib.
+# @todo Remove this test route in Drupal 9.0.0.
+csrf_test.deprecated.protected:
+  path: csrf/deprecated/protected
+  defaults:
+    _controller: '\Drupal\csrf_test\Controller\TestController::testMethod'
+  #methods:  [POST]
+  requirements:
+    _access_rest_csrf: 'TRUE'
diff --git a/core/modules/system/tests/modules/csrf_test/src/Controller/TestController.php b/core/modules/system/tests/modules/csrf_test/src/Controller/TestController.php
new file mode 100644
index 0000000..d722b3a
--- /dev/null
+++ b/core/modules/system/tests/modules/csrf_test/src/Controller/TestController.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\csrf_test\Controller;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Just a test controller for test routes.
+ */
+class TestController {
+
+  /**
+   * Just a test method for the test routes.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   The response object.
+   */
+  public function testMethod() {
+    return new Response('Sometimes it is hard to think of test content!');
+  }
+
+}
diff --git a/core/modules/system/tests/src/Functional/CsrfHeaderTest.php b/core/modules/system/tests/src/Functional/CsrfHeaderTest.php
new file mode 100644
index 0000000..c52582e
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/CsrfHeaderTest.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\tests\system\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+use GuzzleHttp\Cookie\CookieJar;
+
+/**
+ * Tests protecting routes by requiring CSRF token in the header.
+ *
+ * @group system
+ */
+class CsrfHeaderTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['system', 'csrf_test'];
+
+  /**
+   * Tests access to routes protected by CSRF header requirements.
+   *
+   * This checks to one route uses _csrf_request_header_token and one that uses
+   * the deprecated _access_rest_csrf.
+   */
+  public function testRouteAccess() {
+    $client = \Drupal::httpClient();
+    // Check both test routes.
+    $route_names = ['csrf_test.protected', 'csrf_test.deprecated.protected'];
+    foreach ($route_names as $route_name) {
+      $user = $this->drupalCreateUser();
+      $this->drupalLogin($user);
+
+      $csrf_token = $this->drupalGet('session/token');
+      $url = Url::fromRoute($route_name)
+        ->setAbsolute(TRUE)
+        ->toString();
+      $domain = parse_url($url, PHP_URL_HOST);
+
+      $session_id = $this->getSession()->getCookie($this->getSessionName());
+      /** @var \GuzzleHttp\Cookie\CookieJar $cookies */
+      $cookies = CookieJar::fromArray([$this->getSessionName() => $session_id], $domain);
+      $post_options = [
+        'headers' => ['Accept' => 'text/plain'],
+        'http_errors' => FALSE,
+      ];
+
+      // Test that access is allowed for anonymous user with no token in header.
+      $result = $client->post($url, $post_options);
+      $this->assertEquals(200, $result->getStatusCode());
+
+      // Add cookies to post options so that all other requests are for the
+      // authenticated user.
+      $post_options['cookies']  = $cookies;
+
+      // Test that access is denied with no token in header.
+      $result = $client->post($url, $post_options);
+      $this->assertEquals(403, $result->getStatusCode());
+
+      // Test that access is allowed with correct token in header.
+      $post_options['headers']['X-CSRF-Token'] = $csrf_token;
+      $result = $client->post($url, $post_options);
+      $this->assertEquals(200, $result->getStatusCode());
+
+      // Test that access is denied with incorrect token in header.
+      $post_options['headers']['X-CSRF-Token'] = 'this-is-not-the-token-you-are-looking-for';
+      $result = $client->post($url, $post_options);
+      $this->assertEquals(403, $result->getStatusCode());
+    }
+  }
+
+}
