5

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,
      })
    }
  }
sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
WriterState
  • 359
  • 3
  • 18
  • 1
    What is your webpage URL, and your server URL? – Roy Wang Dec 12 '19 at 07:05
  • they are different... One is a heroku random url the other is a netlify url Note that it worked on local host port 3000(server) and local host port 8000(client). And also that the cookies are being set by the server. I can see them in chrome dev tools. – WriterState Dec 12 '19 at 23:02

2 Answers2

8

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.

Roy Wang
  • 11,112
  • 2
  • 21
  • 42
2

...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 });
winwiz1
  • 2,906
  • 11
  • 24
  • 1
    I tried these changes and I wasn't able to get them to work. What's more interesting, the cookies are being sent over and I can see them in Dev Tools. And when I do cookies.get(), it returns an empty object. My thinking is that either Netlify handles cookies in a strange way or there's something wrong with the js-cookie package or how I am using it. – WriterState Dec 12 '19 at 14:30
  • Another issue is that you use `credentials: "include"` on the client and do not [set](https://stackoverflow.com/a/48231372) the [Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) header. This MDN page says "_both_ the server (using the Access-Control-Allow-Credentials header) and the client (by setting the credentials mode for the XHR, Fetch, or Ajax request) must indicate that they’re opting in to including credentials". I assume the client JS is downloaded from Netlify and not from Heroku server. – winwiz1 Dec 13 '19 at 01:05