Sunday, January 17, 2016

Ionic Angular App as a Website Front-end - AJAX/CORS solutions

Ionic and Angular are great frameworks for building single page web apps.  If you want to create an app that you can then deploy on almost any mobile device out there based on HTML5 and Javascript (JS), then you are starting in a good place.

If you are just building an app that does not communicate to other websites and deals only with information stored locally or already on the app, then you will have no issues.  What the guides do not explain well is all the issues you will have communicating with other websites.  These are JS features (or limitations) that protect users from malicious js code.  Native Android (dalvik) and iOS (Objective-C/Swift) apps do not have that limitations, so native developers may be unfamiliar with these restrictions.

This article will help you identify and solve some common pitfalls when building an ionic app.  The source code to the sample ionic app is available on github.

Typical architecture for an Ionic App as a web site front-end.

 

Make sure your server allows remote access by Javascript:


If you are communicating to another server, you will need to set it up to allow for Cross-Origin Resouce Shared (CORS) access or JS will not be able to read the responses.  If the server you want to talk to is not your own and does not have Access-Control-Allow-Origin in its response headers set to where you need it (generally '*' or 'file://'), then you'll need to find a different way such as a proxy or a native app.
In Apache's httpd.conf file, you will need something like this on the directory you want to allow access to:
<Directory "/your_www_dir">
  #You may prefer "file://" instead of "*" because it is more restrictive
  Header set Access-Control-Allow-Origin "*"
  ...
Or this can be done in your host language.  For PHP:
//When testing app locally, it will be run from localhost:8100
$client_origin = "";
if (array_key_exists('HTTP_ORIGIN', $_SERVER)) {
    $client_origin = $_SERVER['HTTP_ORIGIN'];
}
if (stristr($client_origin, 'localhost') !== false) {
    $origin = "http://localhost:8100"; //test host
} else {
    $origin = "file://";
}
//Allow only from two specific locations: testing, and from JS app
header("Access-Control-Allow-Origin: $origin");
//To be less restrictive, you can use this line below
//header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");

Make sure your Ionic project has whitelists enabled and configured:


Newer versions of Ionic projects seem to have this enabled by default, but if you do not have the whitelist plugin already, you will need to install it.

Check if the whitelist plugin is installed:
$ ionic plugin list
cordova-plugin-console 1.0.2 "Console"

...
cordova-plugin-whitelist 1.2.0 "Whitelist"

If not, install it:
$ ionic plugin add https://github.com/apache/cordova-plugin-whitelist.git
Configure the app's config.xml file to allow access to outside sites (i.e. beyond 'file://'), add after <content src="index.html"/>.  Add the last two lines to allow Android/iOS apps to open sites other than you own (if desired):
<access origin="*"/>
<allow-intent href="*"/>
<allow-navigation href="*"/>

For iOS apps, check your App Transport Security (ATS) settings:


When you do an app build, a platforms/ios/{app_name}/{app_name}-Info.plist file is created.  If your app ONLY accesses HTTPS, you won't need to set this.  Otherwise add the bolded lines to the file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs$
<plist version="1.0">
  <dict>
    ...
    <key>NSAppTransportSecurity</key>
    <dict>
      <key>NSAllowsArbitraryLoads</key>
      <true/>
    </dict>

  </dict>
</plist>
If this is not set, you may have communication issues and see the following error:
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

Load your code or try the sample project:


The sample project uses the Angular $http service to communicate to a sample website.  I use http://espn.go.com as an example because they send an Access-Control-Allow-Origin = '*' and allows us to test our app.  You can see the response in the Message section and any errors in the Error section.


iOS app publishing tips


Getting an app running in the Apple App Store has its own set of challenges.  Check out these Ionic iOS App Publishing tips: Publishing an Ionic Angular App for iOS - The Hidden Steps & Pitfalls.


Something you should never ever do - have Allow Origin set to * and Allow Credentials to true



According to the spec you can't have Allow Origin = "*" and Allow Credentials set to "true".  There is a really good reason for this.  It prevents malicious Javascript from using your credentials saved in cookies against you.  Say you logged into a website and it knows who you are through a cookie.  If in another window you ran some malicious JS code and both of these settings were enabled, the JS code could look like a logged in user to the remote server.  Not so bad if it is a sports website, but horrendous if it is your banking site.  With all that said there is a way to do this in Apache.  Just echo the given origin back to the user.  The browser never sees it as Allow Origin=* so it allows the request.

This code in your httpd.conf completely exposes your users to malicious JS code:

   <Directory "/somedir">
       SetEnvIf Origin "^(.*)$" ORIGIN_SUB_DOMAIN=$1
       Header set Access-Control-Allow-Origin "%{ORIGIN_SUB_DOMAIN}e" env=ORIGIN_SUB_DOMAIN
       Header set Access-Control-Allow-Credentials "true"