I have a cordova/ionic mobile app that loads google maps (in the index.html main file) into the app (both android and ios) using: https://maps.googleapis.com/maps/api/js?key=AndroidKey and https://maps.googleapis.com/maps/api/js?key=iOSKey. Each key is locked down with "app" restrictions and its not working. I discovered that web service api's can only be locked down by HTTP referrer OR Server IP.
But since the maps are loaded directly via the client, there is no HTTP referrer by domain or a server IP...is there any other way I can lock down the API keys?
Can I use something like https://github.com/wymsee/cordova-HTTP to create an HTTP referer? And if I can, what kind of legit domain referrer can I create that would work with google maps api HTTP referer restrictions?
update:
someone marked this as a dup, but that post is about Android SDK API, whereas mine is about Javascript Map API.
in ionic 3/4/5 when using cordova-plugin-ionic-webview (docs) the referrer is ionic://localhost for iOS and http://localhost for Android.
first solution is to customize the scheme and/or hostname - this sounds like a reasonable option as this way the referrer can be set to sth like https://mobileapp.author.domain.com/ which cannot be easily stolen by a website (well another app could possible set the same).
similarly for capacitor "server": {"hostname": "mobileapp.author.domain.com"} can be used (as per this SO answer: How to protect Google Maps API key on Ionic app? )
quick'n'dirty option is to add a *://localhost/* as a website restriction - this is the only way I found to whitelist the ionic://localhost/ referrer. This should also work for capacitor which uses capacitor://localhost/
Related
I have a mobile app written in Flutter. I am calling Google API's for Places and for Geocoding. Since the calls to these services are made by including the key in the url it is very important that this keys be restricted so that anyone can't just intercept and use them to make many calls and rack up a big account for us with Google.
Examples of API calls are:
https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$input&types=address&language=$lang&components=country:za&key=$apiKey
and
https://maps.googleapis.com/maps/api/geocode/json?latlng=$lat,$lon&key=$apiKey
(where $apiKey=our Google Maps API Credentials Key $input, $lang, $lat and $lon represent other variables)
Google currently allows the following Application Restrictions on API Credentials:
None
HTTP Referrers
IP Addresses
Android Apps
iOS Apps
When I don't have any restrictions on my key (option "None") then my mobile application works perfectly fine. However, if I choose to restrict it to iOS Apps (and specify the Bundle Identifier for my app) I get the error "This IP, site or mobile application is not authorized to use this API key". The same happens when I restrict the key to Android Apps and specify my Package Id and SHA1 signature for the Android app.
What am I missing? According to https://developers.google.com/maps/api-key-best-practices#restrict_apikey I should be fine to restrict the key to my specific mobile application. It seems like Google Maps is not picking up that I'm indeed making the call from the correct mobile app. The error occurs regardless of whether I run this in the Simulator or on an actual device in either debug or release mode. (I have tested the iOS version of the app from Test Flight too. When I remove the restrictions, my api calls work; when I restrict it only to my iOS app's Bundle Identifier it stops working.) Is there anything else I need to configure? Is the problem perhaps that the app is written in Flutter?
I found some links (like this) that suggest that mobile applications should never use the key directly in the url but should rather use our own server as a proxy to Google and then we should restrict access to our server's IP. This seems like an unnecessary overhead since the very existence of the option to restrict the key to specific app id's and platforms suggests that this should be possible (as does the Google documentation I refer to).
I ended up creating a proxy server. (More on that below).
Thinking about it critically I realised there would not really be any way for the API to discern whether incoming requests are being made from the specific mobile app or not so I don't think the restriction to specific iOS or Android apps is really possible for https requests.
I also investigated some of the flutter plugins provided but most of them seem to use google_maps_webservice in the background. And google_maps_webservice requires either a key in the app code or a proxy to be specified. Most of the plugins derived from google_maps_webservice don't even offer the proxy option and requires a key to be specified. So even if these plugins are used, one ends up "giving away" your key in the app code which makes it possible to reverse-engineer and obtain your code. (It ends up in the AppDelegate.m file on iOS and AndroidManifest.xml in Android). Google recommends against giving out your api key in this way.
So I ended up creating a proxy server. Requests are made to my proxy without the key and then the proxy passes on the request to Google after adding the secret key. The proxy then returns the response to the app. The app and traffic between app and server never contains a key.
Luckily my app was already using AWS and users were signing in with Cognito. So I could create the Proxy easily in API Gateway and I let the app authenticate to my proxy server using Cognito. This ensures that only users validly logged in on my app in Cognito will be able to call the proxy and keys are always kept secret.
If you want to take advantage of Google Map Service API configuration for a specific Android/iOS app you'll need to be using the native Android & native iOS SDKs which depend upon the SHA fingerprint & package name, or bundle id.
As the Google Places API seems popular a number of third-party attempts exist: https://pub.dev/packages?q=google+places
a similar situation exists for geocoding: https://pub.dev/packages?q=google+geocoding
You can see if any fit your app or you'll need to create your own platform channel for support: https://flutter.dev/docs/development/platform-integration/platform-channels
I have Ionic PWA app published for Android and iOS (I used Capacitor to generate the native build). In the frontend code, it has my Google Maps API key, however, I can't restrict it to any of the options google offers because...
HTTP referrers - It's not on a public domain name, it's on a local host within the webview of the native app. http://localhost/ for Android and capacitor://localhost/ for iOS. It does not seem very secure to use these as restrictions as they are very generic, and all other apps will have the same ones.
IP addresses - For obvious reasons.
Android Apps - It's not within the native code, it's within a webview.
iOS Apps - It's not within the native code, it's within a webview.
None of these options can work for my situation. So how can I protect my API key from abuse?
Any ideas? I can't be the only the one using Google Maps API within an Ionic app.
You can configure the hostname of capacitor apps
"server": {
// You can configure the local hostname, but it's recommended to keep localhost
// as it allows to run web APIs that require a secure context such as
// navigator.geolocation and MediaDevices.getUserMedia.
"hostname": "unique-app",
}
and then restrict the the API keys to capacitor://unique-app
https://capacitor.ionicframework.com/docs/basics/configuring-your-app
In order to protect your API key you have to check the value of the window.location.href within a webview. I guess you will see something like file://some/path.
So you will need apply HTTP referrer restriction for this path. Note that URLs with a file:// protocol require special representation as explained in
https://developers.google.com/maps/documentation/javascript/get-api-key#restrict_key
Note: file:// referers need a special representation to be added to the key restriction. The "file://" part should be replaced with "__file_url__" before being added to the key restriction. For example, "file:///path/to/" should be formatted as "__file_url__//path/to/*". After enabling file:// referers, it is recommended you regularly check your usage, to make sure it matches your expectations.
I hope this helps.
Old question but still...
If you do not want to store the api_key in your app, request it at run time from your own server over a POST https request before running any Google Maps requests.
I am building a progressive web app on Google App Engine.
Authentication is via Google API.
It is all working fine on a desktop browser, but when I access the app on a mobile phone browser (both iPhone and Android), the authentication button is hidden.
(I don't think this is because I am using service workers and it has cached an older version of the code, because I have just accessed it on a fresh phone and it is still doing it.)
Could this be a cross-origin or cross-domain conflict?
I have found the answer, and it now works.
The answer was to enable CORS (Cross-Origin Resource Sharing) with static hosted content on Google Appengine
(link found on the Enable CORS website)
I developed a html5 page to list google drive files and test with PhoneGap server. All works fine, because I created a Oauth2 with http://localhost:3000 configured.
When I tried to test a APK file, with SHA1 (also configured on Oauth2 in google drive console), I received the error:
Refused to display 'https://accounts.google.com/o/oauth2/auth?client_id=<KEY>' in a frame because it set 'X-Frame-Options' to 'SAMEORIGIN'. I understand that this error is because the Oauth2 doesn't expect the origin. But the Origin is not http, if file know... (file:///android_asset/www/index.html). The GoogleAPI Console doesn't accept 'file' protocol... just 'http'.
How can I solve this?
According to solution given in this SO post - Refused to display in an frame because it set 'X-Frame-Options' to 'SAMEORIGIN', regarding error encountered:
This is not related to disabling security in chrome browser. I believe there might me some issues with my XAMPP Windows local host.Deploying the same application in node.js server or hosting Dropbox/Google Drive as a web app also works fine.
Additionally, to make a request, as discussed in step #2 from the basic steps in using OAuth 2.0 to Access Google APIs, obtain an access token from the Google Authorization Server. Ways to make a request are as follows:
a JavaScript application might request an access token using a browser redirect to Google
an application installed on a device that has no browser uses web service requests.
Sample scenarios which you can use can be found in the documentation.
I have a web based service (running for years) that works with google apis.
Now I would like to create an android and iphone ui (using xamarin) that uses my web services which in turn talks to google apis.
Given that I don't talk directly to google apis from the phone, do I need to use a web browser control to let the user login, or can I authenticate my server and establish a session natively on the phone?
I'm afraid I need to use a browser window, and I don't think it's user friendly to have to login to a google account in a browser window on the phone.
(I want to keep the logic on the server, so that the phone apps and the web ui uses the exact same code for it's calculations.)
EDIT: It seems Cross-client identity is what I'm looking for.
It can be done natively. See this video https://www.youtube.com/watch?v=9wAx39s10yw which explains all of the procedure using cross-platform authentication.