diff --git a/plugins/views_data_export_plugin_display_export.inc b/plugins/views_data_export_plugin_display_export.inc
index ce2c6d7..2e730a9 100644
--- a/plugins/views_data_export_plugin_display_export.inc
+++ b/plugins/views_data_export_plugin_display_export.inc
@@ -238,8 +238,17 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
     }
 
     // Try and get a batch context if possible.
-    $eid = !empty($_GET['eid']) ? $_GET['eid'] :
-            (!empty($this->batched_execution_state->eid) ? $this->batched_execution_state->eid : FALSE);
+
+    if (!empty($_GET['eid']) && !empty($_GET['token']) && drupal_valid_token($_GET['token'], 'views_data_export/' . $_GET['eid'])) {
+      $eid = $_GET['eid'];
+    }
+    elseif (!empty($this->batched_execution_state->eid)) {
+      $eid = $this->batched_execution_state->eid;
+    }
+    else {
+      $eid = FALSE;
+    }
+
     if ($eid) {
       $this->batched_execution_state = views_data_export_get($eid);
     }
@@ -306,9 +315,13 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
     list($usec, $sec) = explode(' ', microtime());
     $this->batched_execution_state->sandbox['started'] = (float) $usec + (float) $sec;
 
+    // Pop something into the session to ensure it stays aorund.
+    $_SESSION['views_data_export'][$this->batched_execution_state->eid] = TRUE;
+
     // Build up our querystring for the final page callback.
     $querystring = array(
       'eid' => $this->batched_execution_state->eid,
+      'token' => drupal_get_token('views_data_export/' . $this->batched_execution_state->eid),
       'return-url' => NULL,
     );
 
@@ -404,9 +417,6 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
         break;
 
       case VIEWS_DATA_EXPORT_FOOTER:
-        // Update the temporary file size, otherwise we would get a problematic
-        // "Content-Length: 0" HTTP header, that may break the export download.
-        $this->outputfile_update_size();
         $sandbox['finished'] = 1;
         $state->batch_state = VIEWS_DATA_EXPORT_FINISHED;
         break;
@@ -424,6 +434,13 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
   function execute_final() {
     // Should we download the file.
     if (!empty($_GET['download'])) {
+      // Clean up our session, if we need to.
+      if (isset($_SESSION)) {
+        unset($_SESSION['views_data_export'][$this->batched_execution_state->eid];
+        if (empty($_SESSION['views_data_export'])) {
+          unset($_SESSION['views_data_export']);
+        }
+      }
       // This next method will exit.
       $this->transfer_file();
     }
@@ -532,6 +549,7 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
     $query = array(
       'download' => 1,
       'eid' => $this->batched_execution_state->eid,
+      'token' => drupal_get_token('views_data_export/' . $this->batched_execution_state->eid),
     );
 
     return theme('views_data_export_complete_page', array(
@@ -577,28 +595,9 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
     if (!$this->view->init_style()) {
       $this->view->build_info['fail'] = TRUE;
     }
-
-    $uri = $this->outputfile_path();
-    $scheme = file_uri_scheme($uri);
-    if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {
-      $headers = file_download_headers($uri);
-      if (count($headers)) {
-        // Set our headers and ensure no other one conflicts with them.
-        $this->add_http_headers();
-        $reserved_headers = array_flip(array('content-type', 'cache-control', 'content-disposition'));
-        foreach ($headers as $name => $value) {
-          if (isset($reserved_headers[drupal_strtolower($name)])) {
-            unset($headers[$name]);
-          }
-        }
-        file_transfer($uri, $headers);
-      }
-      drupal_access_denied();
-    }
-    else {
-      drupal_not_found();
-    }
-    drupal_exit();
+    // Set the headers.
+    $this->add_http_headers();
+    file_transfer($this->outputfile_path(), array());
   }
 
   /**
@@ -725,6 +724,11 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
     // Save the file into the DB.
     $file = $this->file_save_file($path);
 
+    // Make sure the file is marked as temporary.
+    // There is no FILE_STATUS_TEMPORARY constant.
+    $file->status = 0;
+    file_save($file);
+
     return $file->fid;
   }
 
@@ -738,18 +742,6 @@ class views_data_export_plugin_display_export extends views_plugin_display_feed
     }
   }
 
-  /**
-   * Updates the file size in the file entity.
-   */
-  protected function outputfile_update_size() {
-    $output_file = $this->outputfile_path();
-    $file = current(file_load_multiple(array(), array('uri' => $output_file)));
-    if ($file) {
-      $file->filesize = filesize($output_file);
-      file_save($file);
-    }
-  }
-
   function abort_export($errors) {
     // Just cause the next batch to do the clean-up
     if (!is_array($errors)) {
diff --git a/tests/access.test b/tests/access.test
index 1b58fa9..f826d75 100644
--- a/tests/access.test
+++ b/tests/access.test
@@ -38,19 +38,45 @@ class ViewsDataExportAccessTest extends ViewsDataExportBaseTest {
     variable_set('menu_rebuild_needed', TRUE);
 
     $this->drupalLogin($this->admin_user1);
+    // Catpure the session_id as the redirects in the request ditch it.
+    $session_id = $this->session_id;
     $this->assertBatchedExportEqual($path, $expected, 'Batched access export matched expected output.');
 
-    // Assert that we can re-download directly.
+    // Remove all the test data, so future exports will be different.
+    db_truncate('views_test')->execute();
+    $this->resetAll();
+
+    // Assert that we can re-download directly when supplying the token.
+    // We rely on this being the first export in this test class.
+    // Restore the session_id from above so we can use drupalGetToken.
+    $this->session_id = $session_id;
+    $token = $this->drupalGetToken('views_data_export/1');
+    $this->drupalGet($path, array('query' => array('eid' => 1, 'download' => 1, 'token' => $token)));
+    $output = $this->drupalGetContent();
+    $this->assertEqual($this->normaliseString($output), $expected, 'Re-download of export file by original user is possible with session token.');
+
+    // Assert that we cannot re-download directly without supplying the token.
     // We rely on this being the first export in this test class.
     $this->drupalGet($path, array('query' => array('eid' => 1, 'download' => 1)));
     $output = $this->drupalGetContent();
-    $this->assertEqual($this->normaliseString($output), $this->normaliseString($expected), 'Re-download of export file is possible.');
+    $this->assertEqual($this->normaliseString($output), '', 'Re-download of export file by original user is not possible.');
 
     // Assert that someone else can't download our file.
     // We rely on this being the first export in this test class.
     $this->drupalLogin($this->admin_user2);
-    $this->drupalGet($path, array('query' => array('eid' => 1, 'download' => 1)));
-    $this->assertResponse(403, 'Re-download of export file by another user is not possible.');
+    $this->drupalGet($path, array('query' => array('eid' => 1, 'download' => 1, 'token' => $token)));
+    $output = $this->drupalGetContent();
+    $this->assertEqual($this->normaliseString($output), '', 'Re-download of export file by different user is not possible.');
+  }
+
+  /**
+   * Overrides DrupalWebTestCase::drupalGetToken() to support the hash salt.
+   *
+   * @todo Remove when http://drupal.org/node/1555862 is fixed in core.
+   */
+  protected function drupalGetToken($value = '') {
+    $private_key = drupal_get_private_key();
+    return drupal_hmac_base64($value, $this->session_id . $private_key . drupal_get_hash_salt());
   }
 
   /**
diff --git a/views_data_export.module b/views_data_export.module
index 1861b14..92d9bf2 100644
--- a/views_data_export.module
+++ b/views_data_export.module
@@ -31,36 +31,6 @@ function views_data_export_views_api() {
 }
 
 /**
- * Implements hook_file_download().
- */
-function views_data_export_file_download($uri) {
-  if (views_data_export_is_export_file($uri)) {
-    $result = -1;
-    // Allow only owners to access export files.
-    $file = current(entity_load('file', FALSE, array('uri' => $uri)));
-    if ($file && $file->uid == $GLOBALS['user']->uid) {
-      // This is only necessary for file_download_headers() to return a result
-      // evaluating to TRUE, in case no other module added any header.
-      $result = array('X-Drupal-ViewsDataExport' => 1);
-    }
-    return $result;
-  }
-}
-
-/**
- * Checks whether the passed URI identifies an export file.
- *
- * @param string $uri
- *   A file URI.
- *
- * @return bool
- *   TRUE if the URI identifies an export file, FALSE otherwise.
- */
-function views_data_export_is_export_file($uri) {
-  return file_uri_scheme($uri) == 'temporary' && strpos(file_uri_target($uri), 'views_data_export') === 0;
-}
-
-/**
  * Implementation of hook_theme().
  */
 function views_data_export_theme() {
@@ -305,7 +275,7 @@ function views_data_export_view_clear($export_id) {
 function views_data_export_file_presave($file) {
   // Ensure temporary files really are temporary.
   // @see: https://drupal.org/node/2198399
-  if (views_data_export_is_export_file($file->uri)) {
+  if (strpos($file->filename, 'views_data_export') === 0) {
     // There is no FILE_STATUS_TEMPORARY.
     $file->status = 0;
   }
