How to send cross domain ajax requests to wordpress.

  • admin
  • Tag 1
    Tag 2
    Tag 3
    Tag 4

    I recently came across a project, where the client, as usual, wanted some fairly complex functionality. He owned a wordpress site, and wanted to sell a service where he gave other site owners a simple form that they embedded on their site. The goal was to have the form on xyzdomain.com post back to his domain abc.com. Once the data was passed back to his site, some internal validation was to be done, and then status messages had to be sent back to the sending domain. Essentially, the goal was to create a simple api, that allowed other sites to send us data that we processed for them.

    After some brainstorming, I decided the best way to achieve all this in a seamless manner was via ajax. However, I found 1 major obstacle to this goal: WordPress actively blocks cross-domain ajax requests as a security measure. Now it would be easy enough to just forcefully disable this security feature, however doing so would leave us open to all sorts of issues, not something we want. Since this would be entirely unacceptable, the solution was simple: Use a white-listing method to ensure we only accept data from trusted domains. As part of the sign-up process, we simply ask for the domain of the site hosting the form, and add it to our whitelist. This gives us full control over our api, with minimal extra work for the end user.

    So to achieve this all, we needed to do a few simple things:

    1. Add a hook on our site to process our ajax requests.
    2. Add a filter to process incoming requests, and process whitelist checking to ensure access is only allowed to the requesting domain if it’s on our list.
    3. Handle data validation and sanitization, process the data and then send status messages back to the originating form.

    Certainly quite the list of tasks to achieve, but nothing impossible. First we set-up our ajax hooks with filters:

            add_action('wp_ajax_generic_interface', 'process_api_call');
            
            add_action('wp_ajax_nopriv_generic_interface', 'process_api_call');
    
    function process_api_call () {
    
    // Process form sanitization, validation and do whatever stuff with the data. We also send back a json encoded response for the form script to process. 
    
     $return = array(
                'message'   => 'A status message.',
                'error'        => false // or true
            );
    
            wp_send_json($return); 
    
    
    
    }
    

    Super simple. Now we have our end point. However, we still need to allow access to our end-point to other domains. To do that, we use another wordpress filter.

    
    add_filter( 'allowed_http_origin', 'get_whitelisted_domains');
    
        function get_whitelisted_domains() {
            
            $domain_white_list = array('google.com', 'xyz.com');  // Note that you'll want to change this variable to include the list of domains. 
    
            $domain_requested_from = GET_HTTP_ORIGIN(); 
            
            if(in_array($domain_requested_from, $domain_white_list)){
                    
                return true;
                     
            }
                
                return false; 
    
        }  
    
    

    Now there’s a few caveats to this method. First, the GET_HTTP_ORIGIN() can be spoofed, which means it’s vitally important that the whitelist check only be ONE layer of security/validation. For this project, part of the form data is the client’s api key, which is hashed and checked to ensure that they are allowed access. Additionally, spoofing methods can actually be used to send this field as blank, so it would pay to add an extra condition that if the origin header is blank, that it reject the request.

    The final step is the form that the end-user embeds on their site. We wanted to make this as simple as possible. Please note, this is just a sample of the form:

    
    <div id='generic_form_wrapper'></div>
    
    <script type='text/javascript'>
    var script = document.createElement('script');
    script.src = 'http://pathtoremotescript.com/js/payload.js'; // this is the path to the remote script that we host. 
    var head = document.getElementsByTagName('head')[0];
    script.type = 'text/javascript';
    head.appendChild(script);	
    </script>
    

    What this embed does, is creates a simple div element. Additionally, it loads another script that we host remotely, that checks to see if the end user has jquery running. If not, it loads jquery and then loads our form script complete with ajax handler.

    Payload.js looks like this:

    
    var jq_ready = function () {
    	
    	var script = document.createElement('script');
    	
    	script.src = 'http://pathtoremotescript.com/js/form.js';
    	
    	add_script(script);
    	
    };
    
    function add_script(script) {
    	
        var head = document.getElementsByTagName('head')[0];
    	
    	script.type = 'text/javascript';
    	
    	head.appendChild(script);
    	    	
    }
    
    function loadScript(callback) {
    	
    	if (!window.jQuery) {
    		
    	    var script = document.createElement('script');
    	    
    	    script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js';
    	    
    	    script.onreadystatechange = callback;
    	    
    	    script.onload = callback;
     		
     		add_script(script); 
     		
     	} else {
     		
     		jq_ready();	
     		
     	}
     	
    }
    
    loadScript(jq_ready);
    
    

    Quite simple. Checks to see if jquery is loaded, and if not loads a copy from google. Once done, it uses a callback to load our final remotely hosted script that looks like this:

    
    jQuery(document).ready(function($) {
    
    $("#generic_form_wrapper").append("
    <form action='http://pathtoremotesite.com/' id='generic_form' method='post' target='FileFrame'><input type='hidden' name='site_porker' value='pie'><input type='text' name='name'><input type='submit' name='submit'></form>
    
    "); // This is the portion that actually generates the form. It injects it into the simple
    <div> we created above in the 
    // initial embed
    	
    $("#generic_form").submit(function(e) {
    
        $.ajax({
               type: "POST",
               url: 'http://pathtoremotesite.com/wp-admin/admin-ajax.php',
               dataType: 'json',
    
               data: {
               	action: 'generic_interface',
               	},
               success: function(data) {
     	
               },
               error: function(data) {
               	
     
               }
             });
    
        e.preventDefault(); // 
        
    });	 
    	
    }); 
    
    

    Seems a bit over-complicated, and it might very well be. However, for the sake of ease of use, I wanted the embed code to be as simple as possible and leave the remaining process to our remote scripts. I did this for a few simple reasons:

    1. We wanted full control over what happened with the form. By loading our remote scripts, this gives us that control.
    2. We didn’t want our end users to have to change their embed code if we changed something. By loading things this way, our end users embed the code once, and forget about it. We can still change things remotely, without impacting them.

    After all the code is in place, we had a flexible end point that we can process data with, while ensuring that we do things in a secure fashion.

    Leave a comment

  • (required)