Disable access to media files to unauthenticated users

Hello.

I would like my users to be able to upload files.
No one should be able to see those files unless they are logged in.

I am aware of Sending Files, but that would require a lot of extra code (helpers, etc)

It must be a way of intercept requests to (for example) webroot/private/ and response with a custom view instead if not logged in.

Is this possible? What would be the best practice?


Edit:
I’be been reading about this but have not seen any CakePHP specific.
I could create an .htaccess inside /webroot/private that redirects all trafic to a Controller method that check if user is logged and if so Send File. Is this OK?


Edit2:

I have tried several .htaccess configurations with no luck
webroot/.htaccess is overriding webroot/private/.htaccess no matter what I try:

Try1:

#webroot/.htaccess
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !^private
    RewriteRule ^ index.php [L]
</IfModule>

Try2:

#webroot/.htaccess
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_URI} !^private
    RewriteRule ^ index.php [L]
</IfModule>

Try3:

#webroot/.htaccess
<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteRule %{REQUEST_FILENAME} ^private - [NC,L]

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

Try4:

#webroot/.htaccess
<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteRule %{REQUEST_URI} ^private - [NC,L]

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

All those while also having:

#webroot/private/.htaccess
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^ /file/?request_url=%{REQUEST_URI} [L]
</IfModule>

Edit3:
Nevermind… I was using bin/cake server. That’s why .htaccess was not working.
The thing is that now I’m using Apache and a virtualHost and still can’t get this to work.

I need help :frowning_face:


Thanks!

What you could do is something like this in your .htaccess:

# webroot/private/.htaccess
deny from all

Then, make a seperate controller:

namespace App\Controller;

use App\Controller\AppController;
use Cake\Http\Exception\{
  ForbiddenException,
  NotFoundException
};

class PrivateFileController extends AppController {
  public function serve($file) {
    // Check if the user is logged in
    // ... Do your own required checks here
    if(!$this->Auth->user()) throw new ForbiddenException('Please login to access this download');

    // Build the filepath
    $fileName = 'myfile.pdf';
    $localPath = WWW_ROOT . DS . 'private' . DS . $fileName;

    // Make sure the file exists
   if(!\file_exists($localPath)) throw new NotFoundException('File not found');

    // Serve the file
    return $this->response
      ->withFile($localPath);
  }
}

and finally, add the required route:

$route->get(
  '/private/:file',
  ['controller' => 'PrivateFiles', 'action' => 'serve']
)->setPass(['file']);

note: Doing it this way is pretty inefficient

Thanks.
The following changes finally worked for me.
*See notes at the end

#/webroot/private/.htaccess
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^ /index.php [L]
</IfModule>
#/config/routes.php
$routes->scope('/', function (RouteBuilder $builder) {
    ...
    $builder->connect('/private/:path', ['controller' => 'Private', 'action' => 'serve'])->setPass(['path']);
    ...
});
#/src/Controller/PrivateController.php

class PrivateController extends AppController
{
    public function serve($path) {
        // NOT NEEDED: I already check if logged in AppController::beforeFilter
        //if(!$this->UserAuth->isLogged()) throw new ForbiddenException;

        $localPath = WWW_ROOT . DS . 'private' . DS . $path;

        // NOT NEEDED: see below
        // if(!file_exists($localPath)) throw new NotFoundException;

        //  This method already throws NotFoundException if file does not exist.
        return $this->response->withFile($localPath);
    }
}

Anyway I changed my mind.
The project I’m working on will have Proceedings associated with Users and Files. Hence I’ll make a FileHelper and FilesController will read file’s path from database and check it’s permissions per user. The folder won’t be inside webroot. Also, I won’t need custom routes. Maybe I’ll cache that table. Anyway I don’t care about speed while accesing private files.

1 Like