334

I am trying out the new Fetch API but is having trouble with Cookies. Specifically, after a successful login, there is a Cookie header in future requests, but Fetch seems to ignore that headers, and all my requests made with Fetch is unauthorized.

Is it because Fetch is still not ready or Fetch does not work with Cookies?

I build my app with Webpack. I also use Fetch in React Native, which does not have the same issue.

Khanetor
  • 11,595
  • 8
  • 40
  • 76

10 Answers10

381

Fetch does not use cookie by default. To enable cookie, do this:

fetch(url, {
  credentials: "same-origin"
}).then(...).catch(...);
Klesun
  • 12,280
  • 5
  • 59
  • 52
Khanetor
  • 11,595
  • 8
  • 40
  • 76
  • 90
    same-origin doesn't work anymore, include does (see @Jerry's answer): https://developers.google.com/web/updates/2015/03/introduction-to-fetch – jpic Dec 07 '16 at 00:59
  • 9
    @jpic: 'include' only works for cross-origin requests, but not for same-origin requests. Official docs: https://github.com/github/fetch#sending-cookies – HoldOffHunger Nov 29 '17 at 17:26
  • What is the reason then to have httponly cookies if they are readable in js with fetch ? – Martin Bajcar Dec 07 '17 at 22:24
  • 6
    I believe `same-origin` (which *does* still work) means that more headers will be respected (cookies, etc) but your code will have limited access to the response. – Coderer Dec 13 '17 at 15:51
  • Wasted a lot of time trying to figure out the problem in the server side. Thanks a lot! – Pransh Tiwari May 12 '18 at 19:39
  • 2
    @MartinBajcar parameter "credentials" in fetch is not reading any cookie, it only acts as a door, if opened(credentials: 'include'), cookies will be allow to pass, if not opened then they aren't. – John Balvin Arias May 28 '18 at 08:46
  • 6
    @JohnBalvinAriasThx. As I later understood, having cookie httponly means that it's not readable by `document.cookie`, but still available to ajax or fetch requests. – Martin Bajcar May 28 '18 at 22:01
  • 1
    i am unable to get "set-cookie" parameter in response headers in fetch api in android. in ios i am getting it for first time. how to get "set-cookie" every time i login in both android and ios. i used to achieve this in native ios by setting setHTTPShouldHandleCookies to NO. thanks in advance – Dhanunjay Kumar Jun 20 '18 at 16:54
  • @DhanunjayKumar If you have a new question, please use the Ask Question button. Comments are not a good avenue for getting questions answered. – Heretic Monkey Aug 01 '18 at 20:50
  • @HereticMonkey : i have added as question also link : https://stackoverflow.com/questions/50904745/not-getting-set-cookie-in-headers-if-i-call-login-frequently-in-reactnative – Dhanunjay Kumar Aug 02 '18 at 05:06
  • Hello guys, I was reading this question, and I have a similar one, which my cookies are set to HttpOnly and from javascript when I fetch an API it redirects to login url because it cannot reach the cookies, where credentials:same-origin didnt work for me, can you please check my question? https://stackoverflow.com/questions/59311203/cannot-read-property-undefined – Burak Dec 16 '19 at 07:58
  • what if we have different origins. – Menai Ala Eddine - Aladdin Feb 19 '23 at 20:47
299

In addition to @Khanetor's answer, for those who are working with cross-origin requests: credentials: 'include'

Sample JSON fetch request:

fetch(url, {
  method: 'GET',
  credentials: 'include'
})
  .then((response) => response.json())
  .then((json) => {
    console.log('Gotcha');
  }).catch((err) => {
    console.log(err);
});

https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials

zurfyx
  • 31,043
  • 20
  • 111
  • 145
  • 23
    how do you set the cookie though? – pomo Aug 30 '16 at 17:58
  • 2
    The cookie is set from server-side. In my case I was using httponly cookies. – Khanetor Nov 18 '16 at 14:25
  • 4
    @Khanetor can I set cookies using the document.cookie by javascript, and then send the request? – ospider Jun 14 '17 at 01:36
  • @ospider You can send it in the header. – 10101010 Nov 24 '17 at 09:17
  • 1
    Interesting fact: I wanted to cover both cases of fetch with same-origin and cross-origin by using this, and I could not, but it did work for cross-origin requests. =) – HoldOffHunger Nov 29 '17 at 17:25
  • 3
    @ospider I found that just setting the value in `document.cookie` was enough for it to be included in the requests. – skwidbreth Apr 02 '18 at 18:47
  • i am unable to get "set-cookie" parameter in response headers in fetch api in android. in ios i am getting it for first time. how to get "set-cookie" every time i login in both android and ios. i used to achieve this in native ios by setting setHTTPShouldHandleCookies to NO. thanks in advance – Dhanunjay Kumar Jun 20 '18 at 16:54
  • 1
    If you're using a rails API and keep getting CORS issues with `Access-Control-Allow-Credentials` make sure in your Rack::Cors config you set `credentials: true` on the resource. – Mathyou Feb 06 '21 at 19:33
  • @pomo dough* - refrigerate it for 24hrs, it should be fully set by then – wiktus239 Jul 15 '21 at 09:45
  • See this [solution](https://stackoverflow.com/a/74948785/14226292) – Habibullah Rezaie Dec 29 '22 at 08:27
  • This made an end to my countless hour journey on why setting my cookie doesn't work. Thank you so very much. – David Fischer Jan 18 '23 at 10:11
113

Have just solved. Just two f. days of brutforce

For me the secret was in following:

  1. I called POST /api/auth and see that cookies were successfully received.

  2. Then calling GET /api/users/ with credentials: 'include' and got 401 unauth, because of no cookies were sent with the request.

The KEY is to set credentials: 'include' for the first /api/auth call too.

kore666
  • 1,501
  • 1
  • 11
  • 8
  • 1
    I have exactly your problem. The session cookie is never sent on the GET data request. so 401. I have tried Axios and Fetch. same result. 2 possibilities: the login POST doesnt store the received cookie or the following GET data doesnt send the stored cookie – Rhubarb65 Oct 09 '18 at 13:46
  • @Rhubarb65, to win this u should specify `credentials: 'include'` for first `POST /api/auth` – kore666 Oct 09 '18 at 14:18
  • Yes, I had that but it want enough. I use a devserver proxy (client Http) – Rhubarb65 Oct 11 '18 at 13:19
  • Yes, I had credentials but it was not enough. I was using a devserver proxy to get past CORS: (client http) - proxy - (server https). I believe this meant that the sessionid cookie from the server was not set in the browser because secure cookies require https. So I added the flag https: true in the devserver proxy and that fixed it – Rhubarb65 Oct 11 '18 at 13:25
  • 1
    Well cheers. Your answer meant it only took me 1 day of bruteforce. :) – Michael May 22 '20 at 14:50
  • As I remember, used browser dev tools and backend logs (request data) to understand that no cookies were sent. After I randomly (i think so, may be from google search) added needed param to first auth call. PS hehe - found that in profile my location is moon (: – kore666 Oct 05 '20 at 10:02
  • 5
    I owe you a big fat pint of beer. I've been banging my head against the desk for the past 3 hours trying to solve this issue. Thank you x1000. – Gligor Atanasovski Oct 14 '20 at 16:49
  • 3
    Also, don't be me. Make sure the fetch URL is 127.0.0.1 not localhost, or the Cookie won't be set. – Daniel Michaels Apr 01 '21 at 11:32
  • 1
    That's amazing! I would never have thought to try that. What's the rationale to also need to try and send a cookie on the first request? – HetMes Feb 23 '23 at 22:02
  • 1
    You are a legend. 2 days of bruteforce here as well until I read your answer. – geekymartian Jun 17 '23 at 04:24
34

If you are reading this in 2019, credentials: "same-origin" is the default value.

fetch(url).then
alextrastero
  • 3,703
  • 2
  • 21
  • 31
  • 5
    But note that not everyone is using a sufficiently updated browser. I came across this question because my own version of Firefox (60.x, the most recent in Debian Stretch) doesn't set that by default. – philh Oct 30 '19 at 13:34
19

Programmatically overwriting Cookie header in browser side won't work.

In fetch documentation, Note that some names are forbidden. is mentioned. And Cookie happens to be one of the forbidden header names, which cannot be modified programmatically. Take the following code for example:

  • Executed in the Chrome DevTools console of page https://httpbin.org/, Cookie: 'xxx=yyy' will be ignored, and the browser will always send the value of document.cookie as the cookie if there is one.
  • If executed on a different origin, no cookie is sent.
fetch('https://httpbin.org/cookies', {
  headers: {
    Cookie: 'xxx=yyy'
  }
}).then(response => response.json())
.then(data => console.log(JSON.stringify(data, null, 2)));

P.S. You can create a sample cookie foo=bar by opening https://httpbin.org/cookies/set/foo/bar in the chrome browser.

See Forbidden header name for details.

Iceberg
  • 2,744
  • 19
  • 19
5

Just adding to the correct answers here for .net webapi2 users.

If you are using cors because your client site is served from a different address as your webapi then you need to also include SupportsCredentials=true on the server side configuration.

        // Access-Control-Allow-Origin
        // https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api
        var cors = new EnableCorsAttribute(Settings.CORSSites,"*", "*");
        cors.SupportsCredentials = true;
        config.EnableCors(cors);
Mark Dornian
  • 318
  • 4
  • 9
2

This works for me:

import Cookies from 'universal-cookie';
const cookies = new Cookies();

function headers(set_cookie=false) {
  let headers = {
    'Accept':       'application/json',
    'Content-Type': 'application/json',
    'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
};
if (set_cookie) {
    headers['Authorization'] = "Bearer " + cookies.get('remember_user_token');
}
return headers;
}

Then build your call:

export function fetchTests(user_id) {
  return function (dispatch) {
   let data = {
    method:      'POST',
    credentials: 'same-origin',
    mode:        'same-origin',
    body:        JSON.stringify({
                     user_id: user_id
                }),
    headers:     headers(true)
   };
   return fetch('/api/v1/tests/listing/', data)
      .then(response => response.json())
      .then(json => dispatch(receiveTests(json)));
    };
  }
aarkerio
  • 2,183
  • 2
  • 20
  • 34
  • 1
    Thanks! This is exactly what I was trying to figure out!! `'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')` saved the day! – Andrew Aug 09 '22 at 00:17
2

If it still doesn't work for you after fixing the credentials.

I also was using the :

  credentials: "same-origin"

and it used to work, then it didn't anymore suddenly, after digging much I realized that I had change my website url to http://192.168.1.100 to test it in LAN, and that was the url which was being used to send the request, even though I was on http://localhost:3000.

So in conclusion, be sure that the domain of the page matches the domain of the fetch url.

Steve Moretz
  • 2,758
  • 1
  • 17
  • 31
  • 2
    Any ideas if it's port specific? Curious how this works when the host is the same but the port is different? (ie. UI server + backend server) – Ryan May 13 '22 at 19:16
  • Hello @Ryan Yes actually it may seem that ports aren't related to cors but they are : https://stackoverflow.com/a/19966782/10268067 – Steve Moretz May 13 '22 at 19:32
  • note, that also localhost is not the same as 127.0.0.1 - your backend needs to be on the exact same ip address than your frontend – hansaplast Mar 10 '23 at 07:13
1

My issue was my cookie was set on a specific URL path (e.g., /auth), but I was fetching to a different path. I needed to set my cookie's path to /.

spiffytech
  • 6,161
  • 7
  • 41
  • 57
0

If the fetch is not sending credentials even though the request is not intended to be a cross-origin call, it could be because the request is being processed as cross-origin due to differences in protocols between the origin of the request and location of the response.

I observed that my server was returning Location header with an http URL, while the connection was established over https. As a result, the browser treated the request as cross-origin and applied cross-origin rules. It's worth noting that Firefox (version 114.0.2) and Safari (version 16.1) didn't display any warnings in this scenario, but Chrome (version 114.0.5735.198) showed a (blocked:mixed-content) error, which helped in identifying the issue.

If anyone is interested, in this particular case, SSL termination was being performed in the reverse-proxy, but the gunicorn server was not correctly handling it due to misconfiguration, specifically related to the secure-scheme-headers and forwarded_allow_ips settings. After resolving these settings on the server side, fetch started working fine in all browsers.

ahaltindis
  • 338
  • 1
  • 6