A Practical solution for CORS (Cross-Origin Resource Sharing) issues in IONIC 3 and Cordova

Written by Ourarash | Published 2018/10/11
Tech Story Tags: javascript | ionic-framework | ionic | cordova | cors | cross-origin-resource-sharing | ionic-3 | latest-tech-stories

TLDR A Practical solution for CORS (Cross-Origin Resource Sharing) issues in IONIC 3 and Cordova. CORS is a method to prevent a client to request a display a service from a host other than the one that is currently showing. This is done for security reasons. Ionic’s blog has an old post on CORS issues. Unfortunately, the solution that they provide is only suitable when you are running the app using ionic serve or ionic run -l. That solution will not work on the production release!via the TL;DR App

Summary: I provide a practical solution for solving CORS issue in WKWebView in Ionic and Cordova. The solution works both for iOS and Android.
If you code with Ionic or Cordova and use a REST API, you probably have seen an error like this:
XMLHttpRequest cannot load http://api.ionic.com/endpoint. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8100' is therefore not allowed access.
What is CORS?
CORS stands for Cross Origin Resource Sharing. In short, CORS is a method to prevent a client to request a display a service from a host other than the one that is currently showing. This is done for security reasons.
The Problem
In order for an external API server to work in the presence of CORS, it should include something like this in its header:
Access-Control-Allow-Origin: *
The problem however is that some API providers do not include this and since we don’t have any control over the server, we cannot add this to the response header.
This is a huge problem specially in iOS where Ionic and Cordova run in WKWebView, which enforces CORS.
Solutions
Ionic’s blog has an old post on CORS issues. Unfortunately, the solution that they provide is only suitable when you are running the app using ionic serve or ionic run -l. That solution will not work on the production release!
The good news is that you can still solve the problem by doing a tiny little bit of work. I list a couple of solutions:
Using a proxy (Not a good solution): the idea is simple: create your OWN server in the middle of your client and the main API server. Your client request goes to your own server, which support CORS. Your server sends the request to the API server, gets the results, and sends it back to you.
Your server can be as simple as reading requests from specific node like /requests from a database server like Google Firebase, and then write the results in another node called /replies.
While this is doable, I don’t like this solution cause you will need to create a new server and maintain it.
2. Use Native HTTP (My favorite): Fortunately Cordova’s native HTTP plugin comes to rescue. Since this plugin’s HTTP requests don’t go through WKWebView, it does not have CORS issues. This is awesome!
So the solution is to send the HTTP requests for the given API through native HTTP plugin rather than NodeJS’s requests. Therefore:
Solution: Instead of using npm’s Request or Fetch packages, use Cordova’s plugin.
Here is an example usage for Binance’s REST API using Cordova’s native HTTP:
declare var cordova: any;
let url = 'https://api.binance.com/api/v1/exchangeInfo';
let params = {};
let headers = {};
cordova.plugin.http.get(url, 
    params, headers, (response) => {
  console.log(response.status);
}, function(response) {
  console.error(response.error);
});
What if we are using an npm wrapper for the API?
The above solution worked because we were using a simple REST HTTP call. However, sometimes an API call is more complex. For example, you have to provide APIKey and Secret and sign the request.
For such complex requests there are some npm wrappers that do the job and take care of the more complex issues, except that those packages use npm’s Request or Fetch packages.
Depending on the npm wrapper that you use, there are two possible conditions:

1. The Request or Fetch function is exposed in the constructor

If you are lucky, some npm packages like CCXT already allow you to overload their Fetch request function. In this case all you need is to pass your own HTTP Fetch wrapper to its constructor:
//First overload fetchImplementation in the constructor 

let exchange = new ccxt["poloniex"]({
      enableRateLimit: true,
      apiKey:apiKey,
      secret: secret,
      //Overloaded function that uses Native http calls
      fetchImplementation: ccxtFetchImplementation // ------> My wrapper function
    });

//Define ccxtFetchImplementation definition in your IONIC program:

 ccxtFetchImplementation(url, options) {

    let headers = options.headers || undefined;

    return new Promise(async (resolve, reject) => {
      //Cordova native http get request
      cordova.plugin.http.get(url, {}, headers, function (response) {

        //CCXT needs this function to match npm Fetch
        response.text = () => {
          return new Promise((resolve, reject) => {
            resolve(response.data);
          })
        }

        //Use Map to mimic npm Fetch that is used by ccxt
        response.headers = new Map(Object.entries(response.headers));
        response.statusText = response.statusMessage;
        resolve(response);
      }, function (response) {
        console.log('response.error: ', response.error);
        reject(response);
      });
    })
  }

2. The Request or Fetch function is NOT exposed in the constructor

But if you are not lucky, the Fetch request function is not exposed in the constructor. For example here is a great npm wrapper for Binance’s API. But it uses npm’s Request package which causes CORS issue, sigh…
In this case the solution is not too hard: you just need to modify that package and force it to use Cordova’s Request rather than npm’s.
For example, I modified Binance’s npm wrapper such that instead of its default npm Request, it takes the Request function from constructor, which I set it to cordova.plugin.http.get :
const binanceRest = new binanceApi.BinanceRest({
      key: apiKeySecret.apiKey, // Get this from your account on binance.com
      secret: apiKeySecret.secret, // Same for this
      timeout: 15000, // Optional, defaults to 15000, is the request time out in milliseconds
      recvWindow: 10000, // Optional, defaults to 5000, increase if you're getting timestamp errors
      disableBeautification: false,
      /*
       * Optional, default is false. Binance's API returns objects with lots of one letter keys.  By
       * default those keys will be replaced with more descriptive, longer ones.
       */
      handleDrift: false,
      
      requestFunction: cordova.plugin.http.get // ----------> Added by me!
      
      /* Optional, default is false.  If turned on, the library will attempt to handle any drift of
       * your clock on it's own.  If a request fails due to drift, it'll attempt a fix by requesting
       * binance's server time, calculating the difference with your own clock, and then reattempting
       * the request.
       */
    });
So in short, if you are using an npm wrapper for a REST API that doesn’t expose its HTTP request function in its constructor:
Create a branch of that wrapper on Github in your own GithubModify that package and force it to use Cordova’s HTTP requestUse the modified package in your Ionic program
The caveat is that if the npm wrapper is updated you will need to integrate the updates in your branch. But perhaps you can create a pull request and encourage the package owner to use your updates and expose the request function in the constructor.
If you liked this article, please give me feedback if this solution solved your issue.
Also, please try my app that is written in Ionic: Bitcoin CrazYness. It is one of the most comprehensive alerting and portfolio management apps for cryptocurrency traders.

Published by HackerNoon on 2018/10/11