How to serve images and files privatly in Laravel 5.7

Mohamed Lamine Allal
4 min readNov 15, 2018

--

To have private files (images), you need to serve the files through a **route => middleware => controller** flow.

Know that by default, or by the setting of your project (case you are using multi — guard authentication) your auth middleware will handle authentication, and permission. If further authorization are needed you handle it in the controller (using whatever mean you want. If you want to implement roles and permission management [spatie/laravel-permission or Laratrust libraries are a good choice (I use spatie in my project, but know that laratrust is the only one that implement team feature out of the box )]).

Enough talking let’s get to work

We need to set first a route:

The router will receive the request, parse it, and pass it to the right controller with the right data. That’s is precised by our route.

But here we have two cases:

One route that handle all (like a wildcard):

We can have **one route that handle all** our files [i personally don’t prefer that].
We can do that using such a route (it’s like a wildcard).

Route::get(‘/storage/{filePath}’, ‘FileController@fileStorageServe’)->where([‘filePath’ => ‘.*’])

You can see how we precised, that the parameter can be anything (and that include the back slash). I like to use the term wildcard.

Here the filepath, is the relative path to the storage. When you store a file, the path you get, typically you store on the db. If you keep same structure (we can change but we need to do remapping), you can use this filePath parameter exactly as it is to get the path to the file in question. (we will see that).

You can name it too like that:

Route::get(‘/storage/{fileName}’, ‘FileController@fileStorageServe’)
->where([‘fileName’ => ‘.*’])->name(‘storage.gallery.file’);

Naming is useful when we create urls to the route, and so the resources.

Otherwise

A route for each type/category of files (the way i prefer)

(**advantage**: you will be able to control better the accessibility. (Each route and type of resources and it’s rules. If you want to achieve that with the wild card route (let me call it that) you need to have conditional blocks (if else, handling all different situations. It’s unnecessary operations [going directly to the right block, when the routes are separate is better, plus out of that, it allow you to organize better the permissions handling]).

Route::get(‘/storage/gallery/{file}’, ‘System\FileController@getGalleryImage’)
->name(‘storage.gallery.image’);

We had our routes set NOW, let make our Controller/Controllers

The wild card one

Know that disk()->exists() expect a relative path, from your disk root path. So in our example we pass directly the path.

(/…/laravelProject/storage/app) is the default local disk root, and it’s referenced with the helper storage_path(‘app’) .

ex: disk()->exists(‘my/something.jpg’) => it will check in /…/laravelProject/storage/app/my/somehing.jpg”

Note too, that we use response()->file() to send the file. That will form a correct http response for us. The body with all the necessary headers. It get it automatically from the meta data of the file. Know too if you have some special headers you can provide them in second parameter, as an associative array.

response()->file(file, [‘someheader’ => ‘value’, …..])

Know that you can add any kind of logic for permission and authorization handling. As i said a role & permission library may be something you want.

You need to apply your auth middleware and any other, for your controller. or At a route level (example for a complete group). It’s all the same.

class FileController extends Controller {     public function __construct() {           $this->middleware(‘auth:system’);     }

in route group

Route::group([‘prefix’ => ‘admin’, ‘middleware’ => [‘auth:system’]], function () {..})

And one last thing Storage::url() is not working as supposed to. Don’t use it, use storage_path(‘app’) instead.

The per route one

(big difference, is the parameter, now it reference just the file name, and not the relative path to storage disk root) [difference in implementation, but as i said this my preferred way, separate routes, better management].

Now you can check by forming the correct url (go to storage copy past the file name, and form your route. it should show you the image).

This way, the per route way, is more flexible, and sturdy. And perform better, when it come to handling multiple type/category of resources. Setting permission separately is a lot better. Plus it go route => correct controller directly. Which mean directly execute the necessary code. If you want to implement different permission rules, with the wildcard way. You need to put a lot of conditional code, which are unnecessary operations, that get to be executed each time.

One last thing left:

How to show that in view

The wild card one

<img src=”{{route(‘routeName’, [‘fileParam’ => $storageRelativePath])}}” />

Note that `routeName` here in the example above will be `storage.file`, and `fileParam` would be `filePath`. **$storageRelativePath** for example you get from the db (generally that’s what it will be).

The per route

<img src=”{{route(‘routeName’, [‘fileName’ => basename($storageRelativePath)])}}” />

Same but we provide only the filename.

Notes:
The best way to send such a response, is using **response()->file();**.
That what you will find in the 5.7 doc.
That performance wise against **Image::make($storagePath)->response();**. Unless you need to modify it in the fly.

Links to doc:

https://laravel.com/docs/5.7/filesystem#retrieving-files

You may like to check how to change the root path of your storage. Apply customization. Or completely create a custom disk.

https://laravel.com/docs/5.7/filesystem#configuration

https://laravel.com/docs/5.7/filesystem#custom-filesystems

That conclude it.

--

--

Mohamed Lamine Allal

Developper, Entrepreneur, CTO, Writer! A magic chaser! And A passionate thinker! Obsessed with Performance! And magic for life! And deep thinking!