Jonathan Peppers


Xamarin MVP and C# nerd

Securing In-App Purchases for Xamarin with Azure Functions

If you've ever added in-app purchases to your app and track each purchase with analytics, you will quickly see the reports from iTunes (real revenue) don't match up with purchases recorded from your analytics tracking.

Hmm, so what is going on here?

Sadly, it is pretty easy for jailbroken/rooted devices to circumvent in-app purchases on both iOS and Android. To make things worse, any teenager is skilled enough to set this up with a little research on the internet.

So how do I protect my assets?

First of all, keep in mind any protection we add is effectively an arms race between the attacker and the developer. Given enough free time, an attacker will always be able to find a way to get free IAPs in your app. The goal for a developer should be to make it difficult enough to where the attacker see their first few attempts fail. Hopefully this will cause them to move on to easier apps to exploit, and trust me, there are plenty out there.

So how can you protect your IAP revenue?

  • Write the IAP code yourself
  • Get a receipt for every purchase
  • Verify on a server (over SSL) that the receipt is valid

It turns out a little work will cut out almost all piracy against your in-app purchases. Apple recommends quite a bit more protection than this (video here): bundling a custom version of OpenSSL in your app, writing code to parse their receipt certificate format, and then imagine trying to call into that C library from Xamarin, ugh. The act of calling your own server over SSL is going to be more protection for the effort than jumping through all of these hoops.

Write directly against the native APIs, and understand what is going on

Maybe this is a good tip for life, in general. If you are just wanting to get in-app purchases implemented as fast as possible I would recommend trying James Montemagno's plugin (which even has an option for adding server validation). However, if your goal is to prevent piracy, you really need to understand what is going on under the hood. Using someone elses library for IAPs is ineffective against piracy because A) the library developer might have a vulnerability you don't know about, and B) an attacker may attack the library directly. Imagine all the games out there using Unity3D's in-app purchasing plugin. If an attacker found an exploit there, they could get a huge amount of free purchases across many games on the mobile market. Rolling your own in-app purchases is definitely the way to go for preventing piracy, regardless of what technologies your app is built with.

Implementing IAPs yourself is not that hard

Before we get into the weeds, first let's write a base class that can be used on both platforms:

For now, let's just assume these purchases are consumable like purchasing coins, etc. The Purchase and Receipt classes are just DTOs, you can view my full sample project on Github. We only need to implement two simple functions: get the prices (in the user's currency) and actually buying the product.

For our iOS implementation, we will use StoreKit directly. First let's implement GetPrices:

The general pattern here is to Start() an SKProductsRequest, where iOS will post results to an SKProductsRequestDelegate. A good rule to follow for something like this is to use TaskCompletionSource to effectively flatten an ugly callback into a Task and match our nice async API from the base PurchaseService class. I also have a bit of code here to localize the price using native iOS APIs.

Next is the Buy method, a bit more complicated:

What we did here:

  • Setup an app-wide SKPaymentTransactionObserver
  • Call SKPaymentQueue.DefaultQueue.AddPayment to initiate the transaction
  • Grab Apple's receipt from NSBundle.MainBundle.AppStoreReceiptUrl
  • Create our receipt object collecting all necessary data for verifying the transaction on our server

Testing our IAPs

So the C# implementation is one thing, but testing this stuff is also a complete mess on iOS.

The minimum setup for testing GetPrices is:

  • Your app's bundle id matches an app in iTunes Connect (can be unreleased)
  • You have in-app purchases defined in iTunes Connect (can be unreleased and not have screenshots)

So I would verify GetPrices works in your app before moving on, take a look at my unit test as an example of what I used to test. Note that this will still work in the iOS simulator, however Buy will not.

So the remaining setup for testing Buy?

  • Create a sandbox test user in iTunes Connect
  • Log out of iTunes on your device from Settings
  • Setup a development provisioning profile tied to the app in iTunes Connect and deploy to your device
  • Try to Buy, and make sure to login with the test account to iTunes.

Another way to test this, is to submit builds to TestFlight. From there you can actually use real iTunes accounts for testing IAPs. If the app is from TestFlight your credit card will not be charged, but you can test the actual App Store build and verify its correctness.

Note that sandbox purchases act really weird in general: popping up multiple login prompts, prompts appear randomly on your device later, etc. I don't really know why Apple hasn't looked into fixing this, as I've seen this stuff since 2012 at least--and I don't think it's my implementation. I've even seen it in Unity3D apps. I would disregard these issues for the most part, but make sure to test your final build from the App Store and spend real money.

Verifying IAPs with Azure Functions

If your app does not have a backend, then Azure Functions is a perfect opportunity to get your feet wet with a serverless approach and a good fit for validating IAPs.

First let's look at an Azure Function to validate an IAP with Apple:

What we did here:

  • Post to https://buy.itunes.apple.com/verifyReceipt which is Production
  • If Production says this is a Sandbox purchase, call Sandbox at https://sandbox.itunes.apple.com/verifyReceipt
  • The remaining stuff is just validation, verify the bundle id, in-app purchase id, and transaction id all match.
  • Return 200 OK if it is good and a 400 Bad Request otherwise (but don't tell the client why)

You can see the full Azure function on Github here under the api folder. I use file-linking for sharing the Receipt and AppleReceipt classes between my iOS app and my Azure function.

NOTE: in addition to what I have here, you should also keep a record of past purchases somewhere such as an Azure Storage Table. You don't want an attacker to be able to reuse a valid receipt, so I recommend running a query to see if it has been used before. Keeping a log of each Azure Function call and the results should be kept there as well for record-keeping.

Wrapping up the iOS client-side

So on our client, it is not much code to call our Azure function with HttpClient:

Pretty straightforward, calling an Azure function is simple with HttpClient: no third party dependency required.

Other considerations

Implementing IAPs like this is going to stop a huge percentage of piracy in your app, but it is not completely bullet proof.

Some other things to consider:

  • Your Azure function should not allow duplicate transaction ids to be reused
  • You might consider setting up SSL pinning (example here), to make MITM attacks between your Azure function more difficult
  • Unlocking content within your app should be managed somewhere secure such as the iOS keychain, iCloud, your own server, etc.. NSUserDefaults or a simple file are easily modified on a device, so it doesn't help to make the IAP secure if an attacker can modify a local file.
  • Provide a way to unlock content for users if something goes wrong. Ask users for receipt emails. LOG EVERYTHING.

I think that wraps it up. I will cover Android and Google Play in a future post, as this one seems quite long enough.


comments powered by Disqus