Secrets handling in iOS

Cover Image for Secrets handling in iOS

I think this topic is one of the most crucial one, and yet, also one of most commonly overlooked practice by many ios developers.

And it could be several factors why:

  • though using environment variables are common in web development, in iOS development, XCode's environment variables is nothing more than just keyvalue pairs that are only accessible only when you run your app from XCode, and once you deployed it or run it without XCode, you also will lose access to those keyvalue pairs.

  • iOS has standard file for configuration called xcconfig, in which configuration can be also a secret key, which then dev must have to commit for the production deployment. And to be honest, accessing the values from here is not really great because you have to access the Info.plist, which is also not really secured especially for storing secrets, so meaning, you already exposed the production secrets not only to all your developers but potentially from those people who did jailbrake their devices or people that can have the ipa file.

  • Services like Firebase uses a plist file for the client credentials, which could be one reason why devs can assume that it is safe to store secrets in plist. Remeber how easy to read the plist file and other resources like images, fonts and etc from the ipa file, all you have to do is to rename the ipa file to zip file then extract it.

So how do we improve this process?

Don’t git commit any Secret (or atleast production secret)

Yes, once you done this, it is now part of the history (unless you have the admin power to rewrite the origin's history)

Don't store any secret to any unsecured places in the device like file inside application folder or user defaults.

Though you might wondering how about Keychain? Yes, Keychain is secured place, BUT, it must be coming from somewhere remotely right? And that remote should not be publicly accesssible and and must have way to access them from your app, which usually done by using a client secret in which you still have to save somewhere inside the app.

Encrypting and Decrypting your secrets

This is one option to avoid displaying raw secrets inside the repository. Encryption/Decryption can be done using one of several libraries like blackbox, git-crypt, transcrypt, git-secret and etc.

But you still have commit those files that contains secret information into your repository. One thing I don't like with using this approach is that whenever you have to change the secret or add or remove a user, you have to do some manual process depending on library that you used, which is kind of a hussle if you have several devs working in the same project.

Using the Environment Variables (I personally preferred)

Yes, we can still the use environment variables for this.

For development, you can share a file that contains all the secrets needed for the development or you can simply commit those to your repo because I don't see any sense of sensoring these devevelopment secrets to the developers (unless it's a public repo), and at the end of the day, they can easily see those values on runtime through debugger and development secrets can be easily revoked and changed so don't put a burden to developers just to fetch the secrets needed for development.

But it is a different story when it comes to production build. This is where we are going to use the environment variables from the CI/CD.

For this process, we will going to create a file called AppConfig.swift. Yes, this is a swift file. The reason is, compare to Xcconfig or plist file which are resource, swift file is part of the compilation and Code obfuscation process.

For example:

// AppConfig.swift
#if DEBUG
  static let apiClientSecret = "somesecretforapiclient"
#else
  static let apiClientSecret = "$(API_CLIENT_SECRET)" // Placeholder for the CI/CD Env.
#endif

So for example using Github(but applicable in other CI/CD platform as well), it's just Github has dedicated name and place (which is Secrets) to store secret information, other CI/CD platforms still call them environment variables though😅, during the pre-compilation process inside CI/CD, we will going to replace these placeholders with the actual values coming from the environment variables.

You can use library like variable-injector or simply using bash script sed.

Eg:

Using variable-injector:

$ variable-injector --file ${SRCROOT}/YourProject/YourGroupFolderPath/File.swift --verbose

And that's it.