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:


But chrome doesn't set the cookies, in Application -> Cookies -> localhost:8080: "The site has no cookies".
Using form.submit() while the server sets the cookies and redirects works just fine, the problem only occurs using fetch to retrieve json, so this is why I'm posting it here.
Any idea how to make it work?
Having the exact same problem.
From the doc:
Receiving cookies
Like with XMLHttpRequest, the Set-Cookie response header returned from the server is a forbidden header name and therefore can't be programatically read with response.headers.get(). Instead, it's the browser's responsibility to handle new cookies being set (if applicable to the current URL). Unless they are HTTP-only, new cookies will be available through document.cookie.
But it doesn't look like my browser is setting the cookies.
EDIT: If I do document.cookie = 'JSESSIONID=xxxx;Path=/' in the console, and refresh the view. I am logged in.
So looks like the cookies are not being set.
So, I've verified that my issue is not caused by the server (like not setting path or somewthing).
I opened chrome://net-internals/#events and looked at the difference between a fetch call and $.ajax call (which sets the cookie correctly).
There is one thing in particular i noticed:
$.ajax:
t=587184 [st= 2] +URL_REQUEST_START_JOB [dt=6]
--> load_flags = 33026 (BYPASS_CACHE | MAYBE_USER_GESTURE | VERIFY_EV_CERT)
--> method = "POST"
--> priority = "MEDIUM"
--> upload_id = "0"
--> url = "http://localhost:8080/api/v1/login"
fetch:
t=622664 [st= 2] +URL_REQUEST_START_JOB [dt=8]
--> load_flags = 34626 (BYPASS_CACHE | DO_NOT_SAVE_COOKIES | DO_NOT_SEND_AUTH_DATA | DO_NOT_SEND_COOKIES | MAYBE_USER_GESTURE | VERIFY_EV_CERT)
--> method = "POST"
--> priority = "MEDIUM"
--> upload_id = "0"
--> url = "http://localhost:8080/api/v1/login"
See the load flags? Not sure what those are, but I guess that is what causing the problem.
document.cookies prints blank line for me. I've confirmed that regular XMLHttpRequest works fine for me.
Wow. I just realized I did a major mistake.
So, I have two requests; one login request and one customer request. It is the login request that gets the set-cookie header in its response, and then the user should be loggen in.
There is an option called _credentials: 'same-origin',_ which I did not send with the login request (because I thought it only needed to be sent with requests after I was logged in.)
Try 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 => {
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 });
}
});
Make sure you don't add the _credentials: 'same-origin'_ in the header object. It is not supposed to be there. I've seen many do that mistake.
I was too under the impression that credentials: same-origin is used only for sending cookies, not receiving. Thank you, I'll try this as soon as I can.
If that is indeed the solution, it might be a good idea to add this to the documentation/README.
Yep, me too. That's why I didn't send it when logging in...
I agree. The readme should be updated.
Updating the readme...
@stianlp i am facing same problem even after setting the 'same-origin option',
I am using one express server as a backend running on port 8000, react frontend is running on another express server on port 3003.
I am able to see the response cookie in dev tools but it is not set.
Can you please help? i am blocked :/
export function signupUser(user){
return function (dispatch, getState) {
return fetch(api.BASE_URL + api.LOCAL_SIGNUP, {
method: 'post',
headers: {
"Content-type": "application/json"
},
credentials: 'same-origin',
body:JSON.stringify(user)
}).then(function(response) {
return response.json();
})
.then(function(data) {
if(data.errors){
throw data.errors.name.message;
}
return dispatch(loginUserSuccess(data));
})
.catch(function(error) {
throw error;
});
};
}
@ramprasadgs Is the signup request the one that is supposed to set the cookies in your browser or is it a request happening before/after this request that are missing the credentials option?
It's been a while since I worked with this, and I don't remember exactly when to use the option. It is either one of these two cases (or both):
A) Use the option when sending the request that logs a user in aka. receives the session cookie for the server.
B) Use the option when sending all requests after the login request is sent.
I think it's best to just use the option in all requests when debugging, and when it works, see where you can remove it. The docs are still confusing, so it's not possible to understand if the option must be used when logging in OR when sending requests as a logged in user.
Btw, are you getting a response from the sign up request? You should be able to get a response from a login-request, but if I remember correctly, the problems starts when you're doing the next request and the cookie is not set.
EDIT: Btw, make sure the problem is not caused by CORS issues. As your backend and fronend are on different domains (the ports are not the same), you may have to use the credentials: include option. But I don't know how this option works, so I am not sure. https://github.com/github/fetch#sending-cookies
My two cents: If on DEV don't use localhost as the domain value of the Cookie, use 127.0.0.1 instead.
Resources
I had to use credentials: 'include' on the client side and CORS_ALLOW_CREDENTIALS = True in my django app. Also setting my cookie with 127.0.0.1 in localhost response.set_cookie('my_cookie', value=token, httponly=True, domain='127.0.0.1')
I was able to use fetch-cookie with isophormic-fetch. That solved the issue for me. For example when using Jest, I added the following to the top of the setup.js
```
const Tough = require('tough-cookie');
const Store = new Tough.MemoryCookieStore();
const fetch = require('fetch-cookie')(require('isomorphic-fetch'), new Tough.CookieJar(Store));
global.fetch = fetch;
global.cookieStore = Store;
````
I needed the Store to be able to remove cookies during testing.
Thanks @jayesbe , I am trying to configure that since a while !
I have been trying with the code you provided and document.cookie is empty.
In case you still have the configuration? would you mind sharing an example ?
@kopax I am using this ReactNative so there is no document.cookie.
@jayesbe thanks your code sample helped me get through. I got exactly what I need thanks to you. :)
And how is react native ? Do you have a sample project I could start play with? Do you need to have mobile development experience or react is sufficient ?
I don't have any samples, its my day job :) for basic and even some useful apps, react experience is enough. Anything really specific might need some actual android or ios knowledge.
credentials: 'same-origin'
I fought 3 days because of this ####... holy moly.
kudos to @stianlp !
Most helpful comment
Wow. I just realized I did a major mistake.
So, I have two requests; one login request and one customer request. It is the login request that gets the set-cookie header in its response, and then the user should be loggen in.
There is an option called _credentials: 'same-origin',_ which I did not send with the login request (because I thought it only needed to be sent with requests after I was logged in.)
Try this:
Make sure you don't add the _credentials: 'same-origin'_ in the header object. It is not supposed to be there. I've seen many do that mistake.