Cross-domain AJAX using XMLHttpRequest

Author: Samuel Williams When: Friday, 06 May 2011

I recently implemented some cross-domain AJAX using jQuery. I wanted to POST data using JavaScript's XMLHttpRequest to another site, and this required the use of the new HTTP access control headers1.

You can try out the functionality on the new site - simply click the "Contact Us" link in the footer and send us message. That form POSTs data to which is responsible for actually sending us a message.

To make this work, you need to respond to the HTTP OPTIONS method with appropriate access control headers. When the client web browser tries to send the XMLHttpRequest, it first initiates an OPTIONS request to the same URI, and checks the headers. These headers specify things such as what domains can make XMLHttpRequests to this URI.

Server-side implementation

Here is an example of how to respond to the HTTP options method using the Utopia framework:

	"Access-Control-Allow-Origin" => "*",
	"Access-Control-Allow-Headers" => "X-Requested-With",
	"Access-Control-Max-Age" => "60"

on 'index' do |request, path|
	# ...
	if request.options?
		# Allow AJAX requests from different domains.
		return :status => 200, :headers => ACCESS_CONTROLS
	# ...

You can check this using curl -i -X OPTIONS $URI:

$ curl -i -X OPTIONS 
HTTP/1.1 200 OK
Date: Fri, 06 May 2011 02:25:28 GMT
Server: Apache/2.2.16 (Debian)
X-Powered-By: Phusion Passenger (mod_rails/mod_rack) 3.0.5
Access-Control-Allow-Headers: X-Requested-With
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 60
Content-Length: 0
Status: 200
Content-Type: text/plain

One problem I encountered when executing XMLHttpRequests was the fact that they process 3xx redirections transparently. So, if you return a 3xx redirect, it won't actually return this status code to your handler, but instead process the redirection3. For cross-domain requests, this can be a big problem unless you correctly specify OPTIONS for the redirected page too. Therefore, it is wise to ensure that controllers that process XMLHttpRequests return 2xx or 4xx status codes.

It is also important that controllers return the access control headers.

if request.xhr?
	# You also need to provide access control headers here.
	return :status => :success, :headers => ACCESS_CONTROLS
	return redirect(params["from"] ? "success" : "success-no-reply")

Client-side implementation

On the, I use JavaScript and jQuery to serialize the contact form and send this data to

$(function() {
	// The contact form element
	var contact = $("#contact");

		rules: {
			subject: "required",
			from: {
				email: true
			message: "required"
		submitHandler: function() {
			$('#contact-popup .main').slideUp();
			$('#contact-popup .sending').slideDown();
				url: contact.attr('action'),
				type: 'post',
				data: contact.serialize(),
				crossDomain: true,
				headers: {
					"X-Requested-With": "XMLHttpRequest"
				success: function() {
					$('#contact-popup .sending').slideUp();
					$('#contact-popup .success').slideDown();
				error: function(request, status, error) {
					$('#contact-popup .sending').slideUp();
					$('#contact-popup .error').slideDown();

jQuery by default doesn't set X-Requested-With for cross-domain requests, so you need to do this manually.

Further Reading

  • Mozilla Developer: HTTP Access Controls
  • WebKit DOM Programming Topics: Security Considerations
  • XMLHttpRequest: Infrastructure for the send method
  • Secure login using AJAX
  • Rack Memory Usage

  • Comments

    [City], [Country]
    Publicly displayed.
    Your email won't be displayed.
    The following tags are preserved: <pre>, <em> and <a>. All comments are moderated.

    Please note, you can leave a comment that uses (limited) XHTML and Textile syntax.