Downloading with user token

Hello, I am creating a LMS that lets the user to download file. The user need the file should send me his token and after validating I let him download whatever he needs.
Now how can I stop user from downloading directly? because files are uploaded by admin in ftp, and ftp path is in my server

I don’t know where I can start?


You can start by storing your files in your application in the assets folder then add authorization that only one type of user can download unlimited that file(ex: admin can download but user can’t).

If this scenario doesn’t work, then you can create a field in the user tables called plan. When a user pays you update the plan to paid. Then create a rule that only users with a paid plan can download files.

If you want to be even more specific then you will have to create another table with all the files structure like this:

  • id
  • file_name

Then create a join between the user tables with the files tables and verify each user to what file they belong

Also you will have to setup a payment system paypal stripe etc.

Also this link may provide some help Phoenix file download

1 Like

I would “proxy” the download through phoenix…

user buys product - you persist purchase in DB
user hits /download_file/#{file_id}
phoenix verifies the login/session and purchase and then reads the file (or streams it) and sends it to the user

if you have multiple files just show a list of files and then the user can download on /download_file/#{file_id}

that way the user never gets a direct link and if the user shares the link /download_file/#{file_id} - the “stranger” clicking it will fail on having a session/logged in - and will fail on the purchase… so it doesnt work…

1 Like

I think I need this lines of code. this is the place I can start.

conn |> put_resp_header("content-disposition", ~s(attachment; filename="#{filename}")) |> send_file(200, filename)

would you mind explaining me more pleas? the stream file can be attractive, I haven’t used it before (streams)

To protect files in the past (and will be likely how I will do it in an upcoming Phoenix app) would be similar to how I do it with other tech stacks. I let nginx deal with it for the most part, while still doing authentication / authorization within Phoenix.

I’d keep the protected files outside of the main static assets folder.

Then have an “internal” location block in my nginx config like this:

  location /supersecrets/download/ {
    alias /supersecrets/;

Then for the download route at the Phoenix level, I’d authorize the download with a token and before I send the response, I’d set the Content-Disposition, X-Accel-Redirect and Content-Type headers which basically tells Phoenix to let nginx serve the file.

Here’s what that looks like in Python:

    response.headers['Content-Disposition'] = "attachment;"
    response.headers['X-Accel-Redirect'] = '/supersecrets/download/'
    response.headers['Content-Type'] = 'application/octet-stream'

Of course you can change to whatever file you’re serving.

I used this exact strategy like 5 years ago to serve a bunch of protected content and it held up very well.


Hello, Thanks
I use this code:

  def download(conn, _params) do
    file =!("/Users/test/name.png")

     |> put_resp_content_type("image/png")
     |> put_resp_header("Content-disposition","attachment; filename=\"test.png\"")
     |> put_resp_header("X-Accel-Redirect", "/tempfile/download/test.png")
     |> put_resp_header("Content-Type", "application/octet-stream")
     |> send_resp(200, file)


I have a question , how can use! instead of!. I want to create chunks what I need and send to my user

ASfter a google search found this

Sample code from the first link:

iex> stream =!("haiku.txt")
  line_or_bytes: :line,
  modes: [:raw, :read_ahead, :binary],
  path: "haiku.txt",
  raw: true
1 Like

I saw these link before and test, but I had a error when I wanted to send file

file = stream =!("test.png")

     |> put_resp_content_type("image/png")
     |> put_resp_header("Content-disposition","attachment; filename=\"test.png\"")
     |> put_resp_header("X-Accel-Redirect", "/tempfile/download/test.png")
     |> put_resp_header("Content-Type", "application/octet-stream")
     |> send_resp(200, file)

send_resp has this error

no function clause matching in Plug.Conn.resp/3

if I change |> send_resp(200, file.path) it has no error but I couldn’t to create my chunk size and send to my user, and is it my file streams?

what about chunk?

I tested this code:!("haiku.txt") |> Enum.with_index |> Enum.each(fn {content, _index} ->

but I don’t know how can I change this line |> send_resp(200, file) that sends stream file to user download manager

Do you have a controller? or are you just trying to use static files?

Also here is a complete discussion on this

Try the


Seems like is the right call here. I see that this was tried earlier, what about it didn’t work?

I just have static files like png , mp4 and pdf format, I don’t know why Phoenix has no good doc for it, just can find this link

it can’t help me what to do!

I tested, but I can’t use it. I tried to find sample code :frowning:

please see this cod:

  def download(conn, _params) do
    file =!("/Users/shahryar/Desktop/Khat-Ghalam.png", [], 204800)

    file |> Enum.with_index |> Enum.each(fn {content, _index} ->
       |> put_resp_content_type("image/png")
       |> put_resp_header("Content-disposition","attachment; filename=\"test.png\"")
       |> put_resp_header("X-Accel-Redirect", "/tempfile/download/test.png")
       |> put_resp_header("Content-Type", "application/octet-stream")
       |> send_resp(200, content)
       # |> send_chunked(200)


it works and the png file is healthy, but I don’t know Is it streams? I couldn’t use send_chunked(200)

HTTP/1.1 200 OK

Content-Type: image/png; charset=utf-8
Content-Disposition: attachment; filename="test.png"
x-xss-protection: 1; mode=block
Server: Cowboy
x-download-options: noopen
x-permitted-cross-domain-policies: none
Cache-Control: max-age=0, private, must-revalidate
cross-origin-window-policy: deny
Date: Fri, 05 Jul 2019 13:45:41 GMT
Content-Length: 10097
x-request-id: Fa6Gzp-CHY1D3jIAAHID
X-Accel-Redirect: /tempfile/download/test.png
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN

it works but I have this error in my terminal:

[error] #PID<0.2222.0> running KhatoghalamWeb.Endpoint (connection #PID<0.2220.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /download
** (exit) an exception was raised:
    ** (RuntimeError) expected action/2 to return a Plug.Conn, all plugs must receive a connection (conn) and return a connection, got: :ok
        (khatoghalam) lib/khatoghalam_web/controllers/client_home_controller.ex:1: KhatoghalamWeb.ClientHomeController.phoenix_controller_pipeline/2
        (phoenix) lib/phoenix/router.ex:280: Phoenix.Router.__call__/2
        (khatoghalam) lib/khatoghalam_web/endpoint.ex:1: KhatoghalamWeb.Endpoint.plug_builder_call/2
        (khatoghalam) lib/plug/debugger.ex:122: KhatoghalamWeb.Endpoint."call (overridable 3)"/2
        (khatoghalam) lib/khatoghalam_web/endpoint.ex:1:
        (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:33: Phoenix.Endpoint.Cowboy2Handler.init/2
        (cowboy) /Applications/MAMP/htdocs/elixir-ex-source/khatoghalam/khatoghalam/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
        (cowboy) /Applications/MAMP/htdocs/elixir-ex-source/khatoghalam/khatoghalam/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
        (cowboy) /Applications/MAMP/htdocs/elixir-ex-source/khatoghalam/khatoghalam/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Here you can find a complete example of a working streaming app Very high memory usage when streaming file with Phoenix

1 Like

this is not chunk file, it loads a file with url and load it and uses a module named HttpStream, I saw this repo they didn’t chunk file to what size they need like custom size

I sent file to google drive like this!(filepaths, [], chunk_size) 
                |> Enum.with_index 
                |> Enum.each(fn {content, _index} ->
                        # Process.sleep 2000
                        upload_content_to_google(flag, file_size, session_uri, content, chunk_size, email, file)

and sender:

stat = HTTPoison.put(
        "{session_uri}", # url
        {:file, file_path} , # body
            # header
            {"Content-Length", "#{rest_chunk_size}"},
            {"Content-Type", "application/tar"},
            {"Content-Range", range}
        [timeout: 50_000, recv_timeout: 50_000]

but that is upload. and that link you sent me it downloads on external link and runs with stream module , it doesn’t chunk file to many part with a size

maybe I can’t chunk my file to many parts and my user downloadsit , maybe the web server (ngnix) does it!!?

How about opening a new topic to attract more seasoned phoenix members with the following title:

How can I send a chunked file to a user?

Because at this moment i don’t know either.

1 Like