How to implement CSRF protection in Nextjs with Apollo and GraphQL

10,855

Solution 1

Update

After so much browsing i finally was able to send csrf cookie. I think the problem lies with with the word return.When you use return it excludes the cookie. This is what i did by editing /lib/initApollo.js.


function create(initialState, { getToken, cookies, csrfToken }) {
  const httpLink = createHttpLink({
    uri: "http://localhost:3000/graphql",
    credentials: "include"
  });

    const authLink = setContext((_, { headers }) => {
      const token = getToken();
      return {
        headers: {
          ...headers,
          authorization: token ? `Bearer ${token}` : "",
          "x-xsrf-token": csrfToken ? csrfToken : ""
        }
        cookies: {
          ...cookies
        }
      };
    });
  });

pree!! However SSR does not have cookies. I think we should have two endpoint from client and another for SSR. The SSR url can be csrf exempted.

Solution 2

This may not be the answer you are looking for. I have read it here that if you are using JWT there is no need for CSRFToken. Am not completely sure but its the only get going for now.

Benjamin M explains as follow:

I found some information about CSRF + using no cookies for authentication:

https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/ "since you are not relying on cookies, you don't need to protect against cross site requests"

http://angular-tips.com/blog/2014/05/json-web-tokens-introduction/ "If we go down the cookies way, you really need to do CSRF to avoid cross site requests. That is something we can forget when using JWT as you will see." (JWT = Json Web Token, a Token based authentication for stateless apps)

http://www.jamesward.com/2013/05/13/securing-single-page-apps-and-rest-services "The easiest way to do authentication without risking CSRF vulnerabilities is to simply avoid using cookies to identify the user"

http://sitr.us/2011/08/26/cookies-are-bad-for-you.html "The biggest problem with CSRF is that cookies provide absolutely no defense against this type of attack. If you are using cookie authentication you must also employ additional measures to protect against CSRF. The most basic precaution that you can take is to make sure that your application never performs any side-effects in response to GET requests."

There are plenty more pages, which state that you don't need any CSRF protection, if you don't use cookies for authentication. Of course you can still use cookies for everything else, but avoid storing anything like session_id inside it.

Full article here: CSRF Token necessary when using Stateless(= Sessionless) Authentication?

Share:
10,855
The_Wolf
Author by

The_Wolf

Updated on June 13, 2022

Comments

  • The_Wolf
    The_Wolf almost 2 years

    Following this example in Nextjs repository, I want to implement the CSRF protection (perhaps with csurf package), because I'm using a session ID cookie with express-session.

    I tried setting csurf in my custom server and save the generated token in res.locals.csrfToken which can be taken, on first page load, by the static method "getInitialProps" which is located in /lib/withApollo.js in the example I linked. As soon as I try to change page (with links) or try to make a post request with apollo (login, for instance), server changes the csrf token, so the one which was used by Apollo is no more useful and so I get a "csrf is invalid" error.

    Custom server with csurf configuration

    const csrf = require('csurf');
    const csrfProtection = csrf();
    ////express-session configuration code////
    app.use(csrfProtection);
    app.use((req, res, next) => {
        res.locals.csrfToken = req.csrfToken();
    next();
    })
    

    /lib/initApollo.js

    function create(initialState, { getToken, cookies, csrfToken }) {
      const httpLink = createHttpLink({
        uri: "http://localhost:3000/graphql",
        credentials: "include"
      });
    
        const authLink = setContext((_, { headers }) => {
        const token = getToken();
        return {
          headers: {
            ...headers,
            authorization: token ? `Bearer ${token}` : "",
            Cookie: cookies ? cookies : "",
            "x-xsrf-token": csrfToken ? csrfToken : ""
          }
        };
      });
    

    /lib/withApollo.js

    static async getInitialProps(ctx) {
      const {
        Component,
        router,
        ctx: { req, res }
      } = ctx;
      const apollo = initApollo(
        {},
        {
          getToken: () => parseCookies(req).token,
          cookies: req ? req.headers.cookie : "",
          csrfToken: res ? res.locals.csrfToken : document.cookie
        }
      );
    

    With this config, every route is protected against csrf, but the token created on the server often change and Apollo can't retrieve the updated one as soon as it needs, so the first load is successful, but the subsequent page change (links) or any post request fails, because the token has changed.