Dec 21, 2020 · 3 minutes read

Securing media from laravel-medialibrary

While coding on a little project, I've encountered the following issue: I'm using Spatie's laravel-medialibrary package to store and associate files with my models. Since the images may contain private and confidential data, they shouldn't get accessed from outside. Small example: A user is logged...

While coding on a little project, I've encountered the following issue: I'm using Spatie's laravel-medialibrary package to store and associate files with my models. Since the images may contain private and confidential data, they shouldn't get accessed from outside. Small example: A user is logged in in my app and an image from medialibrary gets displayed. When he opens the image in a new tab, he sees the full url: https://example.com/customer-documents/1/page1.jpg. When he would try to modify this url and change the media url to 2 (customer-documents/2/page1.jpg) he would see the top secret document from another user.

And that isn't good.

So I've looked for a solution. I think that's the perfect use case for Laravels signed urls and a custom Url Generator for laravel-medialibrary.

So I've created a new SignedMediaController which should handle all the requests which are asking for an associated media.

public function __invoke(Request $request, Media $media, string $conversion = '')
{
  if (!$request->hasValidSignature()) {
	abort(404);
  }

  // Your authentication logic here
  
  ob_get_clean();

  return response()->file($media->getPath($conversion), [
	'Content-Type' => $media->mime_type,
  ]);
}

First it checks if the url has a valid signature, and blocks the request if not. Feel free to add your custom authentication logic here, maybe if the user is logged in, or the $media->model() relationship belongs to the users team.

Before returning the media, ob_get_clean cleans all the headers and other stuff which blocks it from working, and the media file with its original file type gets returned.

Because I work with multiple conversions, I've added an option to specify a conversion for the image.

I've registered the route in my routes/web.php file:

Route::get('/media/{media}/{conversion?}', SignedMediaController::class)->name('media');

As @devgummibeer said you could also use the signed middleware for the route, instead of checking the valid signature in controller.

Because I want to keep using the $media->getUrl(conversion) syntax, I've created a custom URL generator for Media Library.

It doesn't contain much logic, so I've extended the DefaultUrlGenerator from spaties package and only override my needed method.

namespace App\Support\MediaLibrary;

use Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator;

class SignedMediaUrlGenerator extends DefaultUrlGenerator
{

    public function getUrl(): string
    {
        return \URL::signedRoute('media', [
            'media' => $this->media->id,
            'conversion' => $this->conversion?->getName(),
        ]);
    }

}

And that's it! When executing $media->getUrl(?conversion) it returns a signed url, which points to my controller. This checks if the signature is valid and returns the data. When a user modified the url, he won't see the top secret data from my other users.

Did you enjoy what you’ve read?

Once in a while I send an email with some project updates, article drafts or other cool stuff. I won’t share your email and I won’t let your inbox overflow. Promised.