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.
1public function __invoke(Request $request, Media $media, string $conversion = '') 2{ 3 if (!$request->hasValidSignature()) { 4 abort(404); 5 } 6 7 // Your authentication logic here 8 9 ob_get_clean();10 11 return response()->file($media->getPath($conversion), [12 'Content-Type' => $media->mime_type,13 ]);14}
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:
1Route::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.
1namespace App\Support\MediaLibrary; 2 3use Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator; 4 5class SignedMediaUrlGenerator extends DefaultUrlGenerator 6{ 7 8 public function getUrl(): string 9 {10 return \URL::signedRoute('media', [11 'media' => $this->media->id,12 'conversion' => $this->conversion?->getName(),13 ]);14 }15 16}
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.
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.