You’ve written an open source application for demonstration or illustrative purposes, and your application requires REST API keys for services such as Facebook, Twitter, Wunderground, or Parse or… You get the idea. How do you submit your application to Github for others to use without exposing your own API keys? I can’t take credit for the original idea, but here’s a Swift take on technique described by Mike Lazer-Walker.
We’ll be using iOS property list files and a Swift wrapper. The highlights here are that we are not checking our property list into source control (Git), but our project will reference a file called ApiKeys.plist
. If you download the project from Bitbucket and try to run you’ll get an error because the file ApiKeys.plist
is missing. This is where you create your own ApiKeys.plist
and put in your own API keys.
Note: This technique we are using here is specific to git
. If you are using a different revision control system look up the feature for how to ignore certain files in your source tree (for example, in Subversion you would use the svn:ignore
property).
The general technique is to add a .gitignore
file to the root directory of our project and add the following line:
ApiKeys.plist
and then, to create a ApiKeys.plist
local to your machine with something similar to the following content:
[code]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>API_CLIENT_ID</key>
<string>MyAPIClientID</string>
<key>API_SECRET</key>
<string>MyAPISecret</string>
</dict>
</plist>
[/code]
Once we have that we need a clean mechanism for obtaining the values for our keys. This is done through a utility file in Swift (we call it ApiKeys.swift
for simplicity) with the contents:
1 2 3 4 5 6 7 8 9 10 |
import Foundation func valueForAPIKey(named keyname:String) -> String { // Credit to the original source for this technique at // http://blog.lazerwalker.com/blog/2014/05/14/handling-private-api-keys-in-open-source-ios-apps let filePath = NSBundle.main().path(forResource: "ApiKeys", ofType: "plist") let plist = NSDictionary(contentsOfFile:filePath!) let value = plist?.object(forKey: keyname) as! String return value } |
The code is straightforward and meant to be called like this:
1 |
let clientID = valueForAPIKey(keyname:"API_CLIENT_ID") |
The routine looks for the ApiKeys.plist
file in the application’s resource bundle, loads it as an NSDictionary
and then looks up the value for the given key as a String
.
It should be pointed out that what this code does not do is in any way obfuscate the contents of the plist
. That is, your iOS binary will have the keys included in the resource bundle and could be extracted by someone intent on getting at them.
Now, let’s look at the specifics on how to use this technique in your Swift projects.
Step 1. Add ApiKeys.plist
to your .gitignore
file
This is important; failure to do so will cause Xcode to try to automatically add your plist
file to revision control. We don’t want that.
Step 2. Create ApiKeys.plist
You can either create ApiKeys.plist
by hand or use Xcode to create it. Just make sure that you’ve added ApiKeys.plist
to your .gitignore
file!
To create ApiKeys.plist
use File – New – File in Xcode and create a Resource file of type Property List. Select Next and name the file ApiKeys
(the plist
file type will be created automatically). If you name it something other than what you put in your .gitignore
, it will get added to revision control, which is not what you want..
Also ensure that the file is included in your application target.
Now add the API keys you need for your application to your ApiKeys.plist
file. Select the ApiKeys.plist
file in Xcode and click on the Root
dictionary line, then right-click and choose Add Row. Double-click on the text that says New item and rename it to API_CLIENT_ID
. Double-click on the Value column and type in your actual string. For our purposes I’ve just put MyAPIClientID
.
Step 3. Create ApiKeys.swift
We want a simple wrapper for obtaining our keys, so create a new file called ApiKeys.swift
and in it place the following:
1 2 3 4 5 6 7 8 9 10 |
import Foundation func valueForAPIKey(named keyname:String) -> String { // Credit to the original source for this technique at // http://blog.lazerwalker.com/blog/2014/05/14/handling-private-api-keys-in-open-source-ios-apps let filePath = NSBundle.main().path(forResource: "ApiKeys", ofType: "plist") let plist = NSDictionary(contentsOfFile:filePath!) let value = plist?.object(forKey: keyname) as! String return value } |
Step 4. Use the valueForAPIKey
Routine
1 |
let clientID = valueForAPIKey(named:"API_CLIENT_ID") |
And that’s it! Once you have your clientID
you can add it to whatever REST calls you need to (or if you are dealing with an annoying API add it as a special HTTP X-
header).
Now, I will confess, it is unclear why there isn’t a more elegant way to do this in Xcode. Although this technique is serviceable it isn’t very DRY (don’t-repeat-yourself), but then again, how many times have started a new project only to repeat the same “bootstrap” steps such as adding AFNetworking, CocoaLumberjack, and all of the other popular frameworks? What would be handy is the ability for Xcode to obtain API keys and other confidential information from an application like Keychain and compile them in a secure manner. Que serĂ¡, serĂ¡ I suppose.
Great job but… With this you can only return Strings from .plist. So I made a little bit different function so you can return any value from .plist. This is the code:
DO you have any ideas in the case of deployment where you would store the API Keys to keep them secure ?
See https://dev.iachieved.it/iachievedit/obscuring-api-keys/ for a more secure approach.
Even if this method will hide the API key in a git repository, how secure is this method in the app binary? I guess it’s not the securest method to store an API key in an app.
Mathias, you are correct, this isn’t the most secure approach, thus I wrote up this post as well: https://dev.iachieved.it/iachievedit/obscuring-api-keys/ Ignore my previous reply, I thought you were commenting on this post!