diff --git a/core/core.services.yml b/core/core.services.yml
index d44bcb1..2bcd286 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -293,7 +293,7 @@ services:
     arguments: ['@stream_wrapper_manager', '@settings', '@logger.channel.file']
   form_builder:
     class: Drupal\Core\Form\FormBuilder
-    arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@theme.manager', '@?csrf_token']
+    arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@theme.manager', '@private_key', '@keyvalue.expirable', '@?csrf_token']
   form_validator:
     class: Drupal\Core\Form\FormValidator
     arguments: ['@request_stack', '@string_translation', '@csrf_token', '@logger.channel.form']
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 4b995c1..2599ab7 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -15,8 +15,11 @@
 use Drupal\Core\Access\CsrfTokenGenerator;
 use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
+use Drupal\Core\PrivateKey;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Render\ElementInfoManagerInterface;
+use Drupal\Core\Site\Settings;
 use Drupal\Core\Theme\ThemeManagerInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\HttpFoundation\RequestStack;
@@ -103,6 +106,20 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
   protected $formCache;
 
   /**
+   * The private key service.
+   *
+   * @var \Drupal\Core\PrivateKey
+   */
+  protected $privateKey;
+
+  /**
+   * The expirable key value factory.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface
+   */
+  protected $keyValueExpirableFactory;
+
+  /**
    * Constructs a new FormBuilder.
    *
    * @param \Drupal\Core\Form\FormValidatorInterface $form_validator
@@ -123,10 +140,12 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS
    *   The element info manager.
    * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
    *   The theme manager.
+   * @param \Drupal\Core\PrivateKey $private_key
+   * @param \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface $key_value_expirable_factory
    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
    *   The CSRF token generator.
    */
-  public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ElementInfoManagerInterface $element_info, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL) {
+  public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ElementInfoManagerInterface $element_info, ThemeManagerInterface $theme_manager, PrivateKey $private_key, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CsrfTokenGenerator $csrf_token = NULL) {
     $this->formValidator = $form_validator;
     $this->formSubmitter = $form_submitter;
     $this->formCache = $form_cache;
@@ -135,6 +154,8 @@ public function __construct(FormValidatorInterface $form_validator, FormSubmitte
     $this->requestStack = $request_stack;
     $this->classResolver = $class_resolver;
     $this->elementInfo = $element_info;
+    $this->privateKey = $private_key;
+    $this->keyValueExpirableFactory = $key_value_expirable_factory;
     $this->csrfToken = $csrf_token;
     $this->themeManager = $theme_manager;
   }
@@ -179,6 +200,17 @@ public function getForm($form_arg) {
    * {@inheritdoc}
    */
   public function buildForm($form_id, FormStateInterface &$form_state) {
+    $temp_form_id = is_object($form_id) ? get_class($form_id) : $form_id;
+    try {
+      $hash_salt = Settings::getHashSalt();
+    }
+    catch (\RuntimeException $e) {
+      // During the installer no hash salt is defined yet.
+      $hash_salt = Crypt::randomBytes(8);
+    }
+    $form_build_key = 'form_build_key:' . Crypt::hmacBase64($temp_form_id . serialize($form_state), 'form_key' . $this->privateKey->get() . $hash_salt);
+    $this->keyValueExpirableFactory->get('form_build_key')->setWithExpire($form_build_key, [$temp_form_id, $form_state], 21600);
+
     // Ensure the form ID is prepared.
     $form_id = $this->getFormId($form_id, $form_state);
 
@@ -221,6 +253,13 @@ public function buildForm($form_id, FormStateInterface &$form_state) {
       $form = $this->retrieveForm($form_id, $form_state);
       $this->prepareForm($form_id, $form, $form_state);
 
+      $form['form_build_key'] = [
+        '#type' => 'hidden',
+        '#value' => $form_build_key,
+        '#name' => 'form_build_key',
+        '#parents' => ['form_build_key'],
+      ];
+
       // self::setCache() removes uncacheable $form_state keys (see properties
       // in \Drupal\Core\Form\FormState) in order for multi-step forms to work
       // properly. This means that form processing logic for single-step forms
diff --git a/core/lib/Drupal/Core/Form/FormState.php b/core/lib/Drupal/Core/Form/FormState.php
index 9d651cd..bd80d49 100644
--- a/core/lib/Drupal/Core/Form/FormState.php
+++ b/core/lib/Drupal/Core/Form/FormState.php
@@ -211,6 +211,7 @@ class FormState implements FormStateInterface {
     'form_id',
     'form_token',
     'form_build_id',
+    'form_build_key',
     'op',
   ];
 
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
index 87b9f02..a257ea4 100644
--- a/core/lib/Drupal/Core/Render/Element/RenderElement.php
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -128,7 +128,7 @@ public static function preRenderGroup($element) {
    */
   public static function processAjaxForm(&$element, FormStateInterface $form_state, &$complete_form) {
     $element = static::preRenderAjaxForm($element);
-    if (!empty($element['#ajax_processed'])) {
+    if (!empty($element['#ajax_processed']) && isset($element['#ajax']['url'])) {
       $form_state->setCached();
     }
     return $element;
diff --git a/core/modules/filter/src/Tests/TextFormatElementFormTest.php b/core/modules/filter/src/Tests/TextFormatElementFormTest.php
index b889c4a..3bd0c43 100644
--- a/core/modules/filter/src/Tests/TextFormatElementFormTest.php
+++ b/core/modules/filter/src/Tests/TextFormatElementFormTest.php
@@ -41,7 +41,7 @@ class TextFormatElementFormTest extends KernelTestBase implements FormInterface
   protected function setUp() {
     parent::setUp();
     $this->installEntitySchema('user');
-    $this->installSchema('system', ['sequences', 'router']);
+    $this->installSchema('system', ['sequences', 'router', 'key_value_expire']);
     $this->installConfig(['filter', 'filter_test']);
     // Filter tips link to the full-page.
     \Drupal::service('router.builder')->rebuild();
diff --git a/core/modules/options/src/Tests/OptionsFieldTest.php b/core/modules/options/src/Tests/OptionsFieldTest.php
index d70ec01..a626ac8 100644
--- a/core/modules/options/src/Tests/OptionsFieldTest.php
+++ b/core/modules/options/src/Tests/OptionsFieldTest.php
@@ -27,6 +27,8 @@ class OptionsFieldTest extends OptionsFieldUnitTestBase {
    * Test that allowed values can be updated.
    */
   function testUpdateAllowedValues() {
+    $this->installSchema('system', ['key_value_expire']);
+
     // All three options appear.
     $entity = entity_create('entity_test');
     $form = \Drupal::service('entity.form_builder')->getForm($entity);
diff --git a/core/modules/system/src/Controller/FormAjaxController.php b/core/modules/system/src/Controller/FormAjaxController.php
index 0c29322..87210fe 100644
--- a/core/modules/system/src/Controller/FormAjaxController.php
+++ b/core/modules/system/src/Controller/FormAjaxController.php
@@ -114,13 +114,11 @@ public static function create(ContainerInterface $container) {
    * @throws \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface
    */
   public function content(Request $request) {
-    $ajax_form = $this->getForm($request);
+    $ajax_form = $this->buildForm($request);
     $form = $ajax_form->getForm();
     $form_state = $ajax_form->getFormState();
     $commands = $ajax_form->getCommands();
 
-    $this->formBuilder->processForm($form['#form_id'], $form, $form_state);
-
     // We need to return the part of the form (or some other content) that needs
     // to be re-rendered so the browser can update the page with changed content.
     // Since this is the generic menu callback used by many Ajax elements, it is
@@ -216,4 +214,27 @@ protected function getForm(Request $request) {
     return new FileAjaxForm($form, $form_state, $form_id, $form_build_id, $commands);
   }
 
+  protected function buildForm(Request $request) {
+    $form_build_id = $request->request->get('form_build_id');
+    $form_build_key = $request->request->get('form_build_key');
+
+    $keyvalue = \Drupal::service('keyvalue.expirable');
+    $result = $keyvalue->get('form_build_key')->get($form_build_key);
+    if ($result === NULL) {
+      $this->logger->warning('Expired form.');
+      throw new BadRequestHttpException();
+    }
+    else {
+      list($form_id, $form_state) = $result;
+      $form = $this->formBuilder->buildForm($form_id, $form_state);
+
+      $commands = [];
+      if ($form_build_id != $form['#build_id']) {
+        // If the form build ID has changed, issue an Ajax command to update it.
+        $commands[] = new UpdateBuildIdCommand($form_build_id, $form['#build_id']);
+      }
+      return new FileAjaxForm($form, $form_state, $form_id, $form['#build_id'], $commands);
+    }
+  }
+
 }
diff --git a/core/modules/user/src/Tests/UserAccountFormFieldsTest.php b/core/modules/user/src/Tests/UserAccountFormFieldsTest.php
index 679eec0..f16dc8e 100644
--- a/core/modules/user/src/Tests/UserAccountFormFieldsTest.php
+++ b/core/modules/user/src/Tests/UserAccountFormFieldsTest.php
@@ -29,6 +29,8 @@ class UserAccountFormFieldsTest extends KernelTestBase {
    * Tests the root user account form section in the "Configure site" form.
    */
   function testInstallConfigureForm() {
+    $this->installSchema('system', ['key_value_expire']);
+
     require_once \Drupal::root() . '/core/includes/install.core.inc';
     require_once \Drupal::root() . '/core/includes/install.inc';
     $install_state = install_state_defaults();
@@ -53,6 +55,7 @@ function testInstallConfigureForm() {
   function testUserRegistrationForm() {
     // Install default configuration; required for AccountFormController.
     $this->installConfig(array('user'));
+    $this->installSchema('system', ['key_value_expire']);
 
     // Disable email confirmation to unlock the password field.
     $this->config('user.settings')
@@ -79,7 +82,7 @@ function testUserEditForm() {
     $this->installConfig(array('user'));
 
     // Install the router table and then rebuild.
-    $this->installSchema('system', ['router']);
+    $this->installSchema('system', ['router', 'key_value_expire']);
     \Drupal::service('router.builder')->rebuild();
 
     $form = $this->buildAccountForm('default');
diff --git a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
index 24c4ddd..ce1ce61 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormStateTest.php
@@ -535,7 +535,7 @@ public function testTemporaryValue() {
    */
   public function testGetCleanValueKeys() {
     $form_state = new FormState();
-    $this->assertSame($form_state->getCleanValueKeys(), ['form_id', 'form_token', 'form_build_id', 'op']);
+    $this->assertSame($form_state->getCleanValueKeys(), ['form_id', 'form_token', 'form_build_id', 'form_build_key', 'op']);
   }
 
   /**
@@ -554,7 +554,7 @@ public function testAddCleanValueKey() {
     $form_state = new FormState();
     $form_state->setValue('value_to_clean', 'rainbow_sprinkles');
     $form_state->addCleanValueKey('value_to_clean');
-    $this->assertSame($form_state->getCleanValueKeys(), ['form_id', 'form_token', 'form_build_id', 'op', 'value_to_clean']);
+    $this->assertSame($form_state->getCleanValueKeys(), ['form_id', 'form_token', 'form_build_id', 'form_build_key', 'op', 'value_to_clean']);
     return $form_state;
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
index 12b3260..8ae2abb 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Site\Settings;
 use Drupal\Tests\UnitTestCase;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\RequestStack;
@@ -133,11 +134,6 @@
   protected $translationManager;
 
   /**
-   * @var \Drupal\Core\DrupalKernelInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $kernel;
-
-  /**
    * @var \PHPUnit_Framework_MockObject_MockObject|\Psr\Log\LoggerInterface
    */
   protected $logger;
@@ -149,6 +145,16 @@
    */
   protected $themeManager;
 
+  /**
+   * The expirable key value factory.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $keyValueExpirableFactory;
+
+  /**
+   * {@inheritdoc}
+   */
   protected function setUp() {
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
 
@@ -172,6 +178,21 @@ protected function setUp() {
       ->disableOriginalConstructor()
       ->getMock();
     $this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
+
+    $form_build_key_store = $this->getMock('Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface');
+    $this->keyValueExpirableFactory = $this->getMock('Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface');
+    $this->keyValueExpirableFactory->expects($this->any())
+      ->method('get')
+      ->with('form_build_key')
+      ->willReturn($form_build_key_store);
+    // Set up the settings singleton.
+    new Settings([
+      'hash_salt' => $this->randomMachineName(),
+    ]);
+    $private_key = $this->getMockBuilder('Drupal\Core\PrivateKey')
+      ->disableOriginalConstructor()
+      ->getMock();
+
     $this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface');
     $this->request = new Request();
     $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
@@ -188,7 +209,7 @@ protected function setUp() {
       ->getMock();
     $this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
 
-    $this->formBuilder = new FormBuilder($this->formValidator, $this->formSubmitter, $this->formCache, $this->moduleHandler, $this->eventDispatcher, $this->requestStack, $this->classResolver, $this->elementInfo, $this->themeManager, $this->csrfToken, $this->kernel);
+    $this->formBuilder = new FormBuilder($this->formValidator, $this->formSubmitter, $this->formCache, $this->moduleHandler, $this->eventDispatcher, $this->requestStack, $this->classResolver, $this->elementInfo, $this->themeManager, $private_key, $this->keyValueExpirableFactory, $this->csrfToken);
   }
 
   /**
