Restricting access to certain files in our Laravel app
Tags: laravel, php, security
Published: 2021-05-07 01:54:39

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. 

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

> Notice that we set the visibility as `'private'`. 

### 2. Defining our controller

We create our controller:

```bash
php artisan make: controller FilesController --invokable
```

> I opted for a [single action controller](https://laravel.com/docs/master/controllers#single-action-controllers) since it will only have one method.

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

```php
<?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);
    }
}
```

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
<?php

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

// Other routes..

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

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.

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

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ecff8l1mxjwyxpcqfu1q.png)

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.