iOS emulator HTTP POST convert to OPTIONS

Hi, I’ve got a very basic auth service in Angular that posts to an ExpressJS endpoint. When I run the ionic app in Chrome with --disable-web-security it works just fine. It posts as expected and the correct response is received.

Here’s a look at the request sent from Angular – dumped from an Angular interceptor

{
  "method": "POST",
  "transformRequest": [
    null
  ],
  "transformResponse": [
    null
  ],
  "data": {
    "email": "redacted",
    "password": "redacted"
  },
  "url": "http://localhost:3000/api/session",
  "headers": {
    "Accept": "application/json, text/plain, */*",
    "Content-Type": "application/json;charset=utf-8"
  }
}

…and on the server, I get this:

POST /api/session 200 50.524 ms - 2460

When I run the app as:

$ ionic emulate ios -l -c -s

this is what is posted – again logged from Angular interceptor:

13    099862   log      {
  "method": "POST",
  "transformRequest": [
    null
  ],
  "transformResponse": [
    null
  ],
  "data": {
    "email": "redacted",
    "password": "redacted"
  },
  "url": "http://localhost:3000/api/session",
  "headers": {
    "Accept": "application/json, text/plain, */*",
    "Content-Type": "application/json;charset=utf-8"
  }
}

and this is what is received by the server: OPTIONS /api/session 200 2.044 ms - 11

So, the tl;dr is:

  1. When I run the ionic app in the browser and POST it works just fine
  2. When I run the ionic app in the iOS emulator – same code, same server, same environment, the http POST is changed to http OPTIONS somewhere in the outbound chain (which fails, obviously).

Any clue what’s going on here? I haven’t done a ton of iOS dev. so I’m wondering if there is some bizarre CORS setting I need to tweak?

Using tcpdump this is what is received on the server from the ionic app running in the iOS emulator:

OPTIONS /api/session HTTP/1.1
Host: localhost:3000
Referer: http://192.168.25.105:8100/
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-us
Access-Control-Request-Headers: accept, origin, content-type
Access-Control-Request-Method: POST
Origin: http://192.168.25.105:8100
Content-Length: 0
Connection: keep-alive
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 10_10_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12B411 (2053361424)

and running the same app in Chrome:

POST /api/session HTTP/1.1
Host: localhost:3000
Connection: keep-alive
Content-Length: 53
Accept: application/json, text/plain, */*
Origin: http://localhost:8100
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36
Content-Type: application/json;charset=UTF-8
DNT: 1
Referer: http://localhost:8100/?restart=680685
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: connect.sid=s%3AaGIjECO0E-CmYaj1292PipphQ0K4fSkF.mE2quojvABX4zOgTuGPW9VIsX%2Ff0Jk0YXVSZeZeNnB4

{"email":"redacted","password":"redacted"}

The config.xml file - note <access origin="*"/>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<widget id="com.ionicframework.warehousemobile641362" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
  <name>warehouse-mobile</name>
  <description>
        An Ionic Framework and Cordova project.
    </description>
  <author email="hi@ionicframework" href="http://ionicframework.com/">
      Ionic Framework Team
    </author>
  <content src="index.html"/>
  <access origin="*"/>
  <preference name="webviewbounce" value="false"/>
  <preference name="UIWebViewBounce" value="false"/>
  <preference name="DisallowOverscroll" value="true"/>
  <preference name="BackupWebStorage" value="none"/>
  <feature name="StatusBar">
    <param name="ios-package" value="CDVStatusBar" onload="true"/>
  </feature>
</widget>

Related: http://stackoverflow.com/questions/20671619/angularjs-options-preflight-call-preceding-a-http-post-request

Investigating…

took ngResource out of the picture and using just $http it reacts the same way.

$http.post(servicesConfig.url + '/api/session', { email: 'redacted', password: 'redacted'})
   .success(function (data, status, headers, config) {
      console.log('SUCCESS', data, status, headers, config);
    })
    .error(function (data, status, headers, config) {
      console.log('ERROR', data, status, headers, config);
    });

Same bizness – in Chrome it works fine, in iOS OR android emulator it issues an OPTIONS request. Seems to point toward Cordova, I guess?

What an enormous headache CORS is. I didn’t realize the browser will make a pre-flight OPTIONS request. This ended up being the root of the issue.

After reading following post http://ionicinaction.com/how-to-fix-cors-problems-and-no-access-control-allow-origin-header-errors-with-ionic/ I was able to solve my problem
http://enable-cors.org/

In my case, I’m also developing the server side, so after adding following lines to my nodejs+express server it worked

app.use(function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE, OPTIONS');
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    next();
});