Cross-Domain POST With JavaScript
Normally you can’t send cross-domain requests in JavaScript due to restrictions imposed by the same-origin security policy. There are many clever hacks that circumvent this by using remote script includes (even CSS includes) or proxy scripts, but so far I haven’t seen anything useful for client-side, cross-domain POST requests.
However, it turns out you can send them very easily if you don’t need to know the response to the request. This might sound useless at first, but there are certain situations where it’s all you really need. For example, blackhat SEO comes to mind…
The Basic Idea
Forms can use the POST method and JS can submit forms. This means you can dynamically generate a form with the action
attribute set to any URL (including pages on other domains) and encode all query parameters as hidden <input>
fields, then automatically submit the form.
The problem is this works exactly as if the user had filled out a form and clicked “Submit” – the browser will send the request and open a different page. That’s (usually) bad, so we need to put the form inside an invisible iframe
to hide the redirect. Of course, you can’t get any response data from the frame, so it’s truly “fire and forget”.
As a result, the final script will have two components – a JavaScript function that creates the iframe
, and another script that will run inside the frame and generate and submit the form.
Implementing It
The first part is the crossDomainPost()
JS function. This is the function you will call from your script to initiate a POST request. The function accepts three arguments :
- writer_url – the URL of the script that will generate the form (see below).
- post_target_url – send the POST request to this URL.
- params – query parameters for the POST request, formatted like this :
{param1 : 'value1', param2 : 'value2', ...}
And here’s the source code :
<script type='text/javascript' language='JavaScript'> function crossDomainPost(writer_url, post_target_url, params){ var url_params = ''; for (var key in params){ url_params =url_params + '&' + key + '='+encodeURIComponent(params[key]); } var url = writer_url + '?post_target_url=' + encodeURIComponent(post_target_url) + url_params; var iframe = document.createElement('iframe'); iframe.setAttribute('src', url); iframe.setAttribute('width', 1); iframe.setAttribute('height', 1); iframe.setAttribute('style', 'border: none;'); var p = document.getElementsByTagName('html'); p[0].appendChild(iframe); } </script>
The second component is the script that will generate and submit the form. There are several ways to create a form, but in this case I decided to stick with a mix of HTML & PHP. You can do it purely in JS, but the script would be a bit more complex. Either way, the form is still submitted by JavaScript, so the POST request will be done by the browser, not your server.
This script needs to be placed in a separate file (say, “form_writer.php”) because it will be loaded in a frame. Here’s the source :
<form method='post' action='<?php echo $_GET['post_target_url']; ?>' id='postform'> <?php unset($_GET['post_target_url']); foreach($_GET as $key => $value) { echo "<input type='hidden' name='$key' value='".htmlentities($value, ENT_QUOTES)."'>"; } ?> </form> <script language='JavaScript'> document.getElementById('postform').submit(); </script>
Using The Script
Lets see a real life example, shall we? Here’s how you can use the above scripts to send a trackback in JavaScript :
crossDomainPost( 'http://mydomain.com/form_writer.php', 'http://random-site.com/blog/offtopic/an-example-post/trackback/', { title : 'My Cool Post', excerpt : '...like, totally and such...', url : 'http://mydomain.com/stuff/my-cool-post/', blog_name : 'My Cool Blog' } );
Eeevil 😉
Related posts :
You can even try to receive result 😉 Script in the iframe can chage parent’s hash in URL so.. before post, run a “listener” (like function called with interval) to watch #hash value, then post, PHP after post should generete a response javascript that will put result in the hash, “listener” function can catch it 🙂
there is a limitation in url length, but you can split result in smaller chunks
I thought about doing something like that, but the assumption here is that you’re interacting with an external, third-party target that isn’t built to take advantage of this possibility. This whole thing would be much easier if you had control of the script that receives the POST.
Slight issue with your trick: on refresh or back, the browser will prompt the visitor to resend the post data. Destroying the iframe or the form after the initial POST won’t make a difference.
“Not Acceptable
An appropriate representation of the requested resource…”
Any idea why i get this error? I am trying to get the code to work but keep getting it, ive enlarged the iframe to 500×500 and this error is inside, i have the php file simply as 1.php and have changed the reference to it in the script.
Hmm, I’ve never seen that error before. Maybe it’s something to do with the site you’re trying to send the request to?
Hi White Shadow, I dumped in your code hoping to make a cross domain post request but I ended up getting this error in firefox
document.getElementById(“postform”) is null
[Break on this error] document.getElementById(‘postform’).submit();
located in the php file. Any ideas on that?
Thanks this awesome code though.
BUT the target will see your domain as referer !
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 = ”.”\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.
Cool trick but kind of missing the point.
you would normally want to do a post to send big data chucks (Get has very small limit) – as you have converted your params to url params along the way you have lost this ability and left with a fancy get.
i need to deliver big chunks of data in a post to different domain and be glad to hear if you have a solution to this.