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

Ligonsker's avatar

How does img tag work if there is no defined route in web.php?

This is related to my other post, becuase I wanted to restrict access to users from viewing images via the img tag.

Isn't img src using GET request? Then how does the img tag work if there is no route defined for the source url?

For example, I have images in /mysite/uploads/image.jpg

But there is no GET route for 'uploads' in web.php, yet everyone can still copy paste the URL in the browser and view the image.

I just wanted to restrict users access to view images by using policy or gates or other form of authorization on the GET route of the image URL but Laravel just allow access without anything already

0 likes
23 replies
Tray2's avatar

All those gates and authentications you are talking about is server side, so you should habdle it in your blade files, that is where you should prevent the image tag from being rendered.

That means that you don't have to do anything client side, unless of course you want to use JavaScript to display images.

document.querySelector('#update-image').src = '<path to image>';

Then you still need to handle any authorization server side.

2 likes
Ligonsker's avatar

@Tray2 Yes, I want to use AJAX or Fetch API to get a list of URLs then append img's to the page.

but still, how am I able to access the URLs if they aren't defined in web.php? Maybe it has something to do with the fact that I created a symbolic link from app/storage/ to app/public/storage so maybe it also create some route behind the scenes to this symlink url?

Ligonsker's avatar

@Tray2 but can't I use Laravel's routing to block access to that route before the file name so that anything after is blocked even files, something like:

https://yoursite.com/images/{block_all}

Or that's not possible with files?

Tray2's avatar

@Ligonsker As long as you store the images in public no.

You can of course create a route for viewing images.

2 likes
Ligonsker's avatar

@Tray2 yes that's what I currently have, but I'm stuck as to how to "restrict" access to users from viewing other users images

Because now that I put symlink to public anyone can access it, but I want only the user who uploaded the photos to be able to view his photos (or those who he gave permissions to view these images)

From my previous post: https://laracasts.com/discuss/channels/code-review/how-to-restrict-access-to-folders-after-creating-symlink-to-storage

Although I think it's not the role of PHP/Laravel, maybe the server (nginx) in this case?

Tray2's avatar

@Ligonsker All restrictions should be made in the application.

In your image table you add a user_id, that way you know who uploaded it. Then you filter on that, so only images belonging to the authenticated user is fetched from the database.

Now you can use uuid for the filename, that way no one can guess the filenames.

Then you set the visibility on the file.

https://laravel.com/docs/9.x/filesystem#file-visibility

2 likes
Ligonsker's avatar

@tray2 @newbie360 thank you guys. So from what I understand, there is no realistic way to block access to the file path as I thought initially (to restrict access on the full path to the file).

And since it must be public in order to display in the browser, anyone who gets the link will have an option to view the file.

What I can do then is to make it hard to guess the file names as @tray2 said, maybe using UUID.

Although - Laravel uploads files with Laravel's Str::random(40); which uses PHP's random_bytes function. Should I change it to UUID then?

Then, all files will be publicly accessible but people are going to have to guess the exact file names which is hard in case we're talking about UUID/long random strings.

But for me it still feels weird that all the files are publicly accessible, but I guess that I have to put higher emphasis on protecting the server itself? So that someone won't be able to access the uploads/ folder itself, but only specific files path?

For example:

Block access to GET requests on mysite/uploads/ but then if someone has the file path mysite/uploads/<very_long_unpredictable_string> then it does the job I'm looking for?

newbie360's avatar

@Ligonsker with hashed string still can't prevent user share the link to other peoples

i remember someone say can use permission in S3 disk

Ligonsker's avatar

@MichalOravec but in this case it means the images are placed in the public folder (or symlink to it) and then it means I have to put publicly place all the uploaded images of all users in the public folder (or symlink to it)

But also I use ajax to get the images

jlrdw's avatar

@Ligonsker you don't use the symlink for the folders that have private files. I serve them with readfile() but there are other techniques.

Edit:

Just one example, as there are many ways to accomplish this:

In ImageController

    public function displayImage()
    {
        $basedir = "/stohere/uploads";   // A folder that has nothing to do with web
        $imagedir = Request::input('dir');
        $image = Request::input('img');
       
       // Implement your auth here how you choose

        $file = $basedir . '/' . $imagedir . '/' . $image;

        header('Content-Type: image/jpeg');
        ob_clean();
        readfile($file);
        exit(0);
    }

**View

<img src="<?php echo 'http://localhost/laravel859/image?dir=imgdogs&img=' .  $mydog; ?>" alt="" class="image">

But just a simplified example. Change / adapt as needed.

2 likes
Ligonsker's avatar

@jlrdw But I need it for the <img> src, can it be done via readfile is as well?

Ligonsker's avatar

@jlrdw thank you, I just tested using the response as shown in the docs, and it showed me the image from storage/uploads/ path, but the image src shows as mysite.dev, so no real path. Now how am I supposed to use that response if I want to use AJAX to fetch let's say 100 images at once? It works when I simply write the route to the Controller method:

// web.php
Route::get('show_image', [Controller::class, 'show_image']);

and

// Controller.php
public function show_image(Request $request)
{
    $pathToFile = '../storage/uploads/460s6DLmCnvoom90S7wfk.jpg';
    return response()->file($pathToFile);
}

then when I go to in the browser to the url mysite.dev/show_image it displays me the image with src as mysite.dev/show_image.

But how can I use this response to actually display it with AJAX, not in blade? And for multiple files, not one like here?

jlrdw's avatar

@Ligonsker you really need to take some of the free lessons from here, there is quite a few on JavaScript.

Just my suggestion but I would not want to pull 100 images at a time, maybe 10 at a time paginated.

And free video as an example is:

https://laracasts.com/series/javascript-techniques-for-server-side-developers/episodes/1

When using Ajax and JavaScript I frequently use a server fetched partial to display things like a lookup table, but images in a table also work, in fact any type of page.

I use an object instead of an iframe normally.

But just my opinion you need to take a couple to 6 months and really dig in some tutorials and video lessons and learn, and practice programming along the way.

Edit:

In other words you do not have to mess around with JavaScript to display images, yet you are using JavaScript for a server partial. It is literally that easy.

2 likes
Ligonsker's avatar

@jlrdw I only show like 20 images at a time and only low quality thumbnails which load relatively fast.

I don't use pagination, instead I'm using the Oberserver Intersection API and I made an infinite scroll

That's why I need a way to fetch multiple images with the Fetch API, to get 20 more images at a time.

In my testing it was simple, I had an array of image sources from some stock images api:

const images = [
  {
    id: '1',
    src: 'https://image_api/path_to_some_image1'
  },
    id: '2',
    src: 'https://image_api/path_to_some_image2'
  },
   // more images paths
    id: '20',
    src: 'https://image_api/path_to_some_image20'
  }
];

But now I need to find a way to somehow replace the above hard coded public images sources array with images I fetch from storage/uploads/.

I just now wonder if I can use response()->file($pathToFile); to get multiple files (like 20) and also save them in an array or something similar to the structure above to later append to my photo list <div>

jlrdw's avatar

@Ligonsker I suggest stay away from infinite scroll. It seems great at first until the user wants to goto (page up) a previous image. But just my suggestion.

But if using infinite scroll make sure to use server side pagination so you only have so many in memory.

Meaning don't keep getting 20 more until suddenly there are a thousand images loaded into memory.

But now I need to find a way to somehow replace the above hard coded public images sources array with images I fetch from storage/uploads/

Again I suggest not having private images in storage unless you implement good authorization for those folders. If anyone can view them, then it doesn't matter.

Edit:

When I suggested tutorials, I meant core concepts, i.e., HTML, CSS, Javascript basics, DOM manipulation, etc. Not the use of a package.

2 likes
Ligonsker's avatar

@jlrdw thanks. I plan to add a vertical bar on the side with dates then the user can jump to a date then the fetching will start from that date (I still need to figure then how to fill the gap above it, but I assume I'll create empty grid items with the number of items that are above this date but this is less important as I'm doing it to learn the Intersection Observer API)

Regarding server side pagination, I'm not sure if that what you meant but the controller should only fetch 20 at a time, so if I jump to a later date I still fetch 20 starting from the desired date.

I suggest not having private images in storage unless you implement good authorization for those folders. If anyone can view them, then it doesn't matter.

But it's not that anyone can view them, only the user who uploaded his files can view them. Or users he gave permissions to. These are private photos that the user keep. I will (try to) Implement good authorization.

I just tested with private Google Drive photos as well - I found the image url in devtools and it's actually showing a source to some Google CDN but with very long hashed string path which means... Google drive photos are publicly stored but with difficult to guess hash? Or they're doing something else? I'm just confused because it means private Google drive photos are accessible to anyone as long as they have the path so they're not blocking the path as I thought would happen. So they store everything in some public folder and rely on having impossible to guess paths?

Ligonsker's avatar

@tray2 @newbie360 @michaloravec @jlrdw I found a solution to protect images and serve the images only to users who actually have access to the page and block users from directly going to the image URL in the browser! (Using nginx, not sure how it's with other servers)

I copy this answer from my other post (Because those were 2 similar posts that lead to the same issue):

I am using nginx X-Accel-Redirect header. What I did was to add a location block in my nginx config with internal directive:

location /uploads{
  internal;
}

Now when I go directly to mysite.test/uploads/image.jpeg (In the browser) I get 404 response from nginx

So in order to view the image, what I do is, I set the path to a Laravel controller in the <img> element containing some identifier to get the image path from the DB, for example:

<img src="mysite.test/image_id?id=1">

Then web.php redirects to the controller:

Route::get('image_id', [Controller::class, 'get_image']);

Then the Controller returns an empty response with X-Accel-Redirect header:

    public function get_image(Request $request)
    {
		$image = Image::find($request->id);
        $image_path = $image->path;
        return response('')->header('X-Accel-Redirect', $image_path);   
    }

And then this header tells nginx that I can access this image and it shows up!

I can even set alias for the uploads folder so it won't be the same as the actual path to further obscure it. It's quite cool.

So the entire flow now is:

  1. User go to /mysite.test/images/
  2. The Laravel Backend returns list of images to show and the frontend appends them as list of <img>s
  3. Each <img> element in turn sends a GET request to Laravel backend again and gets a response with X-Accel-Redirect and show the image!
Ligonsker's avatar

@newbie360 YES! it's a bit different than mine because I'm using <img> tags but close enough. I tested it today to see if it works and it does, thank you!

Please or to participate in this conversation.