I've integrated (but not enforced) App Check within an iOS app of mine, and have a number of requests that are apparently invalid - that is, the requests have an invalid App Check token. I am using Apple's App Attest as the Attestation Provider.
The two example reasons given for this occurring are
"inauthentic client attempting to impersonate your app"
"from emulated environments"
I don't think 1 is happening, because I have a tiny user base (< 10 active users). I also don't think 2 is happening. I very rarely use emulated environments; I prefer building and testing on a physical device.
From troubleshooting on my own, it seems like the issue has to do with me using a debug build on my physical device vs a release build. I followed the instructions here: https://firebase.google.com/docs/app-check/ios/debug-provider, and the errors have apparently dropped to zero for the last 24 hours.
I have two questions:
Is this conclusion correct? That is, is it possible that me using a debug build is what was causing all those unverified requests? The two examples given don't mention anything about a physical device, so I'm not sure if this conclusion is right.
If the answer to 1 is yes, what is the correct workflow setup I should be using? It seems like the debug tokens have the same TTL as the normal App Check tokens (i.e. 1 hour), and manually uploading a token every 1 hour while developing doesn't seem scalable. Is it possible to have the debug tokens have a longer TTL? Or is it possible to upload these tokens through code vs having to manually add via the firebase console?
Firebaser here!
A debug build of your App should fail App Check, part of the verification is that a legitimate device is running a production build of your app. In that regard it is intended and expected.
Using Debug Tokens is the correct approach, the debug token itself will never expire but will be exchanged for an App Check token which will expire in 1 hour or whatever you have your TTL set as by the App Check SDK.
Related
We now know this is an issue with IOS 11.3, and seems to target only Ipad.
When requesting ressources through appcache, the cookies are discarded. If your ressources are behind some authentication. They will be redirected to your authentication page.
As mentioned by Apple, we tried experimentation feature on/off.
Removing authentication for ressources is not a valid permanent option.
We are looking for solutions while the next version of IOS hopefully fix this problem which seem the case with 11.4 beta 2. Changes are to be as minimal as possible to reduce risks.
Following informations are the process we when through when trying to solve our problem. To this day, no valid solutions have been attempted. Service Workers being the most plausible path.
Day 1
We have an application which was running fine in production for a while (almost a year since last deployment).
Our application use app cache to enable offline mode when wifi is not available.
Our application is mostly used on ipad with safari and some surface pro with chrome. Currently most cases are reported with ipad.
In the last few day, more and more users start to have problem loading the cache. We have been able to reproduce the probleme on an ipad after updating to 11.3 (could not reproduce on iphone 11.3) and using google chrome desktop incognito mode on dev machine. Application work on an older iPad we have which is at version 10.3.3.
--Application Cache Error event: Resource fetch failed (2) http://localhost:63330/client/vendor/kendo-ui/kendo.all.js--
Fact
- It always block on the same files, after some testings it seems to be all files bigger than 1.2Mb, in this case kendo is 4.7Mb and the minified one is 1.7Mb.
- Fiddler does not report any error, all files status response is 200
Guess
1. An update to safari and chrome might have changed
2. An ipad setting might have been changed by admins
3. An update to windows or ios, might have changes something
Since safari and ios follow the same release (29 Mar 2018), they are probably linked and the most likely guess, does anyone have an idea why this might happen?
Could not find much on apple support page of changes for 11.3
https://support.apple.com/en-ca/HT208067
https://support.apple.com/en-ca/HT201222
Update Monday 9 april 2018
We have been able to reproduce the issue both by debugging an Ipad and on the mac mini we have. However, the problem is different and for this reason we currently discarded what we found on Chrome on our desktop in incognito.
Here are the new facts:
Cookies were not provided while downloading file with appcache. The first file request is rejected and redirected to login page.
[Warning] ApplicationCache is deprecated. Please use ServiceWorkers
instead. (192.168.0.152, line 2)
[Error] Failed to load resource: the
server responded with a status of 401 (Unauthorized) (cache.manifest,
line 0)
[Error] Application Cache manifest could not be fetched,
because the manifest had a 401 response.
[Error] 2018-04-09 12:01:51 :
APPLICATION CACHE error
logMsg (logDecorator.js:111)
error (logDecorator.js:128)
(fonction anonyme) (applicationCacheUpdateSrv.js:121)
dispatch (jquery-1.10.2.js:5109)
handle (jquery-1.10.2.js:4780)
- After effect of solving authentication, we have problems with IDBDatabase, might be related to Authorization we removed (currently under investigation)
IndexedDB request error (get all rapports) -> NotFoundError: Failed to
execute 'transaction' on 'IDBDatabase': One of the specified object
stores was not found.
We found this by using Charles Proxy for Mac. For this reason, we removed authentication to our statics files and Home page. This seems to work, but our files would be public which is not really an option.
Similar questions:
Cache-Manifest How to handle authentication cookies?
Update Monday 10 april 2018
IndexDB error are not related. Databases were not initialized properly due to authorization missing.
We currently added an alternative home page where authorization is not required instead of removing authorization from the default home page. It would be called by the manifest cache and downloaded properly.
Update Monday 12 april 2018
We tried to secure the static files, we ended up with adding a token in the query url. While it work and we can authenticate the request (note that since we do not have cookies to authenticate the user, the authentication is far from flawless), the Url is now different than what was requested in the cached Home page and make the custom authentication worthless by itself.
We would need to also rewrite all the url for the cached page base the token genereted by the user. In our cases, it involve throwing out the ASP.Net MVC Bundle feature to maybe make a custom one? At this point, we think it might just be easier to try ServiceWorker since appcache is deprecated. This does not guarantee cookie will be passed on with ServiceWorker...
Update Monday 19 april 2018
We had some return from Apple yesterday. They asked be try Prevent Cross-Site Tracking property (both on and off) in Settings > Safari and also try Experimental feature in Settings > Safari > Advanced > Experimental (mentionning ServiceWorkers, but tried them all)
Unfortunately it didn't change anything for me at the moment.
Note: strikethrough some part that were not related directly to the issue
Update Friday 25 may 2018
Added a section to the top to resume the situation and make the question more to the point.
As of 25 april 2018, we tried iOS 11.4 beta and it seem to resolve the issue. According the the deployement timeline, it should be available in about a month according to this.
However until then moving to service workers might be a good idea.
Here is an example from google.
Here is a video which gives an introduction to the subject
I will add an example if we get to move to Service workers
I tested Universal Links in iOS by turning on Airplane mode and saw that the correct application was opened (instead of a website)
This indicates some level of "caching" the apple-app-site-association.
I want to determine the extent to which this is cached, so I can determine
What UX edge cases are there (e.g. Offline for x days)
What security considerations are there (e.g. MITM / SSLStrip + .well-known/URL)
etc.
Ideally I would like to have details if additional logic is employed (conditional caching if HTTPS employed, DNSSec, etc)
The exact behavior here is (intentionally?) unclear from Apple. Here is my personal experience, gleaned partly from official documentation and partly from helping thousands of apps implement Universal Links at Branch.io.
The apple-app-site-association file is cached once when the app is first installed.
If this initial scrape fails, in almost all situations it will not be reattempted. The only exception to this is if the initial return is a 5xx error, in which case a limited number of retries may occur. This is not well-documented, and is not covered in Universal Links documentation at all. You can find a mention in the Shared Web Credentials docs.
The file is not checked at all when a Universal Link is opened. This is why you are able to get Universal Links behavior in airplane mode.
The file does not expire. Once it is cached, it sticks permanently for as long as the app is installed.
The file will be re-checked when installing an app update.
The file must be accessible via a valid SSL connection at either https://example.com/apple-app-site-association or https://example.com/.well-known/apple-app-site-association. If there are redirects of any kind, this will fail.
It is theoretically possible to MITM the request if you are able to install a new SSL certificate directly on the device in question. Charles Proxy for example uses this approach for debugging. I have never seen or heard of this being exploited, and the damage would be quite limited because the domain still has to be specified inside the app itself.
I found a way to get around the caching issue. The cache is bound to the domain name, so for every time you want iOS to request apple-app-site-association you can create a new subdomain, and configure iOS to use that subdomain as the universal link for your app.
Extremely hacky, but it is the only workaround that worked for me.
I run my own VPS in Amsterdam where I have a MySQL database that is being populated and maintained using ASP.NET. It's a Windows Server.
I use this API for four of my existing Android apps (published and working) with a few thousand users who never had any issues connecting to the API through those apps. Recently I finished one of the apps on the iOS platform and it got rejected because Apple couldn't get it to load any content, or it would get stuck on loading without ever returning anything (after we implemented a loading progress animation). After a lot of messaging between me and Apple's review team, they ended up accepting my app to be passed through review even though they never got it to work (or so I believe, they just said they would re-review it and it suddenly got approved after 7 rejects). None of my friends, family or users ever experienced any issues like this on either Android or iOS.
A good friend of mine who did most of the work on the API is also from the USA, which makes me doubt it's a location problem.
I must note that pretty much 99.99% of my users are Dutch and all my projects are build for Dutch users.
Does anyone have experience or ideas in this field? I'm about to publish an update for the already published app and I'm afraid they will reject it because of the same issue.
The exact message I got at first was:
Specifically, upon launch we found that the app failed to load any content.
Changing the API calls to be over HTTPS seems to have fixed the issue, Apple now has access to my API through all my apps.
We are developing an iPhone application as well as an iPad application.
Both of them do have different Bundle Identifieres but should receive Push Notifications.
The certificates are generated like documented in apples programming guide.
So, due to the fact that we do have four ceriticates (APNS-Dev-iPhone, APNS-Prod-iPhone, APNS-Dev-iPad, APNS-Prod-iPad) I am wondering how to combine those certificates into one file?
The problem is, that on the backend side we are only storing the device token of a user. It is easy to decide whether we are working in development mode or not,but how should I decide which certificate to use - the ipad one or the iphone one?
Due to the fact, that we only have the device token, we do not know if the user is using the iPad application or the iPhone one, so we cannot assume which certificate to use when connecting to the apns apple server.
Any suggestions on how to handle this issue? Should we store more information - for example the device the user was sending the device token from? Or is there any easier way to combine all the certificates into one file and send it over to the apns apple server?
Thanks in advance, I would appreciate any help!
I have a similar issue and have not found a solution that I find satisfactory.
The way that we handle it is similar to what tGilani described. We have to store some kind of identifier that distinguishes which certificate to send a notification through for a particular device token.
Our issue goes further in that we might possibly have to send notifications to different applications altogether. These applications will probably be on a per tenant basis. Thus creating a universal application and one certificate will not work for us.
It would be terribly convenient if I could find a way to combine multiple p12 files into one jks and continue to use one of the Java PNS or Java APNS projects to send notifications.
I'm considering using the CFUUIDCreate API to build a database in my server to measure what percentage of users of each of my apps are running which version of iOS (to help me make a decision on iOS minimum supported version for future development).
My question is: Should I ask the user for permission to send the (anonymous) UUID / iOS version data pair to my server, or is it OK to do it automatically?
I ask because I could bet on the safe side and ask anyway, but most users would feel intimidated by the technical details and most likely decline. Also, the longer the text in an UIAlertView, the more likely the user won't read it.
Will Apple reject my app if I don't ask? Even if they don't, do you think I should always ask the user for permission to send this anonymous data?
What's everyone's experience implementing this kind of functionality?
OK, this is what I'm going to do, based on Nikolai Ruhe's answer but improving on a pitfall I just discovered.
Suppose user John Doe installed my app on his iPhone running iOS 5.0.
On the first launch, the app sends an anonymous request to my server that effectively increases by one the counter for 'Devices running iOS 5.0'. The app records this event and the iOS version (in User Defaults or Keychain) and does not send any further requests as long as the locally stored OS version string and the current one (returned by the system) are equal.
The next week, John upgrades to iOS 5.1 and launches my app. The app detects the OS version discrepancy and sends a new request to my server.
But if this only adds one device to the 'iOS 5.1' population, John's iPhone is now being counted twice: once as "Devices running 5.0" and once as "Devices running 5.1".
So to fix this, my HTTP request should look like this:
http://myserver.com/my_stats_scripts/index.php?app_id=com.myCompany.myApp&new_os_ver=5.1&old_os_ver=5.0
So my database can increase the number of 5.1 devices and decrease the number of 5.0 devices by one.
Of course, on the first request, the HTTP parameter old_os_ver is set to empty, and the server treats it like a new device.
If I pull this right, I no longer need UUIDs. But I am still sending system info covertly to my server. I think I'll also disclose this on the Terms of Use.
You should definitely ask for permission. The crucial bit here is that your data collecting might be anonymous, yet it can be used it to track individual users.
Web browsers send a user-agent string with every request. The difference is that they do not send a universally trackable id that would never change.
So the problematic piece in your proposal is the UUID. Why not just leaving it out? Your app would send an anonymous request once and locally store the transmitted iOS version. When the user upgrades the OS, you send another request with the new iOS version and an update count.
Using this scheme you would not transmit trackable data and still get a proper usage-by-os statistic.