I've followed the following steps:
Get the server to allow cross domain calls (with all the headers and stuff) This works
Test the server with some cross domain calls This works
Get the server to force a certificate This works
Go to a file on the server with a browser, choose the right certificate and see the file Still works
Now we get to the nice part
Combine the cross domain calls with the certificate <-- this does not work
Problem
I am getting the certificate request from the browser, but when I select the same certificate as I do when using the browser, the call is made but I get a 403 Forbidden.
Code
$.ajax({
type: "POST",
xhrFields: {withCredentials: true},
dataType: "xml",
contentType: "text/xml; charset=\"utf-8\"",
url: "https://www.myOtherServer.com/testfile.asp",
});
Any ideas?
Edit
The Access-Control-Allow-Credentials: true and the Access-Control-Allow-Origin are properly configured.
Additional information
I'm starting to think that it has something to do with the content type. When I change it to "text/html" I get a 415 error, but I do really need to send xml because it is a SOAP server.
Response headers
Access-Control-Allow-Cred... true
Access-Control-Allow-Head... Content-Type, Origin, Man, Messagetype, Soapaction, X-Test-Header
Access-Control-Allow-Meth... GET,POST,HEAD,DELETE,PUT,OPTIONS
Access-Control-Allow-Orig... https://www.mywebsite.com
Access-Control-Max-Age 1800
Cache-Control private
Content-Length 5561
Content-Type text/html; charset=utf-8
Date Wed, 19 Dec 2012 15:06:46 GMT
Server Microsoft-IIS/7.5
X-Powered-By ASP.NET
Request headers
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language nl,en-us;q=0.7,en;q=0.3
Access-Control-Request-He... content-type
Access-Control-Request-Me... POST
Cache-Control no-cache
Connection keep-alive
Host myhoast.com
Origin https://www.mywebsite.com
Pragma no-cache
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0
My best guess is that this is a problem not with your Javascript but with your CORS configuration. Did you set up your server with the Access-Control-Allow-Credentials: true header? http://www.w3.org/TR/cors/#access-control-allow-credentials-response-header
Also note that, even when the allow-credentials header is set, the browser will not allow responses to credentialed requests if Access-Control-Allow-Origin is *, according to these docs: https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control#Requests_with_credentials.
Edit: Since the OP has the CORS headers set up properly, the problem seems to be that the server is rejecting OPTIONS requests with a 403 status code. OPTIONS requests (known as the "preflight request") are sent before certain cross-domain requests (such as POSTs with application/xml content types), to allow the server to notify the browser of what types of requests are allowed. Since the browser doesn't see the 200 response that it expects from the OPTIONS request, it doesn't fire the actual POST request.
basicly we just have to write on htaccess
Header set Access-Control-Allow-Origin “*”
but when we need cookie etc, we had to add script on your ajax code and htaccess
i write about cross domain XHR on my blog, blog.imammubin.com/cross-domain-xhr/2014/05/28/ (Edit: site no longer exists)
hope this help..
Related
I am trying to understand how the whole CORS policy works. To explain my confusion, let me give you an example:
$.get("https://www.google.com", function(response) { alert(response) });
The above request will return with the following error:
XMLHttpRequest cannot load https://www.google.com/. Redirect from 'https://www.google.com/' to 'https://www.google.ca/?gfe_rd=cr&ei=TlqUWeGEH5HRXqW6utgI' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://fiddle.jshell.net' is therefore not allowed access.
Now in order for that to work, google would have to white-list https://fiddle.jshell.net.
Now, if I were to try the same thing on a restful API page, that will work. My question is really simple, Why?
Trying to analyze this, I tried hitting an API and analyzing its response:
https://apigee.com/console/bing?req=%7B%22resource%22%3A%22web_search%22%2C%22params%22%3A%7B%22query%22%3A%7B%22query%22%3A%22sushi%22%2C%22sources%22%3A%22web%22%7D%2C%22template%22%3A%7B%22format%22%3A%22json%22%7D%2C%22headers%22%3A%7B%7D%2C%22body%22%3A%7B%22attachmentFormat%22%3A%22mime%22%2C%22attachmentContentDisposition%22%3A%22form-data%22%7D%7D%2C%22verb%22%3A%22get%22%7D
Response:
HTTP/1.1 200
Date:
Wed, 16 Aug 2017 14:31:32 GMT
Content-Length: 266
Connection: keep-alive
Content-Type: application/json; charset=utf-8
Server: Apigee Router
X-Content-Type-Options: nosniff
I came to conclusion that it must be the headers. Specifically I belive that it is this header: Content-Type: application/json; But I don't know for sure, I am trying to understand this and hoping somebody here can explain to me.
So I did 2 tests: running your code $.get("https://www.google.com", function(response) { alert(response) }); snippet from the console and requesting https://www.google.com from https://apigee.com/console/others
I think what happens in the 1st case is the fact that the request is done from the client, next request headers are sent:
:authority:www.google.com
:method:GET
:path:/?_=1502896196820
:scheme:https
accept:*/*
accept-encoding:gzip, deflate, br
accept-language:en-US,en;q=0.8
origin:https://stackoverflow.com
referer:https://stackoverflow.com/questions/45717044/understanding-page-response
user-agent:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3187.0 Safari/537.36
x-chrome-uma-enabled:1
x-client-data:CJG2yQEIo7bJAQiMmMoBCKudygEIs53KAQjRncoBCKiiygE=
Since Google does not reply with 'Access-Control-Allow-Origin: *' - client, and in the request I have origin:https://stackoverflow.com, Chrome in my case throws CORS error.
In the 2nd test, using https://apigee.com/console/others and requesting https://www.google.com , apigee.com seems to overwrite headers and sends:
GET / HTTP/1.1
Host:
www.google.com
X-Target-URI:
https://www.google.com
Connection:
Keep-Alive
Also, from DEV console, I can see it does server to server call so no client involved in throwing CORS, thus I am getting the responses with Google page.
UPDATE:
Regarding JSON API requests, here is some interesting info from Google CloudPlatform about CORS
Note: CORS configuration applies only to XML API requests. For JSON
API requests, Cloud Storage returns the Access-Control-Allow-Origin
header with the origin of the request.
Thus, if the request is performed from the client, a client should not throw CORS errors since it gets Access-Control-Allow-Origin with the same origin it sent.
However, different APIs and clients might process requests differently. Thus, sometimes Firefox throws CORS while Chrome does not.
I am trying to add a custom header to my angular js GET request as below:
$http({
method : 'GET',
url : s,
headers : {
"partnerId" : 221,
"partnerKey" : "heeHBcntCKZwVsQo"
}
})
But the issue is the headers are getting added to Access-Control-Request-Headers as below and I am getting 403 Forbidden response:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:50.0)
Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/ *;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: GET
Access-Control-Request-Headers: partnerid,partnerkey
Origin: http://localhost:8080
DNT: 1
Connection: keep-alive
I also tried below changes but no luck
return $http.get(s, {
headers : {
'partnerId' : 221,
'partnerKey': 'heeHBcntCKZwVsQo'
}
})
In other related answers in SO I saw that the header partnerId and partnerKey need to be enabled in server side. But I am able to add these custom headers in POSTMAN client and other POST clients and able to get the expected response. So I guess I am missing something. Can someone guide me in this. Thanks in advance
Edit: One more thing I noted is that partnerId is replaced as partnerid while passing in the request. Not sure if that makes a difference.
If you add any headers to a scripted cross-origin request other than any CORS-safelisted request-headers, it triggers browsers to first do a CORS preflight request.
There is no way to prevent users’ browsers from doing that CORS preflight (though there are ways to get around it locally in your own browser when doing testing; for example, by using Postman).
So for users to be able to use a Web app of yours that makes scripted cross-origin requests with custom headers, the server to which those cross-origin requests go needs to be CORS-aware.
The reason Postman can make such requests without causing a preflight is, Postman’s not a browser engine—it's an extension that’s not restricted by the same-origin policy, so doesn’t need CORS.
Postman can basically do whatever curl or other such tools can do, but just within a browser UI for convenience. It’s otherwise bypassing normal Web-security features built into browsers.
I got this code:
var req = new HttpRequest();
req.open("POST", "http://localhost:8031/rest/user/insert");
req.setRequestHeader("Content-type", "application/json");
req.send(json.stringify(user_map));
But, instead of sending the POST verb, when I see it in fiddler I see this:
OPTIONS http://localhost:8031/rest/user/insert HTTP/1.1
Host: localhost:8031
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: http://127.0.0.1:3030
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 (Dart) Safari/537.33
Access-Control-Request-Headers: origin, content-type
Accept: */*
Referer: http://127.0.0.1:3030/E:/grole/dart/Clases/Clases/web/out/clases.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: es-ES,es;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
As you can see, it is using the OPTIONS verb instead of POST?
What's going on?
The OPTIONS verb is a preflight request sent by some browsers to check the validity of cross origin requests. It pretty much checks with the server that the Origin(requester) is allowed to make the request for a specified resource. Also, depending on which headers are sent back by the server it lets the browser know which headers, methods, and resources the origin is allowed to request form the server.
The browser sends the OPTIONS request then if the server answers back with the correct headers (CORS headers) allowing the origin to make the request, you should see your POST request go through afterwards.
Note that the CORS headers must be returned on both the OPTIONS response as well as the POST response. This means your server must be able to respond to the options method on the routes you want to access across domains.
This is known as Cross-origin Resource Sharing. Mozilla has some pretty good documentation on the subject. https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS
If you have more questions let me know and I'll answer them.
One way to avoid this problem is by sending the request payload without custom headers and using formData to setup your request payload.
Here's the jQuery documentation:
http://api.jquery.com/load/
As mentioned there as an additional note:
Due to browser security restrictions, most "Ajax" requests are subject
to the same origin policy; the request can not successfully retrieve
data from a different domain, subdomain, or protocol.
Is there any way to bypass this limitation?
One way is to create a proxy page that requests the external page on the server. The implementation is dependent on the technology being used, but the idea is that you can then make an ajax call to your proxy page instead which will be on the same domain as the calling page.
Yes, there are several ways to get around this. But I would strongly suggest CORS (Cross-Domain Resource Sharing). CORS is the emerging standard for addressing cross-domain Ajax. This is replacing approaches such as JSONP (which has known security issues).
Unfortunately, CORS is not supported by older IE versions (6-9), but there are established polyfills for browsers which do not natively implement CORS. easyXDM is one such polyfill.
At a basic level, below is a sample request (from the MDN link above), where the browser generates a request with the Origin header, indicating the domain of the request, which is validated by the endpoint:
GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2
An Ajax endpoint supporting CORS may respond with the appropriate headers, which the browser validates. See Access-Control-Allow-Origin and Access-Control-Allow-Credentials in this example:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
The browser may also send a pre-flight (using the OPTIONS verb) which can indicate to the server what headers or verbs the Ajax call will use. The pre-flight is actually a separate call from the primary Ajax call, but is only invoked under certain conditions.
Many web servers (IIS, Apache) support CORS through modules. The servers will require configuration to white-list origins, allowed verbs, headers, etc. Wildcarding can also be used but is not recommended.
i am including JS on domain1 form domain2
<script type="text/javascript" src="http://www.domain2.com/script.js"></script>
that script doesn onload and on button click a JSONP request to domain2
$.getJSON( 'http://www.domain2.com/process?callback=?',
function(data){
if ( data ) processData( data );
}
);
and then displaying the data on domain1.
So here is my problem:
The getJSON request doesnt send cookies to the domain2.
The weirdest thing is that it does send the cookies half a day and the other half not. :-)
This is how the request looks like when it doesnt work:
Request details
GET /ajax/embed-user-library?detail=98&callback=jsonp1312398534998 HTTP/1.1
User-Agent: Opera/9.80 (Windows NT 6.1; U; en) Presto/2.9.168 Version/11.50
Host: www.floowie.com
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en,sk-SK;q=0.9,sk;q=0.8
Accept-Encoding: gzip, deflate
Referer: http://www.sokker.cz/en/test2
Connection: Keep-Alive
Response details
HTTP/1.1 200 OK
Date: Wed, 03 Aug 2011 19:06:51 GMT
Server: Apache/2.2.16 (Debian)
X-Powered-By: PHP/5.3.5-0.dotdeb.1
Set-Cookie: SESSID=64292b70dc28d7c6c9f13f70070353d8; path=/; domain=.floowie.com
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Content-Length: 34
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: application/json
And this when it works(nothing changed in the scripts):
Request details
GET /ajax/embed-user-library?detail=99&test=1&callback=jsonp1312398534999 HTTP/1.1
User-Agent: Opera/9.80 (Windows NT 6.1; U; en) Presto/2.9.168 Version/11.50
Host: test1.floowie.com
Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1
Accept-Language: en,sk-SK;q=0.9,sk;q=0.8
Accept-Encoding: gzip, deflate
Referer: http://www.sokker.cz/en/test2
Cookie: __utma=254918925.1489796832.1301725317.1312260335.1312298033.44; __utmz=254918925.1312298033.44.11.utmcsr=sokker.cz|utmccn=(referral)|utmcmd=referral|utmcct=/en/test2; lang=en; FLWSESSID=ddd1bc696f83f5a70b5f0f3ae30b4691; __utma=121955676.1030804516.1282595153.1312390656.1312397285.194; __utmb=121955676.8.10.1312397285; __utmc=121955676; __utmz=121955676.1312397285.194.21.utmcsr=floowie.crmserver.cz|utmccn=(referral)|utmcmd=referral|utmcct=/index.php
Connection: Keep-Alive
Response details
HTTP/1.1 200 OK
Date: Wed, 03 Aug 2011 19:07:45 GMT
Server: Apache/2.2.16 (Debian)
X-Powered-By: PHP/5.3.5-0.dotdeb.1
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Cache-Control: no-cache, must-revalidate
Pragma: no-cache
Content-Length: 20
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: application/json
Did someone see such a behaviour?
Is it solvable?
Thank you
If you want to use AJAX petitions over different domains/subdomains you have to implement Cross Origin Requests.
References:
http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
https://developer.mozilla.org/en/http_access_control
Examples:
http://arunranga.com/examples/access-control/
Your server needs to send this headers:
Access-Control-Allow-Origin: test1.floowie.com
Access-Control-Allow-Credentials: true // allow cookie/session credentials
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
You can return the Access-Control-Allow-Origin globally or set specifically dependent of your input Origin ($_SERVER['HTTP_ORIGIN']) request header. Also apply for Access-Control-Allow-Methods.
You must implement the OPTIONS petition. Before the first AJAX call, modern browsers call that URL with an OPTIONS method to retrieve the above headers.
Ok this is the first part, the second is with jQuery. Read very carefully this page: http://api.jquery.com/jQuery.ajax/
You will need to add some options to every AJAX call, you can do it globally:
$(document).ajaxSend(function (event, xhr, settings) {
settings.xhrFields = {
withCredentials: true
};
});
Or specific:
$.ajax({
url: a_cross_domain_url,
xhrFields: {
withCredentials: true
}
});
This issue made me lose many hours... hope it helps.
Note that you won't need to set your cookie domain as ".floowie.com" if you want.
You must properly implement CORS requests with credentials to send and receive cookies via Ajax. See developer.mozilla.org, specifically under the section titled "Requests with credentials."
First off, here is a simple CORS Ajax request with credentials, using jQuery 1.5.1+:
$.ajax({
url: "http://www.domain2.com/process",
xhrFields: {
withCredentials: true
}
}).done(function (data) { console.log(data); });
Note the withCredentials flag in the xhrFields. This flag tells the browser to send cookies with the request for the external domain, not the origin domain. In your case, cookies for www.domain2.com will be sent, and you will have access to them server-side.
On the server-side, you need to add certain headers to the response:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: www.domain1.com
Important: requests with credentials cannot set the Access-Control-Allow-Origin header to global (Access-Control-Allow-Origin: *). It must specify domains (Access-Control-Allow-Origin: www.domain1.com).
It's obviously better if you specify a domain for the Access-Control-Allow-Origin header. But if you don't know or care where the CORS request is coming from, you could use the Origin header from the request and simply set the Access-Control-Allow-Origin header of your response to that. In C#, this is how we did this:
this.Response.AddHeader("Access-Control-Allow-Origin", this.Request.Headers["Origin"]);
After doing all of this, cookies that you set server-side will be sent back with the response, and the browser will be able to properly handle them and insert them into the browser's cookie store for www.domain2.com. And any subsequent CORS requests you send will send these cookies in the request as well.
If you are sending a request other than with the GET, POST, or HEAD methods, you will need to implement Preflighted requests (see under section titled "Preflighted requests"):
Unlike simple requests (discussed above), "preflighted" requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests are preflighted like this since they may have implications to user data. In particular, a request is preflighted if:
It uses methods other than GET, HEAD or POST. Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.
It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)
Side-note about IE8 and IE9:
The Ajax call above will fail in IE8 and 9. I included the JS file from MoonScript/jQuery-ajaxTransport-XDomainRequest on my page, and this automagically allowed CORS requests to work in those old IE versions. But sadly, the XDomainRequest object that MS created for IE8 and 9 does not allow cookies to be sent or received. (see this MSDN blog post for more information)
You have different hosts. In the first example the host is "Host: www.floowie.com". In the second it is "Host: test1.floowie.com".
I'm guessing that the cookies are originally set by 'test1.floowie.com' and you haven't specified that they should be available to '.floowie.com' (i.e. the whole domain and all subdomains).
Can you post the code that sets the cookies in the first place?
If you get this fixed, it should at least show consistent behaviour. However, IE will probably still not pass cookies across subdomains. That's what I'm wrestling with at the moment, which is how I can across your question.