Search code examples
node.jsexpressd3.js

How can I distinguish if a request came from a Fetch API call, in a Node.js / Express / D3.js app?


I have a web application built with Node.js and Express 4.x, where a typical page looks like this:

...
<script type="text/javascript" src="jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="d3-5.4.0.min.js"></script>
<script type="text/javascript">
    $(loadData); // When the page is fully loaded, call loadData()
    function loadData() {
        d3.json('/data/purchases').then(generateCharts);
    }
    function generateCharts(data) { }
</script>
...

It just calls d3.json() to read data from the server, and then generates some charts with them.

The way d3.json() communicates with the server is with a GET request to the specified URL, /data/purchases in the example. If somebody types the same URL in the browser, they would obviously get the same response with the JSON data. And that's what I want to prevent.

Is there any way I can check whether a request came from a d3.json() call?

I thought of checking the X-Requested-With header with Express req.xhr property, but it's set to false. Apparently d3-fetch doesn't use an XMLHttpRequest to load the data, they use the new Fetch API instead.

I've also tried with req.is('application/json')... but it returns null.

Anything else I can try?

PS: I already have a simple authorization system, which checks if the current user should have access to any requested URL, both for pages and API calls. But it requires some extra configuration data stored in my database, making the app slightly harder to maintain. So, given that we don't actually have any sensitive data, I thought I could drop the authorization checks for API calls, and instead allow them to be made only through d3.json() and not manually. I could instead allow them completely... I just don't want to :), which provides an opportunity to learn a bit more about Express and D3.js.


Solution

  • d3.json() allows to pass init parameters to the underlying Fetch call. This can be used, amongst other things, to define a custom header:

    const initParams = {
        headers: new Headers({
            "MyVeryOwnHeader": "Hooray!"
        })
    };
    
    d3.json('/data/purchases', initParams).then(generateCharts);
    

    Which can then be checked server-side in Express' routes.js file:

    if (req.get('MyVeryOwnHeader') === 'Hooray!') {
        // Valid API call made from d3.json, not by the user directly typing the URL
    }
    

    That's the basics; you can complicate it as much as you wish. For example, instead of a static value, you could generate a random token (or nonce) for each requested page, and store it in a session variable, to later check the value when you validate the next API call. The only problem would be users browsing with multiple tabs simultaneously: it could lead to race conditions where some pages would fail to load the necessary data.

    And of course, if the user wants, they still can inspect the code, find out the last token, and then generate fake calls to the API including the custom header. This isn't intended to provide any security at all, only to discourage direct access to the data.