diff --git a/file_entity.field.inc b/file_entity.field.inc
index 43016ff..dd47830 100644
--- a/file_entity.field.inc
+++ b/file_entity.field.inc
@@ -15,6 +15,14 @@ function file_entity_field_formatter_info() {
     'field types' => array('file', 'image'),
     'settings' => array('file_view_mode' => 'default'),
   );
+  $info['file_download_link'] = array(
+    'label' => t('Download link'),
+    'description' => t('Displays a link that will force the browser to download the file.'),
+    'field types' => array('file', 'image'),
+    'settings' => array(
+      'text' => t('Download'),
+    ),
+  );
   return $info;
 }
 
@@ -35,6 +43,15 @@ function file_entity_field_formatter_settings_form($field, $instance, $view_mode
       // Never empty, so no #empty_option
     );
   }
+  elseif ($display['type'] = 'file_download_link') {
+    $element['text'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Link text'),
+      '#description' => t('This field support tokens.'),
+      '#default_value' => $settings['text'],
+      '#required' => TRUE,
+    );
+  }
 
   return $element;
 }
@@ -51,6 +68,9 @@ function file_entity_field_formatter_settings_summary($field, $instance, $view_m
     $view_mode_label = file_entity_view_mode_label($settings['file_view_mode'], t('Unknown'));
     $summary = t('View mode: %mode', array('%mode' => $view_mode_label));
   }
+  elseif ($display['type'] == 'file_download_link') {
+    $summary = t('Link text: %text', array('%text' => $settings['text']));
+  }
 
   return $summary;
 }
@@ -112,6 +132,23 @@ function file_entity_field_formatter_view($entity_type, $entity, $field, $instan
       }
     }
   }
+  elseif ($display['type'] == 'file_download_link') {
+    foreach ($items as $delta => $item) {
+      $file = (object) $item;
+      if (file_entity_access('download', $file)) {
+        $text = token_replace(t($settings['text']), array('file' => $file), array('clear' => TRUE));
+        $token = drupal_get_token("file/{$file->fid}/download");
+        $element[$delta] = array(
+          '#type' => 'link',
+          '#title' => $text,
+          '#href' => "file/{$file->fid}/download",
+          '#options' => array(
+            'query' => array('token' => $token),
+          ),
+        );
+      }
+    }
+  }
 
   return $element;
 }
diff --git a/file_entity.module b/file_entity.module
index ecb9b77..12acdf4 100644
--- a/file_entity.module
+++ b/file_entity.module
@@ -2178,3 +2178,14 @@ function file_entity_fnmatch($pattern, $string) {
   }
   return fnmatch($pattern, $string);
 }
+
+/**
+ * Return an URI for a file download.
+ */
+function file_entity_download_uri($file) {
+  $path = "file/{$file->fid}/download";
+  return array(
+    'path' => $path,
+    'options' => array('query' => array('token' => drupal_get_token($path))),
+  );
+}
diff --git a/file_entity.pages.inc b/file_entity.pages.inc
index f6776a9..c14febd 100644
--- a/file_entity.pages.inc
+++ b/file_entity.pages.inc
@@ -35,6 +35,11 @@ function file_entity_view_page($file) {
  * Menu callback; download a single file entity.
  */
 function file_entity_download_page($file) {
+  // Ensure there is a valid token to download this file.
+  if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], "file/$file->fid/download")) {
+    return MENU_ACCESS_DENIED;
+  }
+
   // If the file does not exist it can cause problems with file_transfer().
   if (!is_file($file->uri)) {
     return MENU_NOT_FOUND;
diff --git a/file_entity.tokens.inc b/file_entity.tokens.inc
index dbdd7fa..3012452 100644
--- a/file_entity.tokens.inc
+++ b/file_entity.tokens.inc
@@ -38,6 +38,11 @@ function file_entity_token_info() {
     'description' => t('The file type of the file.'),
     'type' => 'file-type',
   );
+  $info['tokens']['file']['download-url'] = array(
+    'name' => t('Download URL'),
+    'description' => t('The URL to download the file directly.'),
+    'type' => 'url',
+  );
 
   return $info;
 }
@@ -77,6 +82,11 @@ function file_entity_tokens($type, $tokens, array $data = array(), array $option
             $replacements[$original] = $sanitize ? check_plain($file_type->label) : $file_type->label;
           }
           break;
+
+        case 'download-url':
+          $uri = file_entity_download_uri($file);
+          $replacements[$original] = url($uri['path'], $uri['options'] + $url_options);
+          break;
       }
     }
 
@@ -84,6 +94,9 @@ function file_entity_tokens($type, $tokens, array $data = array(), array $option
     if (($file_type_tokens = token_find_with_prefix($tokens, 'type')) && $file_type = file_type_load($file->type)) {
       $replacements += token_generate('file-type', $file_type_tokens, array('file_type' => $file_type), $options);
     }
+    if ($download_url_tokens = token_find_with_prefix($tokens, 'download-url')) {
+      $replacements += token_generate('url', $download_url_tokens, file_entity_download_uri($file), $options);
+    }
   }
 
   // File type tokens.
diff --git a/tests/file_entity.test b/tests/file_entity.test
index dbbcde6..2435075 100644
--- a/tests/file_entity.test
+++ b/tests/file_entity.test
@@ -171,6 +171,16 @@ class FileEntityTestHelper extends DrupalWebTestCase {
 
     return $file;
   }
+
+  /**
+   * 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());
+  }
 }
 
 class FileEntityUnitTestCase extends FileEntityTestHelper {
@@ -1104,14 +1114,19 @@ class FileEntityAccessTestCase extends FileEntityTestHelper {
     $this->drupalGet("file/{$file->fid}/view");
     $this->assertResponse(200, 'Users with access can access the file view page');
 
+    $url = "file/{$file->fid}/download";
     $web_user = $this->drupalCreateUser(array());
     $this->drupalLogin($web_user);
-    $this->drupalGet("file/{$file->fid}/download");
+    $this->drupalGet($url, array('query' => array('token' => $this->drupalGetToken($url))));
     $this->assertResponse(403, 'Users without access can not download the file');
     $web_user = $this->drupalCreateUser(array('download any document files'));
     $this->drupalLogin($web_user);
-    $this->drupalGet("file/{$file->fid}/download");
+    $this->drupalGet($url, array('query' => array('token' => $this->drupalGetToken($url))));
     $this->assertResponse(200, 'Users with access can download the file');
+    $this->drupalGet($url, array('query' => array('token' => 'invalid-token')));
+    $this->assertResponse(403, 'Cannot download file with in invalid token.');
+    $this->drupalGet($url);
+    $this->assertResponse(403, 'Cannot download file without a token.');
 
     $web_user = $this->drupalCreateUser(array());
     $this->drupalLogin($web_user);
diff --git a/views/views_handler_field_file_link_download.inc b/views/views_handler_field_file_link_download.inc
index cd2fe46..088d18c 100644
--- a/views/views_handler_field_file_link_download.inc
+++ b/views/views_handler_field_file_link_download.inc
@@ -22,8 +22,9 @@ class views_handler_field_file_link_download extends views_handler_field_file_li
     }
 
     $this->options['alter']['make_link'] = TRUE;
-    $this->options['alter']['path'] = "file/$file->fid/download";
-    $this->options['alter']['query'] = drupal_get_destination();
+    $uri = file_entity_download_uri($file);
+    $this->options['alter']['path'] = $uri['path'];
+    $this->options['alter'] += $uri['options'];
 
     $text = !empty($this->options['text']) ? $this->options['text'] : t('Download');
     return $text;
