diff --git a/core/includes/form.inc b/core/includes/form.inc
index 395cca7..22d4514 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -1137,6 +1137,7 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
 
       // Setting this error will cause the form to fail validation.
       form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
+      watchdog('system', 'Form token validation failed during form submission. %sent_token was sent, but %expected_token was expected. The form submission has been ignored.', array('%sent_token' => $form_state['values']['form_token'], '%expected_token' => md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))), WATCHDOG_ERROR, l(t('view form'), request_path()));
     }
   }
 
diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/FormTokenTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/FormTokenTest.php
new file mode 100644
index 0000000..9576a79
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Form/FormTokenTest.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Form\FormTest.
+ */
+
+namespace Drupal\system\Tests\Form;
+
+use Drupal\simpletest\WebTestBase;
+
+class FormTokenTest extends WebTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Form token validation',
+      'description' => 'Tests validation of Form API tokens.',
+      'group' => 'Form API',
+    );
+  }
+
+  function setUp() {
+    parent::setUp(array('form_test', 'dblog'));
+
+    // We need to log in as form tokens aren't validated for anonymous users as
+    // they are shared in cached pages.
+    $this->web_user = $this->drupalCreateUser(array('access content'));
+    $this->drupalLogin($this->web_user);
+  }
+
+  /**
+   * Test that form submissions with an invalid token are rejected, with an
+   * error message shown to the user and logged to the watchdog.
+   */
+  function testInvalidFormToken() {
+    $this->drupalGet('form-test/input-forgery');
+    $edit = array('checkboxes[one]' => TRUE, 'form_token' => 'invalid_form_token');
+    $this->drupalPost(NULL, $edit, 'Submit');
+
+    $this->assertRaw(t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => url('form-test/input-forgery'))), t('Form submission with invalid form token displayed an error.'));
+
+    $watchdog_count = db_select('watchdog', 'w')
+      ->condition('w.type', 'system')
+      ->condition('w.message', 'Form token validation failed during form submission. %sent_token was sent, but %expected_token was expected. The form submission has been ignored.')
+      ->condition('w.variables', '%invalid_form_token%', 'LIKE')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+    $this->assertEqual(1, $watchdog_count, t('Form submission with invalid form token was logged to the watchdog.'));
+  }
+}
diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module
index ff6d544..34f4d53 100644
--- a/core/modules/system/tests/modules/form_test/form_test.module
+++ b/core/modules/system/tests/modules/form_test/form_test.module
@@ -1746,7 +1746,8 @@ function _form_test_disabled_elements($form, &$form_state) {
 }
 
 /**
- * Build a form to test input forgery of enabled elements.
+ * Build a form to test input forgery of enabled elements. We also use this
+ * to test our form tokens.
  */
 function _form_test_input_forgery($form, &$form_state) {
   $form['#submit'] = array('_form_test_submit_values_json');
