I have an EXTJS application on http://extjs.domain1.com and serverside Laravel application on http://domain2.com.
I noticed that some browsers (like Safari or old IE) has set very strict cookies privacy settings by default. In the end, if browser don't accept cookies, my laravel_session cookie cannot be saved and the user cannot log in.
My idea is to display a proper information for the user, only if the cookies cant be saved in current browser. So, on my ExtJS's app launch, after it's initialized, I'm sending a custom Ajax GET request to the server (from domain1.com to domain2.com) and the server returns me a Set-Cookie header with sample cookie.
If the cookie is saved, it would mean that the browser's settings are OK (please correct me if I'm wrong).
Now, my cookie's name is Time.
The ajax response in firebug looks like this:
HTTP/1.1 200 OK
Date: Wed, 03 Feb 2016 12:19:32 GMT
Server: Apache
Set-Cookie: Time=1454511121; expires=Wed, 03-Feb-2016 14:54:11 GMT; Max-Age=9279; path=/; laravel_session=LONG---SESSION---KEY; expires=Wed, 03-Feb-2016 14:19:32 GMT; Max-Age=7200; path=/; httponly
Cache-Control: no-cache
Access-Control-Allow-Origin: http://extjs.domain1.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Origin, Content-Type
Access-Control-Allow-Credentials: true
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json
I have tried many combinations.
Every time the
console.log(document.cookie)
returns an empty string.
The "Time" cookie is visible in firebug > Cookies and it is NOT httponly.
The "expires" of "Time" is greater than my current browser/computer date.
I tried setting "path" of cookie to '/' or empty string.
I tried setting the "domain" of cookie to ".domain1.com", "extjs.domain1.com".
What am I doing wrong? The document.cookie is always empty, but firefox shows me that there is a non-httponly cookie.
I noticed a little difference in chrome.
The response Set-Cookie looks like this:
Set-Cookie:Time=1454511121; expires=Wed, 03-Feb-2016 14:54:11 GMT; Max-Age=7715; path=/;
Set-Cookie:laravel_session=LONG---SESSION---KEY; expires=Wed, 03-Feb-2016 14:45:36 GMT; Max-Age=7200; path=/; httponly
There are two Set-Cookie headers, but I read that it should not be a problem.
In Chrome I can see the cookies in Dev tools > Network tab > xhr request > Cookies tab. I cannot see a cookie in Dev tools > Resources > Cookies > extjs.domain1.com
Related
We have the following configuration:
testing.parentdomain.com
When you access this domain and create a basket we create a cookie stored for the basket value. The cookie domain is set to .testing.parentdomain.com, it is Httponly and has a path of /
We have a subdomain to the above which would like to access the cookie. subdomain.testing.parentdomain.com
This sub domain makes a call to an endpoint on the parent domain such as: testing.parentdomain.com/basketData. This call is a GET request that returns JSON.
Issue
The issue is that the subdomain does not appear to send the cookie value when making the request and therefore we do not get the expected response.
Attempts
Looking at other questions we have tried CORS and credential changes.
As an additional note, we bundle the below JS with webpack/babel.
Our request is from AJAX as follows:
$.ajax({
url: url,
type: 'GET',
xhrFields: {
withCredentials: true
},
crossDomain: true
})
The server is setup with CORS for the subdomain and allow-crendtials. In the response we can see these are returned.
access-control-allow-credentials: true
access-control-allow-origin: subdomain from above
Is there any reason that the cookie is not sent with the request to the parent domain? We have logged out the cookies on the server side response and they are not there as we expect.
Request Headers
:authority: testing.parentdomain.com
:method: GET
:path: /basket/data/
:scheme: https
accept: /
accept-encoding: gzip, deflate, br
accept-language: en-GB,en;q=0.9,en-US;q=0.8
origin: https://subdomain.testing.parentdomain.com
referer: https://subdomain.testing.parentdomain.com/
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-site
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Response Headers
access-control-allow-credentials: true
Access-Control-Allow-Methods: GET, PUT, POST, DELETE, HEAD, OPTIONS
access-control-allow-origin: https://subdomain.testing.parentdomain.com
cache-control: no-cache, no-store
content-length: 2238
content-type: application/json; charset=utf-8
date: Tue, 03 Nov 2020 20:39:36 GMT
expires: -1
pragma: no-cache
server: Microsoft-IIS/10.0
set-cookie: AWSALB=N0bcThdgRFzrSfQVNIsffgsvY6T/y2Bp47RZJCueeSLOS7eEjo0AThiElXmww6fy2eynRyyt8gAB8di/Mqy1x+Ds8Ig1TumKkWnQiFvIkoELI/rEYYgyUxbEtUI4; Expires=Tue, 10 Nov 2020 20:39:36 GMT; Path=/
set-cookie: AWSALBCORS=N0bcThdgRFzrSfQVNIsffgsvY6T/y2Bp47RZJCueeSLOS7eEjo0AThiElXmww6fy2eynRyyt8gAB8di/Mqy1x+Ds8Ig1TumKkWnQiFvIkoELI/rEYYgyUxbEtUI4; Expires=Tue, 10 Nov 2020 20:39:36 GMT; Path=/; SameSite=None; Secure
status: 200
strict-transport-security: max-age=31536000;
vary: Origin
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-robots-tag: noindex
x-ua-compatible: IE=edge
x-xss-protection: 1; mode=block
Even if you are calling the main domain from a subdomain, this is considered a cross-origin request.
Quote from the RFC 6454 which qualifies the "Origin" term:
Q: Why use the fully qualified host name instead of just the "top-
level" domain?
A: Although the DNS has hierarchical delegation, the trust
relationships between host names vary by deployment. For example, at
many educational institutions, students can host content at
https://example.edu/~student/, but that does not mean a document
authored by a student should be part of the same origin (i.e.,
inhabit the same protection domain) as a web application for managing
grades hosted at https://grades.example.edu/.
So all of the things you did are indeed required to make it work:
access-control-allow-credentials: true
access-control-allow-origin: subdomain.testing.parentdomain.com (not a wildcard)
withCredentials: true in the request
The SameSite=None cookie attribute is not required in this case because a request from a subdomain to another subdomain of the same domain is considered "same site" (Source).
So just check that everything is correctly set, it should work as is.
At beginning of your question you stated:
The cookie domain is set to .testing.parentdomain.com
but in the logged server response:
set-cookie: AWSALBCORS=N0bcThdgRFzrSfQVNIsffgsvY6T/y2Bp47RZJCueeSLOS7eEjo0AThiElXmww6fy2eynRyyt8gAB8di/Mqy1x+Ds8Ig1TumKkWnQiFvIkoELI/rEYYgyUxbEtUI4; Expires=Tue, 10 Nov 2020 20:39:36 GMT; Path=/; SameSite=None; Secure
the Domain=.testing.parentdomain.com; parameter is clearly missing.
I don't know which programming language you are using to set the cookie, but I strongly suggest you to check the call you use to set the cookie in your server response.
So, I have two sites http://localhost/ and http://3rdPartyLocallyHostedAPI/ (Not the real names) - both are local intranet sites, and due to the nature of 3rdPartyLocallyHostedAPI being it's namesake, localhost is having to make CORS requests to it.
These requests are working fine, data is returned or can be posted to 3rdPartyLocallyHostedAPI as would be expected, however this warning is being shown:
A cookie associated with a cross-site resource at http://3rdPartyLocallyHostedAPI/ was set without the SameSite attribute. A future release of Chrome will only deliver cookies with cross-site requests if they are set with SameSite=None and Secure. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5088147346030592 and https://www.chromestatus.com/feature/5633521622188032.
Now, I've looked at multiple answers such as this one, this one and this one which state that the SameSite attribute needs to be set on the server, which doesn't make any sense as the two cookies it's taking issue with (ss-pid and ss-id) are set in the request, not returned in the response? This has confused me, as I can't figure out how or where to make a change to ensure the SameSite policy on these cookies is set to none or secure.
I think it's the jQuery that is performing the AJAX request that is at fault though:
// trimType and queryValue are determined elsewhere by some jQuery selections, their values are not important to the question being asked.
$.ajax({
url: 'http://3rdPartyLocallyHostedAPI?q=' + trimType + '?q=' + queryValue + '&resultsOnly=true',
data: {
properties: (trimType === 'Record') ? 'Title,Number,RecordRecordType' : 'NameString'
},
xhrFields: {
withCredentials: true
},
dataType: 'json'
}).done(function (data) {
if (data.Results.length > 0) {
$resultsPane.html('');
for (var i = 0; i < data.Results.length; i++) {
// Not relevant to the question being asked so removed, only some jQuery in here to display results on page.
}
} else {
$resultsPane.html('<p class="py-1 pl-1 list-group-item text-muted">No Results found.</p>');
}
}).fail(function () {
$resultsPane.html('<p class="py-1 pl-1 list-group-item text-muted">No Results found.</p>');
});
When it doesn't have the withCredentials = true property set, and therefore is authenticating anonymously against the API (which only gives limited access, hence the need to pass windows credentials), the SameSite warning does not appear. Here is the request header:
GET /CMServiceAPIAuth/Location?q=%22SDC%20*%22&resultsOnly=true&properties=NameString HTTP/1.1
Host: serverName
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
Origin: http://localhost:64505
Referer: http://localhost:64505/Home/DisplayRecord
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cookie: ss-pid=OQtDrnmok62FvLlZPnZV; ss-id=cIaIcS3j0jmoouAaHHGT
The two cookies that chrome is having issues with are ss-pid and ss-id, there are no cookies being passed back by the response headers:
HTTP/1.1 200 OK
Cache-Control: private,no-cache
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-IIS/8.5
X-Content-Type-Options: nosniff
X-Powered-By: ServiceStack/4.512 NET45 Win32NT/.NET
X-AspNet-Version: 4.0.30319
Access-Control-Allow-Origin: http://localhost:64505
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST,GET,OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Persistent-Auth: true
X-Powered-By: ASP.NET
Date: Mon, 27 Jul 2020 07:02:06 GMT
Content-Length: 1597
So, with all this in mind, can someone explain where I'm going wrong? Do I need to make changes to the jQuery AJAX to prevent this warning (and therefore prevent a future issue when the change the warning is alerting me about happens) - or, do I actually need to set an additional header on the server, I'm wondering if in the pre-flight OPTIONS request if it tries to figure out the SameSite setting for the request or something like that?
Via an IIS Module, I do have access to add additional headers to the response the server is sending, so if that is what is needed, I can do it - I just don't quite understand on which end the warning is being caused by and would appreciate any explanation people can provide.
Ok then, I think I've done enough research to figure out the issue I'm facing, so I'll answer my own question.
So, one page that really helped me to actually understand what SameSite is about was this one, so to anyone else having issues with SameSite, take a read so that you understand the reason behind it and how it works.
Having done some reading and seeing this answer to another post helped me to connect the dots. I deployed the web site I'm working on to an actual web server, and found that the following was the response header:
HTTP/1.1 200 OK
Cache-Control: private,no-cache
Content-Type: application/json; charset=utf-8
Vary: Accept
Server: Microsoft-IIS/10.0
X-Content-Type-Options: nosniff
X-Powered-By: ServiceStack/4.512 NET45 Win32NT/.NET
X-AspNet-Version: 4.0.30319
Set-Cookie: ss-pid=0QyVIKf4edkAKd2h4be5; expires=Fri, 27-Jul-2040 09:58:39 GMT; path=/; HttpOnly
Set-Cookie: ss-id=fmM1WQsDxXGfR8q9GL6e; path=/; HttpOnly
Access-Control-Allow-Origin: http://server
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST,GET,OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Persistent-Auth: true
WWW-Authenticate: Negotiate oYG2MIGzoAMKAQChCwYJKoZIgvcSAQICooGeBIGbYIGYBgkqhkiG9xIBAgICAG+BiDCBhaADAgEFoQMCAQ+ieTB3oAMCARKicARusah2q1K2ACHwoq1n6DCNq5rx/HFdbK5sU9EohnxrRSpzmelskTTa9xmW8wgeUdwRNQCqMsD/dZ/pUjhdl2CVWjmFZZAfnKl6JEker+s79E9uFXThZZKnqfpqEgSvvqSYpp1KMkaYBYd1uf5mRyE=
Date: Mon, 27 Jul 2020 09:58:40 GMT
Content-Length: 1597
There are two Set-Cookie headers being issued by the server to store values for ss-id and ss-pid. These cookies apparently stand for permanant session ID and session ID, and are issued by ASP.NET for tracking sessions. The browser does not accept and set these two cookies as they lack the SameSite=none setting and the Secure setting - these are the two cookies mentioned in the post above that I was talking about.
So to fix this issue, first off I need to switch to using https for the API (and potentially the site itself) - which I have already done, and somehow figure out how to get the 3rd party API to set SameSite attributes in it's session related cookies.
So for others who hopefully have complete control of your API's etc, you should just be able to set these attributes whenever you are creating/sending cookies in responses, and therefore send cookies from the site to other domains by setting SameSite=None and Secure.
I'm trying to get Chrome to cache my Javascript (the HTML includes a version number as a cache buster). So the idea is that if the version does not change I shouldn't need to reload 1MiB of Javascript but Chrome always seems to reload it anyway. I think I have the server returning the correct headers.
My html looks like:
<script type="application/javascript" async src="/index.js?version=123"></script>
Response Headers
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public
Content-Encoding: gzip
Content-Language: en-US
Content-Type: application/javascript
Last-Modified: Wed, 23 Jan 2019 15:09:36 GMT
Vary: Origin
Vary: Accept-Encoding
Date: Thu, 24 Jan 2019 00:52:23 GMT
Transfer-Encoding: chunked
I have the "disable Cache" un-selected in the Network tab and in the devTools settings I've un-selected "Disable cache (when devTools is running)
I can see that my fonts are cached in the network tab of devTools but my index.js still loads all 1MiB
It seems to be because I'm using https but my dev box server doesn't have a certificate. If I disable https and use http it does cache the Javascript.
I have problem authenticating CORS request in Chrome.
I've Single Page Application running on localhost and webservices running in Azure. I log in using using OpenIdConnect.
When I do CORS request in EDGE to my backend like this, authentication works:
$.ajax({
type: 'get',
url: buildBackendUrl("api/Account"),
xhrFields: { withCredentials: true }
});
however the same does not work in Chrome. When I enter the webservice url to browser manually, the request is authenticated.
I've examined request headers for CORS request and the difference is in cookies:
Edge: ARRAfinity=...; AspNetCore.Cookies=...
Chrome: ARRAfinity=...
Why Chrome does not include all cookies?
EDIT: here are request catched by fiddler:
REDIRECT when I press login:
myapp.azurewebsites.net/api/Account/login?returnUrl=http://localhost:46563/
Since I'm already logged in no need to go to login page. Redirect
myapp.azurewebsites.net/signin-oidc
REDIRECT BACK: localhost:46563/
CORS made from Localhost:
myapp-dev.azurewebsites.net/api/Account
In neither, request nr3 or 4 I don't see the cookies.
Anyway, response of request nr2 (myapp.azurewebsites.net/signin-oidc) tries to sets cookies:
HTTP/1.1 302 Found
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Expires: -1
Location: http://localhost:46563/
Set-Cookie: .AspNetCore.Correlation.OpenIdConnect.3ifhkwCQkMuZkTgBxYiKMOSoLgTX2nIex-8aH-syh5Q=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/signin-oidc; samesite=lax
Set-Cookie: .AspNetCore.OpenIdConnect.Nonce.CfDJ8FG-d2csck1FsQu2pwqnsxLd4w9YWobqchk1w3xMgy7bCX_KilCuRxuj4U0bSTAL-dD_iwdEaZI6pclqlP-3f7QBuKUMS379DFiBPd_tkEkyB_IYVWzJsR1xtw-_qcS1pQL6ial_C2ywbSwRucBxUqtDPMcuFEIomNDDnklpqWUmS_5Xb_tB23Ew7b14M861pL1CtJ18uPqgu-nOgn1RygqhBhMECoQfQ7YhXN_BtfiIbdPfw00jWNfMVc5G1B-SnT_eq80_RmxQ4_JOX3ZJfiI=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/signin-oidc; samesite=lax
Set-Cookie: .AspNetCore.Cookies=...; path=/; samesite=lax; httponly
Theres new draft on cookie policy, called SameSite, currently implemented by Chrome and Opera.
Basically, cookies marked with SameSite=Strict are not sent with CORS request event if you set xhr.withCredentials = true;
In order to make it work, you have to disable SameSite policy on particular cookie. In case of ASP.NET Core 2.0 authetication cookie it was:
services.AddAuthentication(...)
.AddCookie(option => option.Cookie.SameSite = SameSiteMode.None)
.AddOpenIdConnect(...)
(Note: This is a follow up to my question Can jQuery.getJSON put a domain's cookies in the header of the request it makes? and covers the XSS case of Setting a cookie in an AJAX request?)
I've been told I'm unable to set cookies to be read by other domains that are not subdomains of the current domain using $.cookie(..., ..., {domain: ...}). But in a comment on a response to my last question, #zanlok said "The server's reply, however, can definitely set a cookie" and it got two upvotes.
So I thought I'd try using a service which was created for the explicit purpose of setting cookies called Freebase's "touch" API. The call looks like:
$.getJSON("http://api.sandbox-freebase.com/api/service/touch",
{}, // URL parameters
afterCookieIsSetCallback); // Callback function
Looking in FireBug at the response header it's like this:
Date Wed, 24 Nov 2010 03:35:28 GMT
Server Apache
X-Metaweb-Cost [...]
Etag [...]
Expires Wed, 24 Nov 2010 03:35:29 GMT
Cache-Control no-store
Vary Accept-Encoding
Content-Encoding gzip
Set-Cookie mwLastWriteTime=1290569730|10325_9202a8c04000641f80000000199eff96|sandbox; expires=Thu, 25-Nov-2010 03:35:28 GMT; Path=/
Last-Modified Wed, 24 Nov 2010 03:35:28 GMT
Content-Length 134
Content-Type text/plain; charset=utf-8
X-Cache MISS from cache01.sandbox.sjc1.metaweb.com
Connection keep-alive
X-Metaweb-TID cache;cache01.sandbox.sjc1:8101;2010-11-24T03:35:28Z;0001
So there's definitely a Set-Cookie in there, and the script runs the response handler. Yet the cookie is not present in the request headers for later JSON requests this script makes to .sandbox-freebase.com.
(By contrast, simply typing the touch api URL into the address bar and loading it that way does set the cookie for future requests. That applies even in other tabs.)
This seems to be a deviation from a prior "expected behavior", because there was a toolkit published by MetaWeb circa "2007-2009" which seemed to think such an approach could work:
http://www.google.com/codesearch/p?hl=en#v099O4eZ5cA/trunk/src/freebase/api.js&q=touch%20package:http://mjt%5C.googlecode%5C.com&l=340
Without knowing much about it, I'm wondering if it was a recent change that Firefox adopted and then WebKit followed suit. Perhaps the one mentioned here:
http://trac.webkit.org/browser/trunk/WebCore/xml/XMLHttpRequest.cpp#L856
So is there any canonical documentation on this particular issue?
The AJAX call you are making, is making a request to a domain outside of the domain of the top level url(the url in the address bar). This results in it being a 3rd party cookie, by default Internet explorer won't persist a 3rd party cookie. Meaning that the cookie will come back in the Set-Cookie header on the first request, but subsequent requests that you make to that server will not have that cookie sent in the request.
Like you said, if you go directly to the url in your browser it works. This is because in this case it's a first party cookie.
In order for IE to accept 3rd party cookie's the server that is sending the SET-COOKIE header on it's response, must also have a P3P Policy Header set.
Here is an example, when you navigate to CNN, you will notice one of the requests it makes is to a domain name of b.scorecardresearch.com, scorecardresearch is dropping a tracking cookie, but this cookie is considered a 3rd party cookie. So in order to make it work they had to also in include a p3p header, see headers below:
HTTP/1.1 200 OK
Content-Length: 43
Content-Type: image/gif
Date: Thu, 02 Dec 2010 19:57:16 GMT
Connection: keep-alive
Set-Cookie: UID=133a68a4-63.217.184.91-1288107038; expires=Sat, 01-Dec-2012 19:57:16 GMT; path=/; domain=.scorecardresearch.com
P3P: policyref="/w3c/p3p.xml", CP="NOI DSP COR NID OUR IND COM STA OTC"
Expires: Mon, 01 Jan 1990 00:00:00 GMT
Pragma: no-cache
Cache-Control: private, no-cache, no-cache=Set-Cookie, no-store, proxy-revalidate
Server: CS
If you were to copy this header and add it to the response, you would notice that the cookie's start working,
P3P: policyref="/w3c/p3p.xml", CP="NOI DSP COR NID OUR IND COM STA OTC"
It's best that you craft a P3P header specific for your business, but the above should work for testing purposes.
If I correctly understand you, you are wondering why the server sends Set-Cookie only on the first request. If that is true, then it's by design - take a look here:
http://en.wikipedia.org/wiki/HTTP_cookie
Set-Cookie is like a setter - the server sends it for the browser to cache it locally. It can send every time, but there is no need to do that, so it will send it again only if it needs to change the value stored locally.
Browser, on the other hand, will send Cookie header every time with the contents set by the last issued Set-Cookie from the server.