How to protect image from public view in Laravel 5?
Asked Answered
S

5

33

I have installed Laravel 5.0 and have made Authentication. Everything is working just fine.

My web site is only open for Authenticated members. The content inside is protected to Authenticated members only, but the images inside the site is not protected for public view.

Any one writes the image URL directly can see the image, even if the person is not logged in to the system.

http://www.somedomainname.net/images/users/userImage.jpg

My Question: is it possible to protect images (the above URL example) from public view, in other Word if a URL of the image send to any person, the individual must be member and login to be able to see the image.

Is that possible and how?

Stargell answered 6/6, 2015 at 11:42 Comment(3)
Have you tried setting up a route to protect the image path?Mathre
no, but I will try the follow answer and let you knowStargell
@haakym, it the example below worked for me. You are right it is in route.Stargell
L
40

It is possible to protect images from public view in Laravel 5.x folder.

  • Create images folder under storage folder (I have chosen storage folder because it has write permission already that I can use when I upload images to it) in Laravel like storage/app/images.

  • Move the images you want to protect from public folder to the new created images folder. You could also chose other location to create images folder but not inside the public folder, but with in Laravel folder structure but still a logical location example not inside controller folder. Next you need to create a route and image controller.

Create Route

Route::get('images/users/{user_id}/{slug}', [
     'as'         => 'images.show',
     'uses'       => 'ImagesController@show',
     'middleware' => 'auth',
]);

The route will forward all image request access to Authentication page if person is not logged in.

Create ImagesController

class ImagesController extends Controller {

    public function show($user_id, $slug)
    {
        $storagePath = storage_path('app/images/users/' . $user_id . '/' . $slug);
        return Image::make($storagePath)->response();
    }
}


EDIT (NOTE)

For those who use Laravel 5.2 and newer. Laravel introduces new and better way to serve files that has less overhead (This way does not regenerate the file as mentioned in the answer):

File Responses

The file method can be used to display a file, such as an image or PDF, directly in the user's browser instead of initiating a download. This method accepts the path to the file as its first argument and an array of headers as its second argument:

return response()->file($pathToFile);

return response()->file($pathToFile, $headers);

You can modify your storage path and file/folder structure as you wish to fit your requirement, this is just to demonstrate how I did it and how it works.

You can also added condition to show the images only for specific members in the controller.

It is also possible to hash the file name with file name, time stamp and other variables in addition.


Addition: some asked if this method can be used as alternative to public folder upload, YES it is possible but it is not recommended practice as explained in this answer. So the same method can be also used to upload images in storage path even if you do not intend to protect them, just follow the same process but remove 'middleware' => 'auth',. That way you won't give 777 permission in your public folder and still have a safe uploading environment. The same mentioned answer also explain how to use this method with out authentication in case some one would use it or giving alternative solution as well.

Loach answered 6/6, 2015 at 11:46 Comment(6)
not really recommended for normal image uploading without protection as the performance will be a lot worse than letting apache\nginx handling serving the imagesZelda
@AmirBar what is your alternative solution?Loach
for normal image upload that don't need to be protected then upload to the public folder and let nginx handle itZelda
@AmirBar This is only if you need this to protect your files and yes I agree normal upload will be on your public folder, but the last note is because some body ask for it in another comments from another question, I added the note.Loach
I'm assuming the Image::make method above is from the intervention/image package?Authoritative
@Dipen have you tried reading EDIT (NOTE) part? beside that I mention more in addition part, for that reason I have added link in addition to another answer option to solve this #36066644 and find evt. alternative way in this answer.Loach
B
22

In a previous project I protected the uploads by doing the following:

Created Storage Disk:

config/filesystems.php
'myDisk' => [
        'driver' => 'local',
        'root' => storage_path('app/uploads'),
        'url' => env('APP_URL') . '/storage',
        'visibility' => 'private',
    ],

This will upload the files to \storage\app\uploads\ which is not available to public viewing.

To save files on your controller:

Storage::disk('myDisk')->put('/ANY FOLDER NAME/' . $file, $data);

In order for users to view the files and to protect the uploads from unauthorized access. First check if the file exist on the disk:

public function returnFile($file)
{
    //This method will look for the file and get it from drive
    $path = storage_path('app/uploads/ANY FOLDER NAME/' . $file);
    try {
        $file = File::get($path);
        $type = File::mimeType($path);
        $response = Response::make($file, 200);
        $response->header("Content-Type", $type);
        return $response;
    } catch (FileNotFoundException $exception) {
        abort(404);
    }
}

Serve the file if the user have the right access:

 public function licenceFileShow($file)
{
    /**
     *Make sure the @param $file has a dot
     * Then check if the user has Admin Role. If true serve else
     */
    if (strpos($file, '.') !== false) {
        if (Auth::user()->hasAnyRole(['Admin'])) {
            /** Serve the file for the Admin*/
            return $this->returnFile($file);
        } else {
            /**Logic to check if the request is from file owner**/
            return $this->returnFile($file);
        }
    } else {
//Invalid file name given
        return redirect()->route('home');
    }
}

Finally on Web.php routes:

Route::get('uploads/user-files/{filename}', 'MiscController@licenceFileShow');
Brilliancy answered 7/7, 2019 at 19:18 Comment(0)
A
3

I haven't actually tried this but I found Nginx auth_request module that allows you to check the authentication from Laravel, but still send the file using Nginx.

It sends an internal request to given url and checks the http code for success (2xx) or failure (4xx) and on success, lets the user download the file.

Edit: Another option is something I've tried and it seemed to work fine. You can use X-Accel-Redirect -header to serve the file from Nginx. The request goes through PHP, but instead of sending the whole file through, it just sends the file location to Nginx which then serves it to the client.

Aguila answered 7/9, 2016 at 19:50 Comment(0)
A
0

if I am understanding you it's like !

 Route::post('/download/{id}', function(Request $request , $id){
  {

     if(\Auth::user()->id == $id) {
         return \Storage::download($request->f);
     } 
     else {
         \Session::flash('error' , 'Access  deny');
         return back();
     }
  }

 })->name('download')->middleware('auth:owner,admin,web');
Afton answered 11/11, 2019 at 19:7 Comment(0)
V
0

Every file inside the public folder is accessible in the browser. Anyone easily gets that file if they find out the file name and storage path.

So better option is to store the file outside the public folder eg: /storage/app/private

Now do following steps:

  1. create a route (eg: private/{file_name})

    Route::get('/private/{file_name}', [App\Http\Controllers\FileController::class, 'view'])->middleware(['auth'])->name('view.file');

  2. create a function in a controller that returns a file path. to create a controller run the command php artisan make:controller FileController

and paste the view function in FileController

public function view($file)
    {
        $filePath = "notes/{$file}";
        
        if(Storage::exists($filePath)){
            return Storage::response($filePath);
        }
        abort(404);
    }

then, paste use Illuminate\Support\Facades\Storage; in FileController for Storage

  1. And don't forget to assign middleware (in route or controller) as your requirement(eg: auth)

And now, only those who have access to that middleware can access that file through a route name called view.file

Vambrace answered 17/2, 2022 at 7:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.