The Problem
I have a Node.js end-point that properly triggers an arbitrarily-large file download when accessed using the following:
response.setHeader('Content-disposition', 'attachment; filename=' + fileName);
response.set('Content-Type', 'text/csv');
response.status(200);
result.pipe(response);
where result
is a transform stream, and response
is an Express object.
This works fine when directly accessing the end-point in Chrome, Firefox, Internet Explorer, etc. The problem arises from trying to hit the end-point when token-based authentication is enabled.
From a user's perspective, when they click on a button, a file is downloaded.
How do I make the button hit this end-point with the correct authentication token in the request's header and cause a file to be downloaded?
Brainstorm of Some Possible Approaches
When the user clicks on the button, it fires an action that's handled by the redux-api-middleware, which makes the
GET
request to the end-point (with the authentication token automatically included in the request). This middleware saves the response in a variable, which is picked up by a React component. In this React component, if the browser being used (i.e. Chrome and Opera) supports streams,response.body
will exist so you can do something like the following.if (response.body) { const csvReader = response.body.getReader(); const textDecoder = new TextDecoder(); const processCsvRow = (csvRow) => { if (csvRow.done) { console.log('Finished downloading file.'); return Promise.resolve(); } // Write to new file for user to download console.log(textDecoder.decode(csvRow.value, {stream: true})); return csvReader.read().then(processCsvRow); }; csvReader.read().then(processCsvRow); }
With this approach, I'd need to investigate how to handle the data if it's being received by a non-stream-supporting browser.
When the user clicks on the button, it fires an action that saves the end-point into the Redux store by the reducer, which triggers a React component to create an anchor tag that is automatically clicked.
const link = document.createElement('a'); document.body.appendChild(link); // Firefox requires the link to be in the body link.href = endPoint; link.target = '_blank'; link.click(); document.body.removeChild(link); // Remove the link when done
where
endPoint
is the end-point that responds with the file.This approach works when authentication is disabled. When authentication is re-enabled, the authentication token must somehow be injected into the anchor tag's request header.
- Similar to #2, look into simulating an anchor-click by constructing an HTTP request with the authentication token built in.
- Combining elements from #1 and #2, when the user clicks on the button, an action is fired that sends a
GET
request to the end-point, which returns a second temporary unsecured end-point that returns the actual file response. Pass this temporary unsecured end-point to the anchor tag in #2. - Redirect the response to a new tab/window somehow.
- Pass the authentication token as a URL parameter (this is a security concern), then use #2.
- Create a new end-point that generates and returns a new temporary one-time-use download-only token. Use this new end-point to get a temporary token to pass as a URL parameter into the original end-point via #2.