One thing I have noticed over the last couple of years is the change in the way JavaScript applications interact with backend APIs.

When storing a user's files we typically upload and retrieve files via a secured connection to a web application.

It's becoming more common for frontend applications to be requesting resources cross domain or to be in a scenario that all data must be retrieved as JSON via ajax with APIs placing a real emphasis on throughput.

This can result in complications when trying to retrieve files (particularly large ones) securely.

Back in the day...

In the past, most applications would tend to be on the same domain and would typically use cookie based authentication. Consequently, the application would be simply able to handle standard requests for files by creating a URL to be able to access the file and allowing the frontend application to render a link to the URL. Clicking the link would result in an Http response with the mime-type of the file resulting in the browser downloading the file. This was pretty secure provided the connection was secured by SSL and the appropriate caution was taken to protect against cross-site request forgery given the implicit nature of cookie-based authentication.

So what's changed?

Many applications are now architected with the cloud in mind. Often APIs will handle the requests but will offset things such as file storage to another platform based service rather than store uploaded files themselves.

Token based authentication mechanisms such as OpenID Connect connect are common as backend systems are built to support multiple types of client.

This can prove more challenging than allowing a simple browser request to the backend system as communication will typically be via ajax and processing the response of the request is non-trivial.

Leaning on the browser and Azure

Azure storage (disclaimer, I've only tried this with blog storage personally) offers a way of getting around the issue of securely retrieving files by using a secure token sent in a file URL directly to the address of the file in a blob container. As this can be retrieved by a standard browser request this makes the Javascript required to do this trivial.

1. Upload the file

Firstly we upload the file to a web server. The server will take on the responsibility of relaying our file on to the Azure store. As it is in our network we can safely store our access key for the storage account. After this, we can save the URI of the file into a data store so that we can correctly correlate the retrieval of the file to the correct user and records later.
Provided that the connection is secure (uses SSL) we can be sure that the file is secure in transit.
Azure also provides a means of setting up encryption at the container level. Azure Storage Service Encryption can be used to ensure that every write is encrypted using 256-bit AES encryption. This ensures that our file is also secure at rest (the state it is in when not being uploaded or accessed).
What's more, there is no additional cost for this!

2. Retrieve the file URL

At some point, we would want to retrieve our securely stored file. Again we must ensure that our connection is secure by using SSL. Though we do not retrieve our files at this stage we retrieve the means of accessing it.
First, we make a request to the server to get the details of our file. After the server has retrieved this from our datastore we then can access the blob storage API to generate a Shared access signature. This is a tamper proof token that can be added to our URI to allow time-based access to our file.
Once the server has done its part and sent its response we have a URL that is required to access our file.

3. Get the file directly from Azure

This next part is pretty easy. Once we have the URL of our file we just need to open a tab and the browser should download the file.

Possible alternative solution

Html 5 and JavaScript offer a multitude of approaches and to handling file downloads in the browser using newer browser APIs such as Blobs, the FileReader object, and the newer web streams API. Due to the differences in the Browser Object Models, this can be non-trivial particularly with larger files and support can be inconsistent (or non-existent) between browsers.

Final thoughts

The reason I prefer using a Shared Access Signature to "sign" a URL as opposed to using the lower level file-based APIs, basically because of the relative simplicity and scalability of the approach. Whilst there is some extra code to be written on the server and this approach may not suit every scenario I believe we get the following gains:

  1. The front end uses very basic functionality that every browser possesses. This will work the same for small and large files alike.
  2. We offset the burden of delivering the files to the client browser to Azure. There are many cases where this might be desirable.

Azure storage itself offers some compelling features. As well as the ability to generate time-based tamper proof security tokens with ease, the ability to turn on encryption for files at rest through configuration means that from an architectural standpoint we simply have to ensure transport layer security via SSL to guarantee that our files are both secure in transport and at rest.