Statamic Tip: Protecting Documents from Unauthorized Access

We all know the saying: once you upload a file to the web, it's out there forever. By definition most files on the web are public - and the same is true about any assets on your Statamic site.

But your clients might not always want that, and instead want to show certain sensitive files only to users that they themselves have validated and authorized. Here's how you can build just that in a few minutes for your Statamic site.

After these few steps you'll have a protected asset container for your documents, which gives you full control as to who can access the files inside.

Creating a Protected Filesystem

As a first step we need to find a safe place to store our sensitive files. In Laravel/Statamic, the URL of a website usually points to the /public folder, which is also referred to as the web root.

If the server is setup correctly, a web user can only access files and folders inside the web root and nowhere else on the server. Normal Statamic assets are stored in /public/assets and are therefor accessible to the public.

In theory any folder outside of /public would be a valid place to store our files. But let's try to follow conventions and make things easier to find for the next developer, and use the folder /storage/app/protected.

To be able to store files here, we need to define a new filesystem, which is Laravel speak for a storage location. To do so, simply add the following to your disks array in the filesystem config:

/config/filesystems.php
'disks' => [ ... 'protected' => [ 'driver' => 'local', 'root' => storage_path('app/protected'), 'visibility' => 'private', ], ],
Defining our new protected filesystem in Laravel's config

This creates the new filesystem and lets us use it across the entire Laravel and Statamic app.

Setting up the new Asset Container

As a next step we need to set up Statamic so that users can upload files to the newly created filesystem. Under Assets you have the option to create a new asset container, which is Statamic's abstraction for file storage.

The settings are very straight forward. Simply give it a descriptive name (e.g. protected or internal) and then select our protected filesystem as Disk under File Driver.

There are a few more settings which don't affect the topic of this post, so you can set those to whatever works for your use case.

Here is how the YAML file of that new asset container looks:

/content/assets/protected.yaml
title: Protected disk: protected allow_uploads: true create_folders: false
The example settings of our new asset container

At this point you can set up your assets fields to use the protected asset container, so you and your users can add new files to it. But since the filesystem is not publicly accessible, you currently cannot output those files on your website.

Note: For the same reason it is also not possible to preview the files in the control panel.

Gaining Access via a File Controller

By default the web browser displays a file by simply requesting it via its public url. But since our files aren't public, there is no way to retrieve the file from our server. To change that, we need to add our own method of returning files when they are requested by a user.

To do so, we can type in php artisan make:controller ProtectedFilesController to have Laravel jump start us on creating a Controller class.

This class will receive a request for a specific file by name/path, check if the user is logged in, and either return the file to the authorized user or redirect the unauthorized user to the login page.

/app/Http/Controllers/ProtectedFilesController.php
<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\Auth; use Statamic\Facades\Asset; class ProtectedFilesController extends Controller { public function download($filename) { if (!Auth::check()) { return redirect('/login'); } else { $file = Asset::find('protected::' . $filename); $path = $file->resolvedPath(); $headers = [ 'Content-Type' => 'application/pdf', ]; return response()->download($path, $filename, $headers); } } }
The Controller handling our protected file requests

Note that for our application we only needed to enable download links for protected pdf files. Obviously this file would look a bit different if you need to support other file types as well.

Now that the controller is set up, we need a way for a user to call it up when requesting a file. To do so, we set up a new route that calls up the ProtectedFilesController and triggers the download() function defined above.

/routes/web.php
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\ProtectedFilesController; Route::get('/download/{filename}', [ProtectedFilesController::class, 'download']);
Adding a route to call our new controller method

Now that all the setup is done, we can easily link to our protected files in our Statamic templates. Instead of referencing the file path directly as we would with public files, we call our new route and simply supply the file path as an argument.

<a href="/download/{{ file.path }}">Download</a>
Linking to a protected file in an Antlers template file.

And that's it. This is a fairly simple implementation since the project didn't require more. For your application you might want to go a step further and not only check that a user is logged in, but also that they have the correct access rights to view the file.

More Posts