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.
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');
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With