How to Secure iOS App Secret Data With Vault
Working on mobile apps, as developers, we interact with APIs all the time. In order to connect to these APIs, we use API keys. These API keys are very important as it maps/identifies us as a unique user of the system we are trying to integrate with. We always need to make sure these API keys are not accessed by unintended users. Rate limiting, quota control, and security are some of the reasons why API providers have API keys.
One of the good security practices is not to save secrets and API keys as part of your source code. But if we don’t put them in the source code, how will our code know about them and consume it. Well, the answer to the problem is using a security tool called “Vault”. There are various vault options to store secrets, we will talk about the Hashi Corp Vault System. In this article, I will walk you through how to integrate vault with an iOS app.
Prerequisites
- Setting up Vault — Please set up your vault following the instructions mentioned in the link.
- Write Secret — Please make sure you have written your secrets that your iOS application will use.
Integrate with iOS App
Vault can be used to read and write app secrets like API keys. Let's dive into details about how we will actually integrate vault with our iOS app.
Authenticate to Vault
In order to talk to our Vault, we need to authenticate ourselves first. We will use one of the authentication methods that we support in our vault implementation. This will depend on what you chose in the prerequisites. Once you authenticate you get a token that will be used to read your secrets.
Pre Build Script
Add a prebuilt script in Xcode, this will pull the secret from the vault before building your app code. We will use the token retrieved in the previous step to authenticate. Say you wrote a secret on this path /v1/secret/foo of your vault server. You would read it via api call as shown below:
curl -H "X-Vault-Token: {token}" -X GET http://{yourserver}/v1/secret/foo
And you get a JSON response with secrets stored in that path. For eg,
{
"tool1Apikey": "tool1ApiKey",
"tool2Apikey": "tool2ApiKey"
"tool3Apikey": "tool3ApiKey"
}
Save the JSON output as a file and name it whatever you want. For example, I am going to name it as secretData.json. This file will be part of the project directory so that it can be referred locally from our application code.
Reading the SecretData JSON
Third-party libraries usually require initialization right away while our application loads. And to initialize them properly we need API keys. So we will have to read this secret file as the first thing in our AppDelegate. We can easily confine this entire responsibility by making a singleton class say VaultManager, which will read the secretData JSON and expose those values with handy getters that we can use in our app. Code for VaultManager.swift would look something like this.
import Foundation
enum SecretKey:String {
case tool1Apikey = "tool1Apikey"
case tool2Apikey = "tool2Apikey"
case tool3Apikey = "tool3Apikey"
}
class VaultManager {
static let sharedInstance = VaultManager()
private var secretData:SecretResponse?
private init(){
readSecretFile()
}
private func readSecretFile() {
if let url = Bundle.main.url(forResource: "secretData", withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
secretData = try decoder.decode(SecretResponse.self, from: data)
print("success")
} catch {
print("error:\(error)")
}
}
}
func getSecretKey(withKeyName:SecretKey) ->String? {
switch withKeyName {
case .tool1Apikey:
return secretData?.tool1Apikey
case .tool2Apikey:
return secretData?.tool2Apikey
case .tool3Apikey:
return secretData?.tool3Apikey
}
}
}
fileprivate struct SecretResponse:Codable {
let tool1Apikey:String
let tool2Apikey:String
let tool3Apikey:String
}
In the code snippet above, singleton VaultManager will take care of reading the secret and made it available to be used anywhere you need in the app.
Access the read secret
The vault manager in the snippet above exposes a method call
func getSecretKey(withKeyName:SecretKey) ->String?
So if we need to get the key anywhere in the app, all we have to do is call this method and pass the key name. For example:
let keyValue = VaultManager.sharedInstance.getSecretKey(withKeyName: .tool1Apikey)
This way we have totally decoupled the requirement of app secrets for eg API keys to be part of source code.
Conclusion
As a good security practice, we shouldn’t ever store app secrets like API Key in the codebase. They should be packaged with the app dynamically. Vault acts as a great tool to secure your app secrets. Which Vault solution are you using? Let me know your experience in the comments below.