You know those moments when you're building something and questioning the approach? Those moments when something should be easy but it's not?

Restricting downloads to only users who are authenticated should be EASY from the client side ... right?

Having authentication between the client and api is stupidly common. Your client sends all its requests up with a `jwt`, the server validates the `jwt` through middleware and you have an authenticated api.

So what happens when we can't add our token to a request header. A Download Link for example. We need a way to request and download a resource, and make it go through the same authentication pipeline as our app that is as seamless as ... clicking a link.

Probably the right way

If we had all the resources in the world, we could use someones credit card, setup an account on AWS, setup profiles and policies and infrastructure to allow our server to generate signed urls that expire after a couple of minutes.

Well in the world of agile, priority arrangements and short sprints, sometimes you need to take an approach with far less overhead.

Using HTML5, Blobs and Javascript in React

Authenticated File Downloads in React

We can use this technique with our existing authenticated api and build a component wrapper to handle the action lifecycle. An overview of this technique looks like this:

  1. Have an authenticated server that can serve a file like res.sendFile(filePath) which is protected and requires a valid authenticated session
  2. Make a request const result = await fetch(...) from the client to the server with your auth headers to an endpoint that can serve your file as the response.
  3. When the client receives the response you convert the data to a blob using const blob = await result.blob().
  4. Create an object URL out of the blob by using const objectURL = window.URL.createObjectURL(blob)
  5. Assign that object url to the ancher tag <a href={objectURL}>download</a>
  6. Programatically click the ancher tag to initiate the download so the user does not have to click a second time.

Let see what a reusable component would look like in React.

import { createRef } from 'react'

export function AuthenticatedLink ({ url, filename, children }) {
  const link = createRef()
  
  const handleAction = async () => {
    if (link.current.href) { return }
  
    const result = await fetch(url, {	
      headers: {...authHeaders}
    })
    
    const blob = await result.blob()
    const href = window.URL.createObjectURL(blob)
    
    link.current.download = filename
    link.current.href = href
      
    link.current.click()
  }

  return (
    <>
      <a role='button' ref={link} onClick={handleAction}>{children}</a>
    </>
  )
}

we then call the component in its parent like

<AuthenticatedLink url='/protected/api/documents/filename.png' filename='filename.pdf' >
  Download file now
</AuthenticatedLink>
  1. We check that the a element does not have a href attribute assigned to stop the recursive pattern that could happen when simulating a click and calling the click handler again.
  2. Using Reacts.createRef allows us to manage the element with ease.
  3. This pattern does not change how a link is clicked in a web application. It works like a normal link

So there we have it, a pretty well supported way of protecting files from a server that requires  authenticated users to make requests.

Peace,