16

I'm trying to implement client login using fetch on react.

I'm using passport for authentication. The reason I'm using fetch and not regular form.submit(), is because I want to be able to recieve error messages from my express server, like: "username or password is wrong".

I know that passport can send back messages using flash messages, but flash requires sessions and I would like to avoid them.

This is my code:

fetch('/login/local', {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        username: this.state.username,
        password: this.state.password,
      }),
    }).then(res => {
      console.log(res.headers.get('set-cookie')); // undefined
      console.log(document.cookie); // nope
      return res.json();
    }).then(json => {
      if (json.success) {
        this.setState({ error: '' });
        this.context.router.push(json.redirect);
      }
      else {
        this.setState({ error: json.error });
      }
    });

The server sends the cookies just fine, as you can see on chrome's dev tools: Network - Cookies Network - Headers

But chrome doesn't set the cookies, in Application -> Cookies -> localhost:8080: "The site has no cookies".

Any idea how to make it work?

Gershon Papi
  • 4,718
  • 3
  • 20
  • 46

3 Answers3

11

The problem turned out to be with the fetch option credentials: same-origin/include not being set. As the fetch documentation mentions this option to be required for sending cookies on the request, it failed to mention this when reading a cookie.

So I just changed my code to be like this:

fetch('/login/local', {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      credentials: 'same-origin',
      body: JSON.stringify({
        username: this.state.username,
        password: this.state.password,
      }),
    }).then(res => {
      return res.json();
    }).then(json => {
      if (json.success) {
        this.setState({ error: '' });
        this.context.router.push(json.redirect);
      }
      else {
        this.setState({ error: json.error });
      }
    });
Gershon Papi
  • 4,718
  • 3
  • 20
  • 46
  • 2
    This didn't work for me , even after setting the credentials: 'same-origin',/'include. – svp Feb 26 '18 at 13:02
  • This does not work even with the credentials as same-origin – Satyam S Apr 04 '19 at 13:19
  • 1
    credentials: 'same-origin' is the default value, no? https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials – ulu Apr 12 '19 at 18:02
  • Not working credentials: 'same-origin' or 'include' – August Dec 27 '19 at 14:10
  • 1
    I fixed this by setting credentials: 'include' and also setting the header "Access-Control-Allow-Origin" to "http://localhost:3000" (my Webstorm is serving the react pages from a different server than my express server) and also setting the header "Access-Control-Allow-Credentials" to "true" – Mark May 05 '20 at 10:28
  • I did as Mark above, but it didn't work for me. But it turns out you can just access `document.cookie`; here's how I accessed a JWT inside a bearer token set-cookie Authorization response: ```const cookie = document.cookie;const findAuthorization = 'Authorization=';const startIndex = cookie.indexOf(findAuthorization) + findAuthorization.length;const endIndex = cookie.indexOf(';', startIndex) ?? cookie.length - 1;const rawJwt = cookie.slice(startIndex, endIndex).split(' ')[1]; ``` – koral Jul 20 '20 at 15:33
5

From Differences from jQuery section of https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

  • fetch() won't receive cross-site cookies. You can’t establish a cross site session using fetch(). Set-Cookie headers from other sites are silently ignored.
  • fetch() won’t send cookies, unless you set the credentials init option. Since Aug 25, 2017: The spec changed the default credentials policy to same-origin. Firefox changed since 61.0b13.)
Konstantin Komelin
  • 414
  • 1
  • 6
  • 8
0

I spent a long time but nothing worked for me.

after trying several solutions online this one worked for me.

Hopefully it will work for you too.

{
  method: "POST",
  headers: {
    "content-type": "API-Key",
  },
  credentials: "include",
}
Mohsin Latif
  • 459
  • 6
  • 7