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(...)
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.
When we log in google. It set cookies for both Google and youtube domain.
In the network, I see two interesting get call,
https://mail.google.com/accounts/SetOSID?authuser
https://accounts.youtube.com/accounts/SetSID.
How can I achieve the same results?
I have to set a cookie for a website like youtube.com from a website like google.com.
I tried making a XMLHttpRequest(). It didn't work. But If I open my second domain in a separate tab, It set the cookie for the second domain.
My request header :
Request URL: https://mysite.ngrok.io/SetSID?params=xyz
Request Method: GET
Status Code: 302 Found
Remote Address: xx.xx.xxx.xxx:xxx
Referrer Policy: no-referrer-when-downgrade
My Response Header :
Access-Control-Allow-Headers: X-Requested-With
Access-Control-Allow-Origin: *
Content-Length: 184
Content-Type: text/plain; charset=utf-8
Date: Wed, 09 May 2018 16:29:34 GMT
Location: https://mysite.ngrok.io/SetSID/callback?params=xyz
set-cookie: _cookie=sample; Path=/; HttpOnly
Vary: Accept
X-Powered-By: Express
If we inspect the console, the request type is binary.
Did it somehow help them achieve the current goal ?
This API is hit
https://myaccount.google.com/accounts/SetOSID?authuser=1&continue=https%3A%2F%2Faccounts.youtube.com%2Faccounts%2FSetSID
followed by the api
https://accounts.youtube.com/accounts/SetSID?ssdc=1&sidt=xxx&continue=https://mail.google.com/mail/u//?authuser
If I am not wrong, Google is loading the https://accounts.youtube.com in a split second and setting up the cookies. It then continue to mail.google.com. (see the params in continue)
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
I have problem with accessing Facebook graph. I am using a Greasemonkey script. When I use the same script in chrome's Tampermonkey it works well, and I can get data.
In Firefox nothing happens, I think maybe it's because of cross-domain restrictions. Am I right, and is there a way this can be solved?
Forgot to mention, in Firefox only works if I am on graph.facebook.com.
edit:
var my_id = 1111111111;
var req = new XMLHttpRequest();
req.open('GET', 'https://graph.facebook.com/'+my_id, false);
req.send();
var contents = req.responseText;
alert(contents);
XMLHttpRequest does not support cross-domain requests. (You say this works in Tampermonkey though??!? Tampermonkey supports GM_xmlhttpRequest() so it might have extended cross-domain XHR to XMLHttpRequest(), maybe.)
Nevertheless, to get this to work in Greasemonkey (and Chrome userscripts, and Tampermonkey), you need to use GM_xmlhttpRequest() -- which allows cross domain requests.
So the code from the question would become:
var my_id = 1111111111;
GM_xmlhttpRequest ( {
method: 'GET',
url: 'https://graph.facebook.com/' + my_id,
onload: function (responseDetails) {
var contents = responseDetails.responseText;
alert (contents);
}
} );
Note that GM_xmlhttpRequest() operates asynchronously. (It has a somewhat dicey synchronous mode but that is not recommended.)
If you are using XMLHttpRequest to POST information then this is highly likely to be caused by the Same Origin Policy (MDN, Wikipedia).
If the page you're on doesn't match the 1) protocol (http) 2) domain and 3) port of the place you're sending information to then Firefox and others will likely block the request. (Which is why it only works when you're on graph.facebook.com.)
Since Facebook is unlikely to support cross origin resource sharing on your behalf, you're probably out of luck.
Cookies and other "ambient authentication" are sent along with POST requests so the a reason browsers enforce this type of policy is to prevent a bookmarklet or Greasemonkey script from being able to send your cookies to a server behind the domain that gave them to you.
Based on observation Facebook Graph does allow CORS because when I tried GET-ing it I was returned status 200 and the header had Access-Control-Allow-Origin:* *
Using Firebug:
The request:
xhr=new XMLHttpRequest();
xhr.open('get','https://graph.facebook.com/id',true);
xhr.send();
The headers:
Response Headers
Access-Control-Allow-Origin *
Cache-Control private, no-cache, no-store, must-revalidate
Connection keep-alive
Content-Length 181
Content-Type text/javascript; charset=UTF-8
Date Tue, 19 Jun 2012 22:41:46 GMT
Etag "some random string"
Expires Sat, 01 Jan 2000 00:00:00 GMT
Pragma no-cache
X-FB-Debug some random string
X-FB-Rev some random number
Request Headers
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding gzip, deflate
Accept-Language en-us,en;q=0.5
Connection keep-alive
DNT 1
Host graph.facebook.com
Origin http://localhost:8080
Referer http://localhost:8080/login
User-Agent Mozilla/5.0 (X11; Linux x86_64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1