How to prevent Content Delivery API abuse in client-side web app

I’m using the Contentful.js npm package in a Next.js web app. Since pages load client-side asynchronously, when I look at Chrome Dev Tools > Network, I can see the Contentful Content Delivery API getting called client-side.

So anyone can copy my Contentful API calls and abuse the endpoint generating millions of requests, thus eating into the Fair Use quota and causing overages.

Are there any best-practices to prevent this from happening?

This seems to be a problem for any client-side web app (e.g. React, Vue, etc) that is using Contentful.

Thanks for the advice.

We are also using Next.js and solved this by setting up a custom express endpoint as a proxy. Our client side app calls /contentful sending any response options. The end point uses the Contentful JavaScript SDK to fetch data and sends back the portion of the response we need. The Contentful API tokens are kept server side and hidden from the user.

The Next.js github repo has examples for setting up a custom server and adding custom environmental variables (they just made this easier in v8).

If you have any follow up questions I’m happy to help.

Very basic example:

server.post('/contentful', jsonParser, (req, res) => {
      const { body } = req;
      contentfulClient
        .getEntries(body)
        .then(response => {
          const { items } = response;

          if (!items || !items.length) {
            throw new Error('No items');
          }
          res.status(200).send(items);
        })
        .catch(error => {
          console.error(error);
          res.status(400).send('Request Error');
        });
    });

Thanks for the reply @kyle.r, this is very helpful

A few questions about this solution:

  • Is this Express proxy endpoint part of your Next.js app code, or entirely separate? I’m wondering if I can colocate it the Express server I already have as part of the Next.js app for doing server-side route handling (i.e. index.js, server.js)

  • Do you have an example of what your Next.js code look like at the page level? That is, how are you interfacing with this endpoint you created? Right now, we’re just calling contentfulClient.getEntries() from everywhere, for example inside files like pages/article.js for article pages, etc.

  • By using this solution, you are forgoing Contentful’s CDN edge-caching, is that correct? Since now all your Contentful requests are coming from the server-side.

An alternative idea, which I’m not sure if is even possible, would be to configure the host parameter in contentful.createClient() to go through a CloudFront proxy, and then somehow have CloudFront request from Contentful with the secret access token. But I’m not sure if this is even possible, or if it would require something like Lambda as well. Do you think this is viable?

Thanks again for your help.

Is this Express proxy endpoint part of your Next.js app code, or entirely separate?

It is part of our custom express server which is part of our Next app, example here.

Do you have an example of what your Next.js code look like at the page level?

We have a function that calls to that endpoint. I named it getEntries so I had to change less code when we moved to this solution.

import fetch from 'isomorphic-unfetch';

const getEntries = async (options) => {
  try {
    const items = await fetch(`${SERVER_URL}/contentful`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(options),
    })

    return items.json();
  } catch (error) {
    console.error(error);
  }
}

export default getEntries;

options is the object of Contentful parameters the SDK takes.

By using this solution, you are forgoing Contentful’s CDN edge-caching, is that correct?

I don’t think so but I should definitely look into that. My understanding is that as long as you are hitting the production Contentful Delivery API they cache all requests and don’t care where they are being requested from.

Your alternate idea sounds basically the same as what this solution achieves: a proxy endpoint that handles authenticating with Contentful. That being said, I’m not sure what the trade offs or gains are from that solution.

Thanks for the detail @kyle.r

Are you fetching via your /contentful express proxy on server-side-rendered page renders as well?

Since that might be an unnecessary step versus querying Contentful directly. I’m thinking of adapting your solution so that only client-side entry fetches go through the proxy, but it does increase the complexity a bit.

We are making all calls to our endpoint in getInitialProps of both _app.js and out individual pages. Our calls in _app.js fetch data common to the entire app and individual pages get what they need. getInitialProps works both on server and clients side requests. You shouldn’t need to differentiate the two Next handles that.

Hi @kyle.r, thanks for the reply. Regarding my last point what I mean is this…

For server-side rendered pages (e.g. say a GET request to http://localhost/my-contentful-page) , your app is doing an internal HTTP call to itself with the await fetch () pointing to /contentful that runs on the same server.

But why not go directly to Contentful in this server-side-rendered case? Because now you have two HTTP calls instead of one, which adds to the load of our Express server: one to /contentful proxy (Express), another to Contentful from the proxy.

In the server-side rendered case, there is no need to proxy Contentful, since the client can’t see those calls anyway, so there’s no tokens being exposed, etc.

Is my understanding correct? And could this be optimized further? So you’re only going through the /contentful proxy for client-side requests, and not initial server-side rendered pages.