I have created my own module with custom block to display private downloads to authenticated users. The downloads files are provided by 3rd-party system and stored in private folder of Drupal downloads/private/. Drupal should limit downloads to authenticated user to specific folder e.g. userA sees only downloads folder /download/private/userA. Important side note: Solution must work with shared hosting provider enviroment.

Currently I have tried few versions download link creation:

$Relative => /download/private/userA/foo.txt => 403
'private://' . $Relative => http://localhost/en/system/files/download/private/userA/foo.txt => 404
'public://' . $Relative => http://localhost/sites/default/files/download/private/userA/foo.txt => 403
Url::fromUri(file_create_url($Relative))->getUri() => http://localhost/download/private/userA/foo.txt => 403

403 responses are delivered from Apache and Drupal is not involved.

My code looks like this:

class DownloadBlock extends BlockBase {

    private build() {
        // Check user has permission + lookup download folder

        $Path = 'C:/xampp/htdocs/drupal/download/private/userA/'
        $aFiles = file_scan_directory(, /.+/, ['recurse' => FALSE]);

        $aLinks = [];
        foreach ($aFiles as $File) {
            // eg download/private/userA/foo.txt
            $Relative = makeRelativeLink($File->uri);
            $link = file_create_url('private://' . $Relative);
            // value $link => http://localhost/en/system/files/download/private/userA/foo.txt
            array_push($aLinks, $link);
        return $aLinks;

I am aware that I need links which causes Drupal to handle the download and hits my custom download hook in myplugin.module:

function myplugin_file_download($uri) {
  \Drupal::logger("download")->debug("Hook download was triggered " .  $uri);
  // Check current user + permission
  // return headers or permission denied
  return [];

Configuration: Drupal - Private file system path is downloads/private/ Default download method: Private local files served by Drupal. Apache blocks all attemps to download a file with plain links. Content downloads/private/.htaccess:

# Deny all requests from Apache 2.4+.
<IfModule mod_authz_core.c>
  Require all denied

# Deny all requests from Apache 2.0-2.2.
<IfModule !mod_authz_core.c>
  Deny from all
# Turn off all options we don't need.
Options -Indexes -ExecCGI -Includes -MultiViews

# Set the catch-all handler to prevent scripts from being executed.
SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006
<Files *>
  # Override the handler again if we're run later in the evaluation list.
  SetHandler Drupal_Security_Do_Not_Remove_See_SA_2013_003

# If we know how to do it safely, disable the PHP engine entirely.
<IfModule mod_php5.c>
  php_flag engine off

I currently developing with XAMPP and Drupal 8.0.5 on Windows Box. I'm out of ideas. My google foo exhausted. Please, somebody point me to missing pieces for my puzzel. Thanks!

  • If it's hitting /en/system/files then that's going through Drupal, otherwise it goes through Apache directly. Which is it? – mradcliffe Apr 20 '16 at 12:52
  • @mradcliffe You are right. Prefixing the relative url with private:// produces /en/system/files and this link is handled by drupal. The problem now hook_file_download($uri) is not called at all. Any ideas? – chrisb Apr 21 '16 at 14:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.