7

Im wondering how to secure my api routes. The documentation says, that api routes are same-site-origin by default.

API Routes do not specify CORS headers, meaning they are same-origin only by default. You can customize such behavior by wrapping the request handler with the cors middleware. Next.js Documentation

But if I use a requesting tool like Postman for example, I can always call it and get the results:

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction

export default (req, res) => {
  res.status(200).json({ name: 'John Doe' })
}

How is that possible? I want to restrict access to my application only.

Marcel Dz
  • 2,321
  • 4
  • 14
  • 49
  • Thank you very much for clearing things up. I want to save a secret in my database and call it if my application is requesting the endpoint to make sure only the application can fetch data. I only don't know how to call that secret since im doing my requests inside the application on clientside which would be executed in code. I can save the secret in an env variable, but I need to read it on the api if the request is coming from my application. For that I need to know how to detect the request as income from my application and not from any other source. Could you tell me how to detect that? – Marcel Dz Jul 01 '21 at 12:58
  • (Im still quite new to Next.js) What if I just take the code from my api route and create a normal function and ony my api route I would ask for the secret? The application itself would use the normal function then and every other source can access the api endpoint only if they have credentials from me. How about that approach? (I only don't know how to do my database function then since its only working serverside not clientside) – Marcel Dz Jul 01 '21 at 13:15
  • Thanks for the response. Do you have any source or examples regarding to req.header.host? Do I get it right asking for the req.headers.host in an if statement on my api endpoint to verify its my application which does the request? – Marcel Dz Jul 01 '21 at 13:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/234416/discussion-between-marcel-dz-and-brc-dd). – Marcel Dz Jul 01 '21 at 13:55

2 Answers2

3

The first part of your question is answered by: CORS with Postman.

For the second part, I came up with a very small code utilizing the concept of short-lived access tokens (that too simplified).

// utils/protector.js

let oldToken, newToken;
const checkToken = ({ headers: { 'x-access-token': token } }) =>
  token && (token === oldToken || token === newToken);

const refreshToken = () => {
  oldToken = newToken;
  newToken = Math.random().toString(36).substr(2);
  setTimeout(refreshToken, 3 * 60 * 60 * 1000); // each token is valid for 3 hrs
};
refreshToken();

export { checkToken, newToken as accessToken };
// api/user.js

import { checkToken } from '../../utils/protector';

const handler = (req, res) => {
  // just add this line to the top of your handler
  if (!checkToken(req)) return res.status(403).send();

  // your API logic here...
  res.status(200).json({ name: 'John Doe' });
};

export default handler;

Now in the page you wanna use a protected API:

// index.js

import { useState, useEffect } from 'react';

import { accessToken } from '../utils/protector';

const Home = ({ accessToken }) => {
  const [user, setUser] = useState('Guest');

  useEffect(() => {
    // this will be called from the client
    fetch('/api/user', { headers: { 'x-access-token': accessToken } })
      .then((response) => response.json())
      .then((data) => setUser(data.name));

    return () => {};
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return <div>Hello {user}!</div>;
};

const getServerSideProps = async () => {
  return { props: { accessToken } };
};

export default Home;
export { getServerSideProps };

Any page using a protected route should be server side rendered, else the protector code will be bundled with the client side and will get executed in the browser, resulting in different tokens at the server side (APIs) and the client.


The concept of oldToken and newToken may appear quite unclear. Suppose:

  • A user visits your site at 11:59.

  • Your token gets refreshed at 00:00.

  • They triggered the API call at 12:01.

  • Now if there hadn't been an oldToken, the newToken would have changed before the call, and the response would have been a 403 Forbidden Error.


I have assumed a user takes exactly less than 3 hrs before they take an action on your page that invokes the protected API.

The timeout is set to 3 hrs, even if after a token is valid for twice its time because if the timeout is half (i.e. 1.5 hr) and the (above) user triggers the API call at 01:31. Then oldToken is the newToken, and newToken is newer, both won't match what the user has.

Hence to guarantee that the received token will be valid atleast for 3 hrs, the total time to live is double. It may happen a token is valid for 6 hr (like for the user who visited the site at 12:00).

brc-dd
  • 10,788
  • 3
  • 47
  • 67
  • 1
    This means there is still 3 hr window where data can be fetched once we get hold of that token. Isnt there anything else that can stop ALL direct hits to BFF and not via UI? – keerti Oct 01 '22 at 01:00
  • This isn't working for me - the version of the tokens served by getServerSideProps and the version accessed by the api endpoint are not the same. How do I force it to be shared? – Tim Trewartha Apr 14 '23 at 07:36
1

I found https://github.com/vercel/next.js/blob/canary/examples/cms-contentful to be extremely useful in helping me with this same issue of getting away from using API routes as the main data retrieval for pages, etc. In this example they have their data retrieval code in a /lib/api.js file (not in an API route). Whether retrieving data using a page (https://github.com/vercel/next.js/blob/canary/examples/cms-contentful/pages/posts/%5Bslug%5D.js) or an API (https://github.com/vercel/next.js/blob/canary/examples/cms-contentful/pages/api/preview.js) the data retrieval and db connections are kept out of your API routes. I suspect you can skip using API routes all together if you only want your app pages to access the data.

Here is another very simple sandbox example (but I suggest reviewing the GitHub examples above first):

https://codesandbox.io/s/data-retrieval-outside-api-7zlmn?file=/pages/index.js

w. Patrick Gale
  • 1,643
  • 13
  • 22
  • When I read this first I just thought: Wow, well this seems to be a great approach. Thinking further there are som questions which we should think of. In my case im using prisma as database setup which is actually common for next projects. Propably it does only work in getserersideprops and at the api routes. We wouldnt be able to call it in /lib/api.js. The second Issue we got: if we get data from a service where we are working with having an api key to access it, we would save it in an env var to have it private. getting the value of an env var also is restricted to the 2 cases (SS and API) – Marcel Dz Nov 11 '21 at 13:43