Web Security: File downloads behind Auth

Gaurav Gupta
5 min readJul 5, 2019

--

Photo by Caspar Camille Rubin on Unsplash

In our application we allow the users to download pdf/excel files. These files are private to the user and should not be available to unauthorized users. We kinda understood how to do it behind Auth, but still wanted to explore different options for downloading files using Javascript.

Below are some of the ways that Javascript file downloads work:

Create a link with appropriate href:
If you already have a link to the file which needs to be download, you can use it as the href in an anchor tag. When the user clicks on the link, the browser would try to open this file as a static asset, if the browser can preview it, it would open up a preview and allow the user to download if they want to.
(See here to understand how does the browser decide whether to preview or download a file).

<a href="fileurl"> Download </a>

Create a link with appropriate href AND download attribute:
You can force the browser to not open up a preview, but directly open the Save File dialog by setting the download attribute on the link. The value of download attribute is the suggested filename to save with. The download attribute would override any Content-Disposition: inline value and not show a preview.

<a href="fileurl" download="suggestedfilename"> Download </a>

window.open with _blank:
If you can’t or don’t want to use an anchor tag (as it would show the url on hover, which you may want to avoid), then you can use a button, and on click open a new window with the file url. The browser would then either show a preview or Save File dialog based on response headers. This has a side effect that a new window is opened, even if you only wanted to download the file, but recent browsers automatically close the window once the file is downloaded.
You might face some problems with popup blocking when using window.open, depending on whether you are using it in an async callback.

window.open(fileurl, "_blank");

window.open with _self:
If you don’t want to open a new window at all, and allow the download from the same window/tab, you can use window.open(fileurl, “_self”). This works fine if the file is supposed to be downloaded directly. In case of a preview, the preview would open in the same window as your application, which is undesirable in most cases.

window.open(fileurl, "_self");

Create a hidden link and trigger click on it :
On click of a button, instead of using window.open, you can also create a hidden link in your code, set the required href, and trigger a click from javascript.

const link = document.createElement("a");
document.body.appendChild(link);
link.href = url;
link.setAttribute("type", "hidden");
link.setAttribute("download", true);
link.click();

Create a hidden iframe:
Not discussed here, but vaguely similar to window.open.

Nice compilation of tricks, but how does any of this work behind Auth?

We first need to establish that the current user is authorized to download this resource. Usually we do it by sending the credentials in Authorization header, but in all the above discussed cases, the requests for download are sent by the browser directly and we can’t add any headers to it.

As discussed here, One option is to:

  • download the file from within the application by sending auth headers,
  • create a blob url
  • set blob url as link href

now the user can click on the link to open/download the file. This approach is similar to what is discussed in the above linked post for images, but this effectively means that the application has to manage all the downloading, what if the file is very big and cannot be converted to a blob? It would be better to let the browser deal with all these problems.

So, in our scenario, we went ahead with the alternative approach; before every download request the frontend gets a new download token from the backend and creates a download url which would only be valid till the token is valid.

UX wise, one of the ways to handle it would have been to show a button which says “Generate Download Link” on click of which we can get a token and show a link on the UI which the user can then click to download the file. But in our scenario, we did not want this indirection, we wanted to have a “Download” button and start the download as soon as the token is available, without asking the user to click on another link, so we ended up using window.open to trigger a file download as soon as we had the token.

All said and done, now the browser can treat this url (with the short lived token appended) as any regular request and we don’t need to send any headers as all the info required to verify the user is appended to the url in the form of a short lived token.

We can then use any of these methods to trigger the actual download: window.open or hidden link or iframe.

This is the workflow:

  • The app shows a “Download” button to the user.
  • The user clicks on the button.
  • The app requests a short lived download token from the backend. This get token endpoint is called from within the application and makes use of the user authorization information in the header. The backend verifies that an authorized user is asking for a download token, and it returns a short lived (in our case 2 minutes) token.
  • The app then creates the download url by appending the token to the filename.
  • The app uses window.open(fileurl-with-token) to trigger the actual download.

References:

--

--

Responses (2)