Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ahmedali32's avatar

Restrict file download from storage directory

Hey i'am saving my videos in the storage directory and i want to display them but disallow users viewing video from downloading it any suggestions please ?

0 likes
16 replies
phildawson's avatar

Well anything online is available to download.

You can deter the 95% by adding <body oncontextmenu="return false;">

You can also append a one time session to the video link which the proxy checks. Just like form csrfs.

/video/view/123/qNyIuxhYhdnjuchR2ZteOYXxIBTnAtHn0zBS4in6/
bobbybouwmann's avatar

YouTube is doing the same thing like phildawson mentioned. However if you take a look at Facebook, they use the standard html5 video element and you can still download the video. So keep in mind that if you can access the video by url and the url is not unique every time, your videos won't be safe

ahmedali32's avatar

@phildawson @bobbybouwmann thanks for replay but about the url you talk about so i 'am limiting access to the user watching now if some one come it will be closed for him which means i can not make two users watch in same time and i do not know hoe to use this /video/view/123/qNyIuxhYhdnjuchR2ZteOYXxIBTnAtHn0zBS4in6/ how to make it and thank's very much .

bobbybouwmann's avatar

You generate a token or a random string for each user and then match that string to a video. Every time you get a new request you create a new token and return the video based on that token for the user.

ahmedali32's avatar

but i want here to restrict download the video is opened in new tab with file-content video/mp4 so now i cannot add any thing rather than check if he is authenticated user if so open the video if not do not open it so i can not control or control download but i think you mean control download by token right does i understand you correctly ?

Bloomanity's avatar

You can't control the download.

The browser has to download the video into a temp folder before it's able to play it.

phildawson's avatar

@saddsa In the video controller you have two methods one for displaying the page the other for proxying the video from your storage directory. The page sets a session with a random string and the method that proxies the video then checks to make sure it matches. If it doesn't match then you know they have not accessed the video file from the page within your site.

Note This only prevents sharing/directly embedding the url to the video file. They can still save and reshare/host it.

ahmedali32's avatar

but here let's imagine a real senario i'am authenticated user and i want to access a file and the file is opened and i want will open inspect element or f12 or developer tools and see the link http://localhost:8000/11223456847/28/ and i take it and put it in a new tab so now i can right click it and download it so ( Authenticated mean have a session ) "The page sets a session with a random string " so he have the session he can open it in another tab ?????

ohffs's avatar

I think if you solve this problem in general, you will be very rich ;-) You're getting into EME territory :-)

spoon's avatar

@ohffs is right.

As long as you aren't Netflix or something, you don't even need to worry about that.

bobbybouwmann's avatar

@saddsa Did you ever copied someone session? That doesn't work you know! And as long as you are logged in on the current window (might be multiple tabs) you can access anything because you still will be logged in. If you would open up an incognito window and paste the url that, you shouldn't be authenticated anymore and you won't be able to see the file! So make sure you protect the route to the file with the auth middleware

michaeldyrynda's avatar

If you use timed - or even single use - tokens to reference a video, by the time somebody tries to open the video in a new tab, the token will have expired. Only your app will know how to generate a new valid token for that user. Of course, you can minimise the chances somebody will download your videos, but if they want them desperately enough, they'll get them. Even if that means screen recording the video while it plays.

1 like
jekinney's avatar

You are getting in the area of DRM. Be careful as now in some countries you must declare your intentions now (ever see the pop up for cookies etc).

Any case, all you can do is minimize the risk but not remove it completely. Look up laravel on YouTube, Jeffrey's videos are everywhere. If there is a will someone will get the data. Why do we hash passwords? Try not to use database ids? To minimize risk but it can't eliminate someone getting your database.

Snapey's avatar

What if you never showed an ID for the video and instead used session variables to hold the ID of the video to be displayed?

You could perhaps delete the session variable as soon as its used to that if the user duplicates the tab they go back to an index page?

Every video would be loaded via /protectedvideo/show and only work once without the user going back to some form of index

Jonjie's avatar

Hi @Snapey . I think that's a nice idea. But, can you give us an example? Thanks

otokapanadze's avatar

Hey guys, I was digging in related topics, to solve this issue for the client, and in the end, I could not find the solution I wanted so I came up with a simple hack myself, which worked for me and might work for you too.

so first you need to have files in a private directory not public and they need to be accessed with the controller, and you need to have this, protecting files: strpos(url()->previous(), 'whatever URL you want'), this way files can only be accessed coming from URL you want.

this is what controller function looks like:

public function fileStorageServe($file) { $request = Request::capture();

    $token = $request->header('token');

    if (strpos(url()->previous(), 'previous url you want')) {

        $filePath = '/videos/' . $file;

        // we check for the existing of the file
        if (!\Storage::disk('local')->exists($filePath)){ // note 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 one (referenced with the helper storage_path('app')
            return '404'; // we redirect to 404 page if it doesn't exist
        }
        //file exist let serve it

        // if there is parameters [you can change the files, depending on them. ex serving different content to different regions, or to mobile and desktop …etc] // repetitive things can be handled through helpers [make helpers]

        return response()->file(storage_path('app'.DIRECTORY_SEPARATOR.($filePath))); // the response()->file() will add the necessary headers in our place (no headers are needed to be provided for images (it's done automatically) expected hearder is of form => ['Content-Type' => 'image/png'];

        // big note here don't use Storage::url() // it's not working correctly.
    }

    return 'Error';
}

and this is the route:

Route::get('/storage/videos/{filePath}', 'VideoController@fileStorageServe') ->where(['filePath' => '.*']) ->middleware('auth')

hope this helps.

1 like

Please or to participate in this conversation.