Don’t ship API keys!

Not so long ago, I read about this article from Thomas Vahter suggesting the distribution of API keys through on-demand resources. Although this approach is appealing, I suggest a different approach: avoid shipping your API keys at all costs!

What are API keys for anyway?

The best way to actually look at an API key is as a simplified authentication mechanism:

Speaking of that, not all API keys are born equal:

Someone using your RevenueCat public key will only be able to fetch entitlements if they also know the customerID associated, and allows very few operations like creating new users with no entitlements, or register the money they spent to their account. Nothing too harmful as the RevenueCat documentation specifies:

RevenueCat provides subscription status via the public API, having App User IDs that are easily guessed is not good. It is recommended to use a non-guessable pseudo-random ID.

On the other hand, someone using your OpenAI key can make arbitrary requests to the entire OpenAI API, which are billed to you! This is the kind of key that should never ever leak. Therefore, this is the kind of key you should never ever ship!

Why obfuscation doesn’t work

There are various practices for shipping API keys, ranging from hard coding to using on-demand resources, all with the consensus to never leak them through source control. Avoiding committing and pushing your keys in Git is already a good start!

Now, whatever the technique you use to obfuscate the key inside your app, there is one catch: it’s somewhere on a device you don’t own and you don’t trust.

No matter how hidden it is from an attacker, it is sent over the network as soon as you make an outgoing call to the API.

And even with TLS enabled, it is very easy to intercept requests on a device you own. The excellent Proxyman app does exactly that using a self-signed root certificate authority and a VPN.

Preventing yourself from a man-in-the-middle attack means using Certificate Pinning techniques in your app. But this approach is viable only if you actually manage the TLS certificate renewal process, and it’s definitely not the case for external APIs like OpenAI. Plus, it means that the apps version you ship will all have an expiration date because they’ll stop working and require an update as soon as the TLS certificate is replaced.

Where to put your sensitive API keys then?

The response is fairly straightforward: put them somewhere you can trust, which is the backend!

Having your own backend is indeed more expensive than just shipping an app, in terms of maintenance and hosting. But thankfully, it not only provides more secure storage for your keys but also allows:

Having your own business logic for authorization
If you’re paying for your API access, you might wanna check with your in-app purchase provider servers (like RevenueCat or Purchasely) the current payment status of the user requesting your API.
Revoking and updating API keys
Without requiring an app update, and breaking older version of the app.
Hiding implementation details
Maybe you don’t want people to know exactly what you send to OpenAI API after all? You can minimize the amount of data that your app send to your server, save your customer bandwidth, and you can improve them on the fly without shipping a new app version!
You don’t have to switch languages
With Vapor, you can comfortably develop all of this using Swift. Sure, there is not yet Swift Foundation integrated to the framework, but can hope for the best in the future.
It doesn’t have to be too complex
Most of these functionalities can be built as a cloud function. And easy hosting solutions like Heroku or Platform.sh can save you maintenance and setup time for your server.

In a nutshell

Make a distinction between sensitive and non-sensitive API keys in your app, and never ever ship the sensitive one with your apps!

Build a small cloud function, or your own API in between, and make sure to encapsulate as much business domain logic as possible in it! And depending on your business model, consider validating the request either with your in-app purchase provider, or directly with Apple. For pay-upfront apps, you can also validate them, a feature available since iOS 14.

The most important thing to consider is that nothing included within your app should be considered trustworthy in any way.


Don’t miss a thing!

Don't miss any of my indie dev stories, app updates, or upcoming creations!
Stay in the loop and be the first to experience my apps, betas and stories of my indie journey.

Thank you for registering!
You’ll retrieve all of my latest news!

Your email is sadly invalid.
Can you try again?

An error occurred while registering.
Please try again