Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle custom headers with CORS Pre-flight request? AJAX - CodeIgniter

I'm working with CodeIgniter and the Restfull API to structure my web server private API.

I've started using CORS as per requirements for some framework I'm using.

Working with Jquery, I can see 2 requests are sent, first one as OPTION type - as expected - but without my custom header (X-API-KEY for security, by default in CodeIgniter Restful API).

I then receive an invalid API Key error message as shown in the picture. Then right after the proper request is being sent with correct headers but in the meantime, the first requests triggered the .fail() function to handle errors.

First Pre Flight call trigger invalid API KEY since we can't pass the X-API-KEY custom header in this callThe normal call working fine

What's best practice to handle that scenario ? i would like my ajax request to smoothly handle the first preflight OPTION request without triggering an error on my app as it does today, then do the normal GET call with custom headers as per how CORS works and execute the success call without never triggering the error call in the first preflight request ?

triggerFriendsWall: function() {
        //Get location
            var options = {
                timeout: 30000,
                enableHighAccuracy: true,
                maximumAge: 90000
            };

            //We need to check if user has disabled geolocation, in which case it makes the app crashes ! (from Cordova.js 1097)
            var position = JSON.parse(localStorage.getItem("position"));

            if (position == "" || position == null || position == "null" || typeof position == "undefined" ) {
                // In this case we have never set location to anything and the user has enabled it.
                navigator.geolocation.getCurrentPosition( function(position) {
                    home.friendsWall(position);
                }, function(error) {
                    common.handle_errors(error, home.friendsWall);
                }, options);
            } else {
                // in this case, user has disabled geolocatoin !
                common.handle_errors(false, home.friendsWall);
            }
},


friendsWall: function(position) {

    $.when(UserDAO.getUsersNearby(position.coords.latitude, position.coords.longitude, home.Usr_radius, home.Usr_limit, home.Usr_offset))
                .done(function(response) {
                   // Do stuff
    })
}


getUsersNearby: function(lat, lng, radius, limit, offset) {
            var key = localStorage.getItem("key");

            return $.ajax({
                type: "GET",
                url: config.server_url + 'user/usersSurrounding',
                headers: {
                    'X-API-KEY': key
                },
                data: {
                    lat: lat,
                    lng: lng,
                    radius: radius,
                    limit: limit,
                    offset: offset
                },
                dataType: 'json'
            });
        },

Many thanks

EDIT: This is the constructor associated to all my controllers ( all controller extend a single controller where construct method is : )

public function __construct()
{

    header('Access-Control-Allow-Origin: *');
    header("Access-Control-Allow-Headers: X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method");
    header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
    $method = $_SERVER['REQUEST_METHOD'];
    if($method == "OPTIONS") {
        die();
    }

    parent::__construct();
    // $this->load->model('admin_model');
    $this->load->library('session');
    $this->load->library('key');
}
like image 816
Miles M. Avatar asked Jun 18 '15 16:06

Miles M.


1 Answers

Are you using Access-Control-Allow-Headers?

Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request.

Try adding the following header to your preflight code.

header("Access-Control-Allow-Headers: content-type, origin, accept, X-API-KEY");

I recall having similar issues, seem to recall some of it being browser specific too...

If it helps here is a snippet from some code I know works:

// CORS and other headers.  Make sure file is not cached (as it happens for example on iOS devices)
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Max-Age: ' . CORS_AUTH_MAX_AGE);

//CORS preflight
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS') {

    header("Access-Control-Allow-Headers: content-type, origin, accept, x-app-sig");

    $acrh = explode(',', strtolower($headers['Access-Control-Request-Headers']));
    foreach ($acrh as $k => $v) {
        $acrh[$k] = trim($v);
    }

    if (! isset($headers['Access-Control-Request-Headers']) || ! in_array('x-app-sig', $acrh)) {
        _log($h, '*** Bad preflight!' . PHP_EOL . print_r($headers, true) . PHP_EOL . print_r($_REQUEST, true));
        header("HTTP/1.1 401 Unauthorized");
        exit; //->
    }

    _log($h, '+++ Successful preflight.' . PHP_EOL . print_r($headers, true) . PHP_EOL . print_r($_REQUEST, true));
    exit; //->
}

//Now we are past preflight.  Actual Auth happens here, I check a signature that I post with payload.

Update: OK, think I better understand your question now. Posted a bit more code. First off, yes, we are doing essentially the same thing there. I just check that the preflight tried to white-list what it should have in terms of headers.

I think the part you are missing is that the preflight should/will not have the custom header you are trying to send. See the answer here: How do you send a custom header in a cross-domain (CORS) XMLHttpRequest?). So like I do you could check that the Access-Control-Request-Headers: are sent with the preflight, but you should not check for the actual header being present on that call.

Sounds like you just need to move a little of the code around server side - make the preflight pretty vanilla and dumb, then do your actual auth or checking of custom headers after successful preflight.

I use a HMAC signature sent with the payload myself to authenticate things after the preflight. I also check that the custom x-app-sig is supplied and what i expect though that is probably redundant.

like image 52
ficuscr Avatar answered Oct 18 '22 17:10

ficuscr