Why is the browser not setting cookies after an AJAX request returns? Why is the browser not setting cookies after an AJAX request returns? ajax ajax

Why is the browser not setting cookies after an AJAX request returns?


OK, so I finally figured out the problem. It turns out that setting the Path option is important when sending cookies in an AJAX request. If you set Path=/, e.g.:

Set-Cookie:SessionId=foo; Path=/; HttpOnly

...then the browser will set the cookie when you navigate to a different page. Without setting Path, the browser uses the "default" path. Apparently, the default path for a cookie set by an AJAX request is different from the default path used when you navigate to a page directly. I'm using Go/Martini, so on the server-side I do this:

session.Options(session.Options{HttpOnly: true, Path:"/"})

I'd guess that Python/Ruby/etc. have a similar mechanism for setting Path.

See also: cookies problem in PHP and AJAX


@atomkirk's answer didn't apply to me because

  1. I don't use the fetch API
  2. I was making cross-site requests (i.e. CORS)

NOTE: If your server is using Access-Control-Allow-Origins:* (aka "all origins"/"wildcard origins"), you may not be able to send credentials (see below).

As for the fetch API; CORS requests will need {credentials:'include'} for both sending & receiving cookies

For CORS requests, use the "include" value to allow sendingcredentials to other domains:

fetch('https://example.com:1234/users', {               credentials: 'include' })

... To opt into accepting cookies from the server, you must use the credentials option.


{credentials:'include'} just sets xhr.withCredentials=true

Check fetch code

if (request.credentials === 'include') {      xhr.withCredentials = true } 

So plain Javascript/XHR.withCredentials is the important part.


If you're using jQuery, you can set withCredentials using $.ajaxSetup(...)

$.ajaxSetup({             crossDomain: true,             xhrFields: {                 withCredentials: true             }         });

If you're using AngularJS, the $http service config arg accepts a withCredentials property:

$http({    withCredentials: true});

If you're using Angular (Angular IO), the common.http.HttpRequest service options arg accepts a withCredentials property:

this.http.post<Hero>(this.heroesUrl, hero, {    withCredentials: true});

As for the request, when xhr.withCredentials=true; the Cookie header is sent

Before I changed xhr.withCredentials=true

  1. I could see Set-Cookie name & value in the response, but Chrome's "Application" tab in the Developer Tools showed me the name and an empty value
  2. Subsequent requests did not send a Cookie request header.

After the change xhr.withCredentials=true

  1. I could see the cookie's name and the cookie's value in the Chrome's "Application" tab (a value consistent with the Set-Cookie header).
  2. Subsequent requests did send a Cookie request header with the same value, so my server treated me as "authenticated"

As for the response: the server may need certain Access-Control... headers

For example, I configured my server to return these headers:

  • Access-Control-Allow-Credentials:true
  • Access-Control-Allow-Origin:https://{your-origin}:{your-port}

EDIT: this approach won't work if you allow all origins/wildcard origins, as described here (thanks to @ChandanBhattad) :

The CORS request was attempted with the credentials flag set, but the server is configured using the wildcard ("*") as the value of Access-Control-Allow-Origin, which doesn't allow the use of credentials.

Until I made this server-side change to the response headers, Chrome logged errors in the console like

Failed to load https://{saml-domain}/saml-authn: Redirect from https://{saml-domain}/saml-redirect has been blocked by CORS policy:

The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. Origin https://{your-domain} is therefore not allowed access.

The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

After making this Access-* header change, Chrome did not log errors; the browser let me check the authenticated responses for all subsequent requests.


If you're using the new fetch API, you can try including credentials:

fetch('/users', {  credentials: 'same-origin'})

That's what fixed it for me.

In particular, using the polyfill: https://github.com/github/fetch#sending-cookies