How can you hide certain static assets behind a 'require_authenticated_user' path?

What’s the correct way to serve certain assets to the client, only if that client is logged in as a user?

I’m new to web dev, and am using Phoenix & Elixir to learn serverside skills. As part of a small project, I’m trying to show a subset of images to the user, pulled from a larger fixed set of images. The particular subset requested by the client changes via some JavaScript & LiveView, each time a particular button is pressed.

So far, so good - I’ve been able to set that up. The total, fixed set of images all live in my priv folder, and the JS changes the url of the Image tags’ src attribute accordingly - thus, the required images are served as static files.

But, I’d like this feature, and any of this particular superset of images, to only be available if the user is logged in? I’ve added the standard auth features from phx.gen.auth and combined with LiveView, added the particular page this feature lives on to be scoped under the require_authenticated_user plug etc. But can’t seem to find how/if I can scope certain static file paths (or, rather, the entire folder of all these paths and subpaths) to be piped through the require_authenticated_user plug? I’ve looked online, but found little, and tried experimenting. Maybe I’m going about this wrong though, and it can’t be done?

My other option would be to store all these assets on my database, and have LiveView send them as data to the client, for the JS to display them as images? And it seems this would provide greater security, as what I’m ideally looking for is to hide the assets as much as possible from everything except the clientside JS, so that, say, even when logged in, the user couldn’t simply navigate to the URL of a particular image as an HTTP request, and then save that image.

But, I sensed that serving images like this via my postgres DB would be much more inefficient for several reasons (and so far, my reading has said that to be true) - but I’m a beginner, so can’t say for sure?

Is there a correct procedure for serving certain assets behind a layer of auth security?

Many thanks for all your time and help!

Plug.Static is a plug like any other. You can use forward within the phoenix router to route to a plain plug or use any other way to wrap that plug to require authentication for access.

1 Like

One thing to consider carefully: Plug.Static’s behavior for things like cache control will likely need tweaking, as the defaults are intended for files like assets that should be aggressively cached for a long time (the opposite of what you’d want).

Re: serving images as data and populating img tags with JS - most browsers offer a “Save Image As…” even for those images. You’ll need to consider exactly what user behaviors you’re trying to prevent.

1 Like

Thanks so much for the tip. The true purpose of the images is actually to populate an HTML canvas - I think that’s less able to be saveable, right?

However, whilst security for this project is an actual concern/goal, my main focus is actually to showcase the best practices - at least to my level of study - of each of the WebDev patterns that I’m trying to demo.

With the tweaking needed that you’re suggesting, it sounds to me that Plug.Static might not be the intended/proper way of hiding assets behind auth, after all? I’m happy to go with a different option if that is considered better, the Plug.Static method was just my idea.

Or, maybe there’s no set/best way of doing it?

Thanks for your feedback, really appreciate it.

Ah ok, thanks. That sounds promising, and I’ve been looking into it, since. Will report back when I’ve been able to give it a good shot.

Thanks for your feedback! Very much appreciate it.

Plug.Static does mostly three things:

  1. Locating the requested file in the filesystem
  2. Handling content type, range and other headers
  3. Calling Plug.Conn.send_file/3,4,5 to efficiently stream the data from the filesystem to the socket

So if you want to handle step 1 yourself (with added authentication and authorization) you can just write a controller that handles the necessary headers and uses send_file to have similar performance/efficiency as Plug.Static

Edit: it won’t be quite as efficient, presumably, as Plug.Static is usually called early in the Endpoint, while controller action pass through all the Endpoint and Router plugs; but you need many of those anyway if you’re going to do session-based auth