Why is the browser not setting cookies after an AJAX request returns?
AjaxCookiesAjax Problem Overview
I am making an ajax request using $.ajax. The response has the Set-Cookie
header set (I've verified this in the Chrome dev tools). However, the browser does not set the cookie after receiving the response! When I navigate to another page within my domain, the cookie is not sent. (Note: I'm not doing any cross-domain ajax requests; the request is in the same domain as the document.)
What am I missing?
EDIT: Here is the code for my ajax request:
$.post('/user/login', JSON.stringify(data));
Here is the request, as shown by the Chrome dev tools:
Request URL:https://192.168.1.154:3000/user/login
Request Method:POST
Status Code:200 OK
Request Headers:
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:35
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
DNT:1
Host:192.168.1.154:3000
Origin:https://192.168.1.154:3000
Referer:https://192.168.1.154:3000/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36
X-Requested-With:XMLHttpRequest
Form Data:
{"UserId":"blah","Password":"blah"}:
Response:
Response Headers:
Content-Length:15
Content-Type:application/json; charset=UTF-8
Date:Sun, 16 Mar 2014 03:25:24 GMT
Set-Cookie:SessionId=MTM5NDk0MDMyNHxEdi1CQkFFQ180SUFBUkFCRUFBQVRfLUNBQUVHYzNSeWFXNW5EQXNBQ1ZObGMzTnBiMjVKWkFaemRISnBibWNNTGdBc1ZFcDNlU3RKVFdKSGIzQlNXRkkwVjJGNFJ6TlRVSHA0U0ZJd01XRktjMDF1Y1c1b2FGWXJORzV4V1QwPXwWf1tz-2Fy_Y4I6fypCzkMJyYxhgM3LjVHGAlKyrilRg==; HttpOnly
Ajax Solutions
Solution 1 - Ajax
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: https://stackoverflow.com/questions/6353782/cookies-problem-in-php-and-ajax
Solution 2 - Ajax
@atomkirk's answer didn't apply to me because
- I don't use the
fetch
API - 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 sending
> credentials 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 (remember to use crossDomain: true
) 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
As for the request, when xhr.withCredentials=true
; the Cookie header is sent
Before I changed xhr.withCredentials=true
- 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
- Subsequent requests did not send a
Cookie
request header.
After the change xhr.withCredentials=true
- 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).
- 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.
Solution 3 - Ajax
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
Solution 4 - Ajax
This may help somebody randomly falling across this question.
I found forcing a URL with https:// rather than http:// even though the server hasn't got a certificate and Chrome complains will fix this issue.
Solution 5 - Ajax
In my case, the cookie size exceeded 4096 bytes (Google Chrome). I had a dynamic cookie payload that would increase in size.
Browsers will ignore the set-cookie
response header if the cookie exceeds the browsers limit, and it will not set the cookie.
See here for cookie size limits per browser.
I know this isn't the solution, but this was my issue, and I hope it helps someone :)