Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Express + React: CSRF Cookie is undefined in production, works locally

I'm hosting my express API on Heroku and my client on Netlify. When I try my signup route locally, my cookie is defined and the route works. However, when it is in production, the cookie always returns undefined and my API times out.

Note that the cookie is being sent successfully from the backend. I’m able to view it in Dev Tools. Additionally, cookies,get() returns an empty object.

I’m using Js-cookie.

I'm using js-cookie in Gatsby. I'm using CSURF in express for the cookie.

Backend:

//CSURF Config
app.use(csurf({ cookie: true }));


//Route that generates CSRF Cookie
app.get("/getToken", (req, res) => {
    res.cookie("XSRF-TOKEN", req.csrfToken());
    res.end();
  });

Frontend:

I'm including the entire sign up function. Note that this is two end point calls, one to retrieve the cookie, and one to create user record.

  userSignUp = async (email, password, resetForm) => {
    console.log("THis is a test of the emergency..")
    await fetch(process.env.API + "getToken", {
      mode: "cors", // no-cors, cors, *include
      cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
      credentials: "include", // include, *include, omit
      headers: {
        "content-type": "application/json",
      },
      method: "GET",
    })
    const token = Cookies.get("XSRF-TOKEN")
    alert(Cookies.get("XSRF-TOKEN"))
    const data = await fetch(process.env.API + "signUp", {
      body: JSON.stringify({ email: email, password: password }),
      mode: "cors", // no-cors, cors, *include
      cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
      credentials: "include", // include, *include, omit
      headers: {
        "content-type": "application/json",
        "csrf-token": token,
      },
      method: "POST",
    }).catch(error => this.setState({ isError: true }))
    if (data.ok) {
      console.log(data.ok)
      data.json().then(content => {
        console.log(content)
        this.props.changeAuth(content.authStatus)
        this.props.setCategories(content.user.categories)
        this.props.getEvents(content.user.events)
        resetForm()
      })
    } else {
      this.setState({
        isError: true,
      })
    }
  }
like image 449
WriterState Avatar asked Dec 10 '19 01:12

WriterState


2 Answers

The cookies will not work if the front-end and server domains are different.

You can set up a proxy through Netlify to redirect all requests to a path (eg. /api/) of the front-end domain to the backend domain.

To set up the proxy, you can create a _redirects file in the publish directory (eg. public) with the following configuration:

/api/*  https://<server-url>:<server-port>/:splat 200

Lastly, send all HTTP requests to that front-end path (eg. https://<front-end-url>/api/users) instead of the backend URL to ensure that the cookies work correctly.

like image 196
Roy Wang Avatar answered Nov 15 '22 00:11

Roy Wang


...when it is in production, the cookie always returns undefined...

One possible reason could be that you are using HTTPS in production (which should be the case) and the browser ignores the CSRF cookie because it is not set as a secure cookie. To fix this issue use secure cookies in production and non-secure ones in development.

P.S.
To make the first cookie to be secure replace this code

app.use(csurf({ cookie: true }));

with that:

app.use(csurf({
  cookie: {
    httpOnly: true,
    secure: !devModeFlag
  }
}));

The httpOnly setting ensures JS on the client cannot touch this cookie which is good for extra security. The boolean devModeFlag variable should be set to true in development and to false in production.

To make the second cookie to be secure replace this code

res.cookie("XSRF-TOKEN", req.csrfToken());

with that:

res.cookie("XSRF-TOKEN", req.csrfToken(), { secure: !devModeFlag });
like image 36
winwiz1 Avatar answered Nov 15 '22 00:11

winwiz1