595

How do I send a cross-domain POST request via JavaScript?

Notes - it shouldn't refresh the page, and I need to grab and parse the response afterwards.

halfer
  • 18,701
  • 13
  • 79
  • 158
Ido Schacham
  • 5,999
  • 3
  • 15
  • 7
  • I'd like to know a little about the use case that lets you try to do this. Could you please tell something about it? – mkoeller Nov 18 '08 at 14:52
  • Basically I'm working on a script that needs to send some text from an HTML file to another server for processing. – Ido Schacham Nov 18 '08 at 18:32
  • 3
    Can you set up a proxy that does this on the server-side and just gives your script the result? Or does it need to be 100% JavaScript? – Sasha Chedygov Aug 19 '09 at 18:42

17 Answers17

395

Update: Before continuing everyone should read and understand the html5rocks tutorial on CORS. It is easy to understand and very clear.

If you control the server being POSTed, simply leverage the "Cross-Origin Resource Sharing standard" by setting response headers on the server. This answer is discussed in other answers in this thread, but not very clearly in my opinion.

In short here is how you accomplish the cross domain POST from from.com/1.html to to.com/postHere.php (using PHP as an example). Note: you only need to set Access-Control-Allow-Origin for NON OPTIONS requests - this example always sets all headers for a smaller code snippet.

  1. In postHere.php setup the following:

    switch ($_SERVER['HTTP_ORIGIN']) {
        case 'http://from.com': case 'https://from.com':
        header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
        header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS');
        header('Access-Control-Max-Age: 1000');
        header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
        break;
    }
    

    This allows your script to make cross domain POST, GET and OPTIONS. This will become clear as you continue to read...

  2. Setup your cross domain POST from JS (jQuery example):

    $.ajax({
        type: 'POST',
        url: 'https://to.com/postHere.php',
        crossDomain: true,
        data: '{"some":"json"}',
        dataType: 'json',
        success: function(responseData, textStatus, jqXHR) {
            var value = responseData.someKey;
        },
        error: function (responseData, textStatus, errorThrown) {
            alert('POST failed.');
        }
    });
    

When you do the POST in step 2, your browser will send a "OPTIONS" method to the server. This is a "sniff" by the browser to see if the server is cool with you POSTing to it. The server responds with an "Access-Control-Allow-Origin" telling the browser its OK to POST|GET|ORIGIN if request originated from "http://from.com" or "https://from.com". Since the server is OK with it, the browser will make a 2nd request (this time a POST). It is good practice to have your client set the content type it is sending - so you'll need to allow that as well.

MDN has a great write-up about HTTP access control, that goes into detail of how the entire flow works. According to their docs, it should "work in browsers that support cross-site XMLHttpRequest". This is a bit misleading however, as I THINK only modern browsers allow cross domain POST. I have only verified this works with safari,chrome,FF 3.6.

Keep in mind the following if you do this:

  1. Your server will have to handle 2 requests per operation
  2. You will have to think about the security implications. Be careful before doing something like 'Access-Control-Allow-Origin: *'
  3. This wont work on mobile browsers. In my experience they do not allow cross domain POST at all. I've tested android, iPad, iPhone
  4. There is a pretty big bug in FF < 3.6 where if the server returns a non 400 response code AND there is a response body (validation errors for example), FF 3.6 wont get the response body. This is a huge pain in the ass, since you cant use good REST practices. See bug here (its filed under jQuery, but my guess is its a FF bug - seems to be fixed in FF4).
  5. Always return the headers above, not just on OPTION requests. FF needs it in the response from the POST.
rynop
  • 41,200
  • 23
  • 87
  • 99
  • Can it return html for example? I need to return html and something is not working... – denis_n Oct 04 '11 at 01:19
  • Yea you should be able to. Never tried it tho. Your server returning 200? Also is your server returning the headers on the OPTIONs AND POST requests? I have updated my answer with more detail about this. Make sure that your server is responding with the correct content-type header too (like text/html). My recomendation is to use google chrome, right click page>inspect element. Click on network tab, and watch the POST and the response. Should give you info on what is going wrong. – rynop Oct 04 '11 at 02:26
  • I have tried this, but still get `400 Bad Request` on `OPTIONS` request. and in `firefox` the second request of `POST` is never made. :( – Zain Shaikh Nov 23 '11 at 07:27
  • Is there a way to callout your local machine in your case statement above? Or do you just have to use the * in this case for the allow origins. – Todd Vance Dec 10 '12 at 15:45
  • @toddv You can add a domain in Access-Control-Allow-Origin, but downside is you cant add a list of domains. – rynop Dec 12 '12 at 20:27
  • For me, it didn't work until I also returned the response header `Access-Control-Allow-Headers: x-csrf-token` – JellicleCat Mar 14 '13 at 17:43
  • @JellicleCat yes if your site is using csrf u'll need to add that – rynop Jul 16 '13 at 19:20
  • what if I don't own the server to which the post request is being made ? Is there a work-around ? – coding_idiot Sep 29 '13 at 08:50
  • I want to make a cross-Domain request from chrome-extension, so what will be my $_SERVER[HTTP_ORIGIN] in this case? –  Nov 04 '13 at 19:28
  • The behavior of firefox seems to have changed now? I don't get any option requests when I do this. – Buge Oct 10 '15 at 18:01
  • Does the server have to explicitly handle the preflight request or is it taken care of internally? – Sudip Bhandari Jul 22 '16 at 08:32
  • 2
    this was last edited 4 years ago - will this work on mobile browsers now? – frankpinto Sep 07 '17 at 17:19
  • hi @frankpinto did it work for mobile device or you used a different method? – laimison Sep 05 '20 at 19:57
124

If you control the remote server, you should probably use CORS, as described in this answer; it's supported in IE8 and up, and all recent versions of FF, GC, and Safari. (But in IE8 and 9, CORS won't allow you to send cookies in the request.)

So, if you don't control the remote server, or if you have to support IE7, or if you need cookies and you have to support IE8/9, you'll probably want to use an iframe technique.

  1. Create an iframe with a unique name. (iframes use a global namespace for the entire browser, so pick a name that no other website will use.)
  2. Construct a form with hidden inputs, targeting the iframe.
  3. Submit the form.

Here's sample code; I tested it on IE6, IE7, IE8, IE9, FF4, GC11, S5.

function crossDomainPost() {
  // Add the iframe with a unique name
  var iframe = document.createElement("iframe");
  var uniqueString = "CHANGE_THIS_TO_SOME_UNIQUE_STRING";
  document.body.appendChild(iframe);
  iframe.style.display = "none";
  iframe.contentWindow.name = uniqueString;

  // construct a form with hidden inputs, targeting the iframe
  var form = document.createElement("form");
  form.target = uniqueString;
  form.action = "http://INSERT_YOUR_URL_HERE";
  form.method = "POST";

  // repeat for each parameter
  var input = document.createElement("input");
  input.type = "hidden";
  input.name = "INSERT_YOUR_PARAMETER_NAME_HERE";
  input.value = "INSERT_YOUR_PARAMETER_VALUE_HERE";
  form.appendChild(input);

  document.body.appendChild(form);
  form.submit();
}

Beware! You won't be able to directly read the response of the POST, since the iframe exists on a separate domain. Frames aren't allowed to communicate with each other from different domains; this is the same-origin policy.

If you control the remote server but you can't use CORS (e.g. because you're on IE8/IE9 and you need to use cookies), there are ways to work around the same-origin policy, for example by using window.postMessage and/or one of a number of libraries allowing you to send cross-domain cross-frame messages in older browsers:

If you don't control the remote server, then you can't read the response of the POST, period. It would cause security problems otherwise.

Community
  • 1
  • 1
Dan Fabulich
  • 31,628
  • 35
  • 121
  • 153
  • 2
    You'll need to set form.target to something, or else the browser will navigate away from your site to the form action URL. Furthermore, the string needs to be unique; if there are other frames or windows using the same name, the form could post to that window instead of your iframe. But how unique does it have to be? Probably not very. The odds of clobbering are pretty small. *shrug* – Dan Fabulich May 18 '12 at 19:52
  • @DanFabulich: That works for me in the sense that I'm able to call the service method, and the service returns the object in XML form (which I saw in Network tab of the Chrome Developer tools). But how would I get the result in my web page? Any way to set callback or something? – Nawaz Sep 29 '12 at 18:09
  • 1
    @Nawaz As I said in my answer, you'll have to do cross-domain cross-frame communication to get the result in your web page. It requires that you control the remote web server so you can modify its response to allow communication with your web page. (For one thing, the server will need to reply with HTML; if the server replies with raw XML, it can't do cross-frame communication.) – Dan Fabulich Sep 30 '12 at 05:00
  • @DanFabulich: I didn't understand a single thing in your comment. These things are completely new to me; I'm not aware with the terminologies as well. Anyway, I used `XMLHttpRequest` and its working. But if you could explain your solution further, I would love to know that as well (maybe, your solution turns out to be better one for me). – Nawaz Sep 30 '12 at 05:32
  • 1
    +1 - this is the best solution I've found if you don't have access to the server – James Long Oct 18 '13 at 15:40
  • @DanFabulich, after submit form, how to read the response? Suppose I can control server. Can you give an example? – jason Jun 11 '14 at 11:30
  • @jason Use Porthole, XSSInterface, EasyXDM, or jQuery PostMessage plugin. – Dan Fabulich Jun 11 '14 at 22:17
  • @DanFabulich. After submit form, does server need return something for the iframe? How does the form page communicate with the data? – jason Jun 12 '14 at 12:26
  • If its not possible to read the response, is it possible to send it to the server? For example that I would add a timeout and then get document.innerHTML and send it to my server? – Vojtech B Jul 29 '14 at 13:15
  • 1
    @VojtechB No, that would be security hole. – Dan Fabulich Jul 29 '14 at 17:54
  • @DanFabulich Thanks, this pointed me in the right direction, but there are several erros with the code, this code does not work. This article helped me http://scriptble.com/2012/02/15/how-to-create-and-write-to-an-iframe-without-jquery/ – Alex Angelico Feb 10 '15 at 00:13
  • @DanFabulich Sentence in your answer `If you don't control the remote server, then you can't read the response of the POST, period` is wrong. jcubic answer and code http://stackoverflow.com/questions/38940932/how-to-invoke-form-post-to-other-domain-and-get-result-html-in-javascript contain solution for cross-domain POST result reading. You can consider improving answer by adding code for cross-domain response reading – Andrus Aug 14 '16 at 10:34
  • 1
    @Andrus You can read result of the POST, but only if you control the server! See in that answer, it says "do X on the sender [client], do Y on the receiver [server]." If you don't control the receiver/server, you can't do Y, and so you can't read the result of the POST. – Dan Fabulich Aug 14 '16 at 15:53
48
  1. Create an iFrame,
  2. put a form in it with Hidden inputs,
  3. set the form's action to the URL,
  4. Add iframe to document
  5. submit the form

Pseudocode

 var ifr = document.createElement('iframe');
 var frm = document.createElement('form');
 frm.setAttribute("action", "yoururl");
 frm.setAttribute("method", "post");

 // create hidden inputs, add them
 // not shown, but similar (create, setAttribute, appendChild)

 ifr.appendChild(frm);
 document.body.appendChild(ifr);
 frm.submit();

You probably want to style the iframe, to be hidden and absolutely positioned. Not sure cross site posting will be allowed by the browser, but if so, this is how to do it.

Lou Franco
  • 83,503
  • 14
  • 127
  • 183
  • 4
    Actually, this is slightly inaccurate, since ifr.appendChild(frm); will not work. the iframe is a reference to a window object, and the appendChild method doesn't exist for it. You'll need to grab the document node in the iframe first. This requires feature detection to work across browsers. – Rakesh Pai Nov 18 '08 at 15:41
  • Thanks. Found these useful links regarding the matter: http://bindzus.wordpress.com/2007/12/24/adding-dynamic-contents-to-iframes/ http://developer.apple.com/internet/webcontent/iframe.html – Ido Schacham Nov 20 '08 at 14:26
  • 19
    Problem! The received response in the iframe lies at a different domain, so the main window has no access to it, neither does the iframe have access to the main window. So this solution only seems good for doing the POST, but you can't parse the response afterward :( – Ido Schacham Nov 30 '08 at 14:54
  • 2
    Try setting an onload in the body tag of the response to a JavaScript function that calls a function in the parent with the response string. – Lou Franco Nov 30 '08 at 19:05
  • This answer didn't work for me; I posted my own variation below. – Dan Fabulich May 30 '11 at 05:12
  • jsonp should work to emulate "GET". How to transfer the authentication info to the iframe? I'd like to do authenticated posts to another domain. – Evgeny Jun 16 '12 at 16:57
  • Found a bug with code above in IE7. It seems doesn't allow to append child nodes created in main document object. Solved it by created form and input elements in next manner: var frm = ifr.contentWindow.document.createElement("form"); var input = ifr.contentWindow.document.createElement('input'); – Oleg Savelyev Jun 25 '12 at 14:41
  • @LouFranco: That works for me in the sense that I'm able to call the service method, and the service returns the object in XML form (which I saw in Network tab of the Chrome Developer tools). But how would I get the result in my web page? Any way to set callback or something? – Nawaz Sep 29 '12 at 18:10
  • for access iframe of different domain, you need to add a child iframe to the iframe page and make sure the child iframe have the same domain as the parent domain. it will be something like this: – user227353 Aug 05 '13 at 20:49
24

Keep it simple:

  1. cross-domain POST:
    use crossDomain: true,

  2. shouldn't refresh the page:
    No, it will not refresh the page as the success or error async callback will be called when the server send back the response.


Example script:

$.ajax({
        type: "POST",
        url: "http://www.yoururl.com/",
        crossDomain: true,
        data: 'param1=value1&param2=value2',
        success: function (data) {
            // do something with server response data
        },
        error: function (err) {
            // handle your error logic here
        }
    });
Community
  • 1
  • 1
Alain Gauthier
  • 650
  • 1
  • 9
  • 13
  • 11
    `crossDomain: true` oddly has absolutely nothing to do with real cross-domain requests. If the request is cross-domain, jquery sets this to true automagically. – Kevin B Apr 27 '16 at 14:28
16

If you have access to all servers involved, put the following in the header of the reply for the page being requested in the other domain:

PHP:

header('Access-Control-Allow-Origin: *');

For example, in Drupal's xmlrpc.php code you would do this:

function xmlrpc_server_output($xml) {
    $xml = '<?xml version="1.0"?>'."\n". $xml;
    header('Connection: close');
    header('Content-Length: '. strlen($xml));
    header('Access-Control-Allow-Origin: *');
    header('Content-Type: application/x-www-form-urlencoded');
    header('Date: '. date('r'));
    // $xml = str_replace("\n", " ", $xml); 

    echo $xml;
    exit;
}

This probably creates a security problem, and you should make sure that you take the appropriate measures to verify the request.

no.good.at.coding
  • 19,647
  • 1
  • 57
  • 51
Robb Lovell
  • 177
  • 1
  • 2
9

Check the post_method function in http://taiyolab.com/mbtweet/scripts/twitterapi_call.js - a good example for the iframe method described above.

ndeuma
  • 709
  • 5
  • 12
6
  1. Create two hidden iframes (add "display: none;" to the css style). Make your second iframe point to something on your own domain.

  2. Create a hidden form, set its method to "post" with target = your first iframe, and optionally set enctype to "multipart/form-data" (I'm thinking you want to do POST because you want to send multipart data like pictures?)

  3. When ready, make the form submit() the POST.

  4. If you can get the other domain to return javascript that will do Cross-Domain Communication With Iframes (http://softwareas.com/cross-domain-communication-with-iframes) then you are in luck, and you can capture the response as well.

Of course, if you want to use your server as a proxy, you can avoid all this. Simply submit the form to your own server, which will proxy the request to the other server (assuming the other server isn't set up to notice IP discrepancies), get the response, and return whatever you like.

Magarshak
  • 141
  • 1
  • 4
6

One more important thing to note!!! In example above it's described how to use

$.ajax({
    type     : 'POST',
    dataType : 'json', 
    url      : 'another-remote-server',
    ...
});

JQuery 1.6 and lower has a bug with cross-domain XHR. According to Firebug no requests except OPTIONS were sent. No POST. At all.

Spent 5 hours testing/tuning my code. Adding a lot of headers on the remote server (script). Without any effect. But later, I've updated JQuery lib to 1.6.4, and everything works like a charm.

Community
  • 1
  • 1
BasTaller
  • 781
  • 9
  • 18
5

If you want to do this in ASP.net MVC environment with JQuery AJAX, follow these steps: (this is a summary of the solution offered at this thread)

Assume that "caller.com"(can be any website) needs to post to "server.com"(an ASP.net MVC application)

  1. On the "server.com" app's Web.config add the following section:

      <httpProtocol>
          <customHeaders>
              <add name="Access-Control-Allow-Origin" value="*" />
              <add name="Access-Control-Allow-Headers" value="Content-Type" />
              <add name="Access-Control-Allow-Methods" value="POST, GET, OPTIONS" />
          </customHeaders>
      </httpProtocol>
    
  2. On the "server.com", we'll have the following action on the controller(called "Home") to which we will be posting:

    [HttpPost]
    public JsonResult Save()
    {
        //Handle the post data...
    
        return Json(
            new
            {
                IsSuccess = true
            });
    }
    
  3. Then from the "caller.com", post data from a form(with the html id "formId") to "server.com" as follow:

    $.ajax({
            type: "POST",
            url: "http://www.server.com/home/save",
            dataType: 'json',
            crossDomain: true,
            data: $(formId).serialize(),
            success: function (jsonResult) {
               //do what ever with the reply
            },
            error: function (jqXHR, textStatus) {
                //handle error
            }
        });
    
Community
  • 1
  • 1
Sujeewa
  • 691
  • 8
  • 10
4

There is one more way (using html5 feature). You can use proxy iframe hosted on that other domain, you send message using postMessage to that iframe, then that iframe can do POST request (on same domain) and postMessage back with reposnse to the parent window.

parent on sender.com

var win = $('iframe')[0].contentWindow

function get(event) {
    if (event.origin === "http://reciver.com") {
        // event.data is response from POST
    }
}

if (window.addEventListener){
    addEventListener("message", get, false)
} else {
    attachEvent("onmessage", get)
}
win.postMessage(JSON.stringify({url: "URL", data: {}}),"http://reciver.com");

iframe on reciver.com

function listener(event) {
    if (event.origin === "http://sender.com") {
        var data = JSON.parse(event.data);
        $.post(data.url, data.data, function(reponse) {
            window.parent.postMessage(reponse, "*");
        });
    }
}
// don't know if we can use jQuery here
if (window.addEventListener){
    addEventListener("message", listener, false)
} else {
    attachEvent("onmessage", listener)
}
jcubic
  • 51,975
  • 42
  • 183
  • 323
  • There is related question in http://stackoverflow.com/questions/38940932/how-to-invoke-form-post-to-other-domain-and-get-result-html-in-javascript . Is it possible to create some plugin or generic function based on your sample ? – Andrus Aug 14 '16 at 10:19
  • @Andrus maybe something like this https://gist.github.com/jcubic/26f806800abae0db9a0dfccd88cf6f3c – jcubic Aug 14 '16 at 12:45
  • This code requires modifying receiver page. How to read response if receiver pages cannot modified ? – Andrus Aug 14 '16 at 16:52
  • @Andrus you can't you need to have access to recever.com iframe to send ajax requests there. Without iframe there will be no requests. – jcubic Aug 14 '16 at 18:59
3

High level.... You need to have a cname setup on your server so that other-serve.your-server.com points to other-server.com.

Your page dynamically creates an invisible iframe, which acts as your transport to other-server.com. You then have to communicate via JS from your page to the other-server.com and have call backs that return the data back to your page.

Possible but requires coordination from your-server.com and other-server.com

  • Didn't even think of using a CNAME to redirect. Good call! I have yet to try this but I'm assuming that the CNAME will trick the browser into thinking it's interacting with the same site? I'm going to be using it to post to Amazon S3 so I'm hoping this works. – SpencerElliott Nov 07 '11 at 19:31
  • 1
    I dont see how this would solve anything. crossing to a different subdomain has the same problems as crossing to a different domain. – Octopus Jun 24 '13 at 18:11
3

I think the best way is to use XMLHttpRequest (e.g. $.ajax(), $.post() in jQuery) with one of Cross-Origin Resource Sharing polyfills https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills#wiki-CORS

Roman Dolgiy
  • 1,411
  • 1
  • 13
  • 21
2

I know this is an old question, but I wanted to share my approach. I use cURL as a proxy, very easy and consistent. Create a php page called submit.php, and add the following code:

<?

function post($url, $data) {
$header = array("User-Agent: " . $_SERVER["HTTP_USER_AGENT"], "Content-Type: application/x-www-form-urlencoded");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}

$url = "your cross domain request here";
$data = $_SERVER["QUERY_STRING"];
echo(post($url, $data));

Then, in your js (jQuery here):

$.ajax({
type: 'POST',
url: 'submit.php',
crossDomain: true,
data: '{"some":"json"}',
dataType: 'json',
success: function(responseData, textStatus, jqXHR) {
    var value = responseData.someKey;
},
error: function (responseData, textStatus, errorThrown) {
    alert('POST failed.');
}
});
Ivan Durst
  • 1,061
  • 2
  • 10
  • 23
2

This is an old question, but some new technology might help someone out.

If you have administrative access to the other server then you can use the opensource Forge project to accomplish your cross-domain POST. Forge provides a cross-domain JavaScript XmlHttpRequest wrapper that takes advantage of Flash's raw socket API. The POST can even be done over TLS.

The reason you need administrative access to the server you are POSTing to is because you must provide a cross-domain policy that permits access from your domain.

http://github.com/digitalbazaar/forge

dlongley
  • 2,028
  • 14
  • 16
1

Should be possible with a YQL custom table + JS XHR, take a look at: http://developer.yahoo.com/yql/guide/index.html

I use it to do some client side (js) html scraping, works fine (I have a full audio player, with search on internet/playlists/lyrics/last fm informations, all client js + YQL)

Guillaume86
  • 13,967
  • 4
  • 48
  • 50
1

CORS is for you. CORS is "Cross Origin Resource Sharing", is a way to send cross domain request.Now the XMLHttpRequest2 and Fetch API both support CORS, and it can send both POST and GET request

But it has its limits.Server need to specific claim the Access-Control-Allow-Origin, and it can not be set to '*'.

And if you want any origin can send request to you, you need JSONP (also need to set Access-Control-Allow-Origin, but can be '*')

For lots of request way if you don't know how to choice, I think you need a full functional component to do that.Let me introduce a simple component https://github.com/Joker-Jelly/catta


If you are using modern browser (> IE9, Chrome, FF, Edge, etc.), Very Recommend you to use a simple but beauty component https://github.com/Joker-Jelly/catta.It have no dependence, Less than 3KB, and it support Fetch, AJAX and JSONP with same deadly sample syntax and options.

catta('./data/simple.json').then(function (res) {
  console.log(res);
});

It also it support all the way to import to your project, like ES6 module, CommonJS and even <script> in HTML.

Jelly
  • 367
  • 1
  • 6
1

If you have access to the cross domain server and don't want to make any code changes on server side, you can use a library called - 'xdomain'.

How it works:

Step 1: server 1: include the xdomain library and configure the cross domain as a slave:

<script src="js/xdomain.min.js" slave="https://crossdomain_server/proxy.html"></script>

Step 2: on cross domain server, create a proxy.html file and include server 1 as a master:

proxy.html:
<!DOCTYPE HTML>
<script src="js/xdomain.min.js"></script>
<script>
  xdomain.masters({
    "https://server1" : '*'
  });
</script>

Step 3:

Now, you can make an AJAX call to the proxy.html as endpoint from server1. This is bypass the CORS request. The library internally uses iframe solution which works with Credentials and all possible methods: GET, POST etc.

Query ajax code:

$.ajax({
        url: 'https://crossdomain_server/proxy.html',
        type: "POST",
        data: JSON.stringify(_data),
        dataType: "json",
        contentType: "application/json; charset=utf-8"
    })
    .done(_success)
    .fail(_failed)