I want to make an XMLHttpRequest to a secure uri (https://site.com/ajaxservice/) from javascript running inside a nonsecure page (http://site.com/page.htm). I've tried all kinds of nutty stuff like iframes and dynamic script elements, so far no go. I know I am violating 'same origin policy' but there must be some way to make this work.
I will take any kind of wacky solution short of having the SSL protocol written in javascript.
That won't work by default due to the same origin policy, as you mentioned. Modern browsers are implementing CORS (Cross-Origin Resource Sharing) which you could use to get around this problem. However this will only work in Internet Explorer 8+, Firefox 3.5+, Safari 4+, and Chrome, and requires some server-side work. You may want to check out the following article for further reading on this topic:
Cross-domain Ajax with Cross-Origin Resource Sharing by Nicholas C. Zakas
You can also use JSONP as Dan Beam suggested in another answer. It requires some extra JavaScript work, and you may need to "pad" your web service response, but it's another option which works in all current browsers.
You can't circumvent cross-domain origin with XHR (well, only in Firefox 3.5 with user's permission, not a good solution). Technically, moving from port 80 (http) to 443 (https) is breaking that policy (must be same domain and port). This is the example the specification itself sites here - http://www.w3.org/Security/wiki/Same_Origin_Policy#General_Principles.
Have you looked into JSONP (http://en.wikipedia.org/wiki/JSON#JSONP) or CSSHttpRequests (http://nb.io/hacks/csshttprequest)?
JSONP is a way to add a <script> tag to a page with a pre-defined global callback across domains (as you can put the <script>s src to anywhere on the web). Example:
<script>
function globalCallback (a) { /* do stuff with a */ }
And then you insert a <script> tag to your other domain, like so:
var jsonp = document.createElement('script');
json.setAttribute('src','http://path.to/my/script');
document.body.appendChild(jsonp);
</script>
And in the source of the external script, you must call the globalCallback function with the data you want to pass to it, like this:
globalCallback({"big":{"phat":"object"}});
And you'll get the data you want after that script executes!
CSSHttpRequests is a bit more of a hack, so I've never had the need to use it, though feel free to give it a try if you don't like JSONP, :).
You said you would take anything short of having the SSL protocol written in JavaScript... but I assume you meant if you had to write it yourself.
The opensource Forge project provides a JavaScript TLS implementation, along with some Flash to handle cross-domain requests:
http://github.com/digitalbazaar/forge/blob/master/README
Check out the blog posts at the end of the README to get a more in-depth explanation of how it works.
Related
Let's consider the following scenario:
a user loads https://foo.com/index.html in one of the modern browsers which allow CORS.
index.html loads a javascript from https://bar.com/script.js via the script tag.
considering a hypothetical situation where this script.js is never cached and the content of script.js has changed.
script.js makes an XHR request to https://baz.com
https://baz.com has enabled Access-Control-Allow-Credentials: * thus this XHR made by script.js goes through.
important user information now can be passed to https://baz.com which is a security risk.
Prior to CORS, XHR calls were strictly followed same-origin policy and thus calls to https://baz.com from https://foo.com would not be permitted by the browsers.
I am wondering if there is a way for https://foo.com/index.html to specify a list of XHR permissible domain names so that the above scenario would not be possible.
Any pointer is highly appreciated.
[UPDATED]
I guess I have found the answer to my question.
Thank you for being considerate 🙏
Best!
I guess I found the answer to my question.
Using connect-src directive of the Content-Security-Policy Header https://foo.com/ can restrict the XHR, fetch calls along with WebSocket, EventSource, <a> ping to desired domains.
Content-Security-Policy: connect-src <source> <source>;
More information at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src
I once thought of deleting my question but someone else like me can be benefited.
CORS blocking is done on the server side, where the response headers are set to allow requests from certain origins or not.
If bar.com/script.js fetches data from baz.com, then baz.com should have the CORS restriction.
I have a http:// site that needs to access a 3rd party JSON API that is exposed on an https:// site. I've read through Ways to circumvent the same-origin policy, but it seems the methods described there aren't appropriate for me:
The document.domain method - only works on subdomains.
The Cross-Origin Resource Sharing method - requires server cooperation.
The window.postMessage method - seems to require opening a popup window?
The Reverse Proxy method - A possible solution, but seems a bit too hard to setup.
http://anyorigin.com - seems to not support SSL.
Is this it? Must I implement solution 4, which seems rather complicated, or am I missing something?
Sorry, it seems that anyorigin.com does support https.
The reason I naively thought it doesn't, is because the API in question returns JSON, and I thought I would actually just get a plain text response (as in my tests with using anyorigin.com on google.com). When it returned just an object, I figured something was broken.
It appears the object simply returns the parsed JSON, so I'm good to go!
Update - anyorigin.com stopped working with some https sites a few weeks after I posted this, so I went ahead and wrote whateverorigin.org, an open source alternative to anyorigin.
You can use Ajax-cross-origin a jQuery plugin. With this plugin you use jQuery.ajax() cross domain.
It is very simple to use:
$.ajax({
crossOrigin: true,
url: url,
success: function(data) {
console.log(data);
}
});
You can read more here: http://www.ajax-cross-origin.com/
JSONP should be on your list, and higher up. Pretty much the standard. It requires server cooperation, but most any API should know what they're doing and support it.
here is a real basic writeup of how it works
The client is on domain foo.com and needs to upload (send POST XMLHttpRequest) to upload.foo.com.
This is restricted because of the same origin policy.
However, the work around that I managed to come up with is, to dynamically create iframe on foo.com opening upload.foo.com and append the JavaScript code which executes the POST request from upload.foo.com like this:
iframe.onLoad [..]
(a=(b=doc)
.createElement('script'))
.src='http://foo.com/upload.php?'+Math.random(),
b.body.appendChild(a);
void(0);
Now, to me this seems redundant: if the later is possible, my logic tells me that the former should be possible as well. Is it?
-- update
I have just noticed that there is file on the sub domain containing this:
<?xml version="1.0" ?>
<cross-domain-policy>
<allow-access-from domain="*" />
<allow-access-from domain="*.foo.com" secure="false" />
</cross-domain-policy>
Can I use it somehow to my advantage?
XMLHttpRequest is not sensitive to document.domain because the object requires mutual opt-in for security reasons, and XHR has no way of knowing what the target might want the document.domain value to be set to. In order for SiteA to interact with the DOM of a site on SiteB, both sites must share a common private domain suffix, and both must opt-in to the communication by setting document.domain to their common suffix.
Your cross-domain policy file doesn't actually make a lot of sense (as it opts-in everything, and then a subset of everything) but it's used for Flash, not XHR (which uses CORS).
I don't think it's possible to simplify this, but if it seems inelegant to you, there are simpler ways to use cross-origin JS.
Indeed, this is almost exactly what jQuery does if you try to send a request using jsonp. Wikipedia for JSONP
(Along with several other ways to bypass the same-origin restriction)
I don't know if this is what you're asking about, but in the name of maintainability, I would advise that you use the jQuery approach.
You need to set dataType: 'jsonp' and you're all set.
You can optionally set the parameter "callback=?"(look at the docs).
I know if on my own webpage, if my user is on :
http://www.example.com/form.php
and I make an ajax request from that page to :
http://example.com/responder.php
It will fail because of the Same origin policy (subdomain is different).
What I am trying to understand is, how is it that AJAX requests can pull data from API's like flickr when the request and server are obviously different.
Edit :
eg: Why does this code work?
$.getJSON('http://api.flickr.com/services/rest/?&;method=flickr...'
(Referred this Community Wiki)
Is it using Cross Origin Resource Sharing?
Thanks!
There are few known methods to work around the Same Origin Policy. One popular technique is to use "Script Tag Injection" such as in JSONP. Since the <script> tag is not constrained by the Same Origin Policy, a script on a third-party domain can provide executable code that interacts with a provided callback function. You may want to check out the "Tips and Tricks" section in the following article for further reading on the topic:
Howto Dynamically Insert Javascript And CSS (hunlock.com)
You may also be interested in checking out the following Stack Overflow post for further reading on other techniques to work around the Same Origin Policy:
Ways to circumvent the same-origin policy
UPDATE: Further the updated question:
Quoting from the jQuery documentation on $.getJSON():
If the URL includes the string "callback=?" in the URL, the request is treated as JSONP instead.
I need to make an AJAX request from a website to a REST web service hosted in another domain.
Although this is works just fine in Internet Explorer, other browsers such as Mozilla and Google Chrome impose far stricter security restrictions, which prohibit cross-site AJAX requests.
The problem is that I have no control over the domain nor the web server where the site is hosted. This means that my REST web service must run somewhere else, and I can't put in place any redirection mechanism.
Here is the JavaScript code that makes the asynchronous call:
var serviceUrl = "http://myservicedomain";
var payload = "<myRequest><content>Some content</content></myRequest>";
var request = new XMLHttpRequest();
request.open("POST", serviceUrl, true); // <-- This fails in Mozilla Firefox amongst other browsers
request.setRequestHeader("Content-type", "text/xml");
request.send(payload);
How can I have this work in other browsers beside Internet Explorer?
maybe JSONP can help.
NB youll have to change your messages to use json instead of xml
Edit
Major sites such as flickr and twitter support jsonp with callbacks etc
The post marked as the answer is erroneous: the iframes document is NOT able to access the parent. The same origin policy works both ways.
The fact is that it is not possible in any way to consume a rest based webservice using xmlhttprequest. The only way to load data from a different domain (without any framework) is to use JSONP. Any other solutions demand a serverside proxy located on your own domain, or a client side proxy located on the remote domain and som sort of cross-site communication (like easyXDM) to communicate between the documents.
The fact that this works in IE is a security issue with IE, not a feature.
Unfortunately cross-site scripting is prohibited, and the accepted work around is to proxy the requests through your own domain: do you really have no ability to add or modify server side code?
Furthermore, the secondary workaround - involving the aquisition of data through script tags - is only going to support GET requests, which you might be able to hack with a SOAP service, but not so much with the POST request to a RESTful service you describe.
I'm really not sure an AJAX solution exists, you might be back to a <form> solution.
The not very clear workaround (but works) is using iframe as container for requests to another sites. The problem is, the parent can not access iframe's content, can only navigate iframe's "src" attribut. But the iframe content can access parent's content.
So, if the iframe's content know, they can call some javascript content in parent page or directly access parent's DOM.
EDIT:
Sample:
function ajaxWorkaroung() {
var frm = gewtElementById("myIFrame")
frm.src = "http://some_other_domain"
}
function ajaxCallback(parameter){
// this function will be called from myIFrame's content
}
Make your service domain accept cross origin resource sharing (CORS).
Typical scenario: Most CORS compliant browsers will first send an OPTIONS header, to which, the server should return information about which headers are accepted. If the headers satisfy the service's requirements for the request provided (Allowed Methods being GET and POST, Allowed-Origin *, etc), the browser will then resend the request with the appropriate method (GET, POST, etc.).
Everything this point forward is the same as when you are using IE, or more simply, if you were posting to the same domain.
Caviots: Some service development SDK's (WCF in particular) will attempt to process the request, in which case you need to preprocess the OPTIONS Method to respond to the request and avoid the method being called twice on the server.
In short, the problem lies server-side.
Edit There is one issue with IE 9 and below with CORS, in that it is not fully implemented. Luckily, you can solve this problem by making your calls from server-side code to the service and have it come back through your server (e.g. mypage.aspx?service=blah&method=blahblah&p0=firstParam=something). From here, your server side code should implement a request/response stream model.
Just use a server side proxy on your origin domain. Here is an example: http://jquery-howto.blogspot.com/2009/04/cross-domain-ajax-querying-with-jquery.html
This can also be done using a webserver setup localy that calls curl with the correct arguments and returns the curl output.
app.rb
require 'sinatra'
require 'curb'
set :views,lambda {"views/"+self.name.to_s.downcase.sub("controller","")}
set :haml, :layout => :'../layout', :format => :html5, :escape_html=>true
disable :raise_errors
get '/data/:brand' do
data_link = "https://externalsite.com/#{params[:brand]}"
c = Curl::Easy.perform(data_link)
c.body_str
end
Sending an ajax request to localhost:4567/data/something will return the result from externalsite.com/something.
Another option would be to setup a CNAME record on your own domain to "Mask" the remote domain hostname.