I have set up my Drupal 7 site for private files in a directory above root (../private). I also set my filefield on my node type to upload the image files to that private directory.

I have two user roles: man and woman. Both user roles can create nodes of a certain type, containing that image file field. Men can never see another man's photo; same thing for women. Men can always see a woman's photo; same thing for women.

Here are my current results:

When a man visits a woman's node, he can view the photo within the node content: http://example.com/members/76/gallery/public/photo-123

When that man views the page source and copies the image URL, he can view that image directly in his browser: http://example.com/system/files/styles/apply_simplecrop/private/pictures/1/Public/filename.jpg?itok=ABC123&sc=abcdefghijklmnop

If that man shares that direct link with another man, that other man can view that picture as well. If he shares the direct link with a woman or a non-logged-in user they are served an Access Denied message and cannot view that picture.

If a man tries to access that image URL without the "itok=" text in the link, he receives an Access Denied message.

My desired result in all of those examples above is to display an Access Denied message.

The ../private directory has an htaccess file containing Deny from all.

Am I missing a step? What can I do to securely deny direct access to those files in the way that I described above?


1 Answer 1


The issue is that the other man that gets the link should get an Access denied right? (The rest seems to work.) This is not how if works unfortunately. Private files are only private if the user is explicitly denied access to them (as happens to women and anonymous users). Since any man is allowed access to that file (he can view it under certain circumstances), the system will serve him that file.

Files are served from this part of system_menu():

$items['system/files'] = array(
  'title' => 'File download',
  'page callback' => 'file_download',
  'page arguments' => array('private'),
  'access callback' => TRUE,
  'type' => MENU_CALLBACK,

Then file_download():

function file_download() {
  // Merge remainder of arguments from GET['q'], into relative file path.
  $args = func_get_args();
  $scheme = array_shift($args);
  $target = implode('/', $args);
  $uri = $scheme . '://' . $target;
  if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) {
    $headers = file_download_headers($uri);
    if (count($headers)) {
      file_transfer($uri, $headers);
  else {

As you can see, Drupal serves an access denied in case there are no headers. So, you should make sure that file_download_headers() does not return any headers.

You can achieve this by implementing hook_file_download() in a custom module and have it return the value of -1 as described in it's documentation. This will however always deny the user access to the file. So within hook_file_download you will have to find some way to conditionally allow the user access to the file. It is entirely up to you how you want to implement this, but a suggestion is:

  • Lookup the file object, by loading it from the file_managed table by its path.
  • Lookup the node(s) that have this image attached.
  • Lookup if the current user visited the node within x minutes by querying the history table. If not, return -1.

This gives a general idea of how Drupal serves these files and what you can do to restrict access. If you run into any problems implementing this, feel free to open a new question and reference this one.

And on a sidenote, if you are trying to prevent user from leaking photo's. If I find a pretty girl, I can also save the image and email it to a friend. If you block that I send a screenshot. In these case he does not even need an account. People will always find a way to circumvent limitations. Is it worth the effort to block this?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.