DEV Community

Cover image for Restricting access to certain files in our Laravel app
Kenny Horna
Kenny Horna

Posted on • Originally published at kennyhorna.com

Restricting access to certain files in our Laravel app

We generally want our app files to be public (profile pictures, product images, and so on). But what happens if we want to restrict the access to them to only logged-in users or users that have a certain role? Today you'll know how.

What we need to do

Before implementing it we have to understand the strategy. Basically, we'll do this:

  1. Define a new disk. This is for ease of reading (and writing) files
  2. Define the controller and method that will be responsible for returning the files
  3. Create the path to the elements of this new disk
  4. Securing our route

Simple right? Well, it is.

1. Defining a new disk

For this, head to the configuration file that handles it: config/filesystems.php. It defines the different disks that our app provides.

A "disk" is nothing more than a representation of a driver and location. You can have a disk that points to your app's local disk, AWS S3, or another different provider.

In our case we will create the 'files' disk. This will be a local disk that will point to the /storage/app/files directory. In that directory is where we will store our restricted files.



'disks' => [
    // ...
    'files' => [
        'driver' => 'local',
        'root' => storage_path('app/files'),
        'visibility' => 'private',
    ],
],


Enter fullscreen mode Exit fullscreen mode

Notice that we set the visibility as 'private'.

2. Defining our controller

We create our controller:



php artisan make: controller FilesController --invokable


Enter fullscreen mode Exit fullscreen mode

I opted for a single action controller since it will only have one method.

We head over to our newly created controller and implement our logic.



<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;

class FilesController extends Controller
{
    public function __invoke(Request $request, $path)
    {
        abort_if(
            ! Storage::disk('files') ->exists($path),
            404,
            "The file doesn't exist. Check the path."
        );

        return Storage::disk('files')->response($path);
    }
}


Enter fullscreen mode Exit fullscreen mode

Checking the code you'll see that we do 2 things: First, we check if the file exists. If it doesn't, we will abort the call by returning a 404 response. After this, we will only use the path that was sent in the request to return the file.

Notice that our method expects to receive the $path parameter.

3. Defining our route

We go to routes/web.php and add our new route. In my case I will give it the following form:



<?php

use App\Http\Controllers\FilesController;
use Illuminate\Support\Facades\Route;

// Other routes..

Route::get('/files/{path}', FilesController::class); // <--


Enter fullscreen mode Exit fullscreen mode

Now you understand where the $path variable that reaches the controller comes from. So, this route will capture requests such as: https://laravel8.test/files/my-file.jpg

4. Securing our route

Now we only have to secure the route. To accomplish it we have different options and that will depend on how you'll want to handle it in your app. In this example I will use the 'auth' middleware to do it. You could also use your ACL system to restrict access to your route or another alternative.



Route::get('/files/{path}', FilesController::class)->middleware('auth');
                                                   ^^^^^^^^^^^^^^^^^^^^


Enter fullscreen mode Exit fullscreen mode

As a test, I uploaded an image to the secure directory /storage/app/arsenal-goal.jpg. In addition, I installed Laravel Breeze and created a user to compare the requests with/without an authenticated user.

Result:

Alt Text

On the left, we have the case when trying to access as a logged in user. On the right, you can see that it redirects us to the login, since I tried to access from incognito mode without logging in.

Top comments (0)