Chapters

Hide chapters

iOS App Distribution & Best Practices

First Edition · iOS 14.4 · Swift 5.3 · Xcode 12.4

Section I: iOS App Distribution & Best Practices

Section 1: 17 chapters
Show chapters Hide chapters

11. Managing Secrets
Written by Pietro Rea & Keegan Rush

You may not have known, but Emitron has a secret — something that Emitron’s developers want to keep hidden from prying eyes. In fact, many apps work with one or more secrets: a special token that some APIs require, known as an API secret!

A secret is private data that your app needs to function. It could be an API secret, also known as an API key, or a password to a particular service or tool, like database credentials.

Many web services require that you use a secret when accessing their API. An API key is a private token that’s unique to you. By providing your secret when making API calls, the owner of the API you’re using can verify your identity.

There could be one API key per app, or keys could be unique for each developer. They let the creators of an API know who is using (and possibly abusing) their service. For paid services, it lets the service provider charge based on your usage.

To use an API that works with secrets, you need to add code to your app to send the secret on every API call. That’s the easy part. Choosing where to store your secrets is a little trickier.

In this chapter, you’ll learn of some choices you can make when managing your secrets. You’ll use a special build configuration file to store Emitron’s secret, and learn about the tradeoffs between different approaches of secret management.

Along the way, you’ll pick up some new skills to use with build configuration files. Time to get started!

Why secrets are secret

A secret is a sensitive piece of data, like a password, that you need to protect from prying eyes. Revealing an API key isn’t as bad as revealing your database credentials, but if someone has your API secret, they can use it to authenticate with that API as if they were you.

For something like an analytics API, having someone else authenticating as you can muddy up your data. For paid services like Amazon’s AWS, it means someone else is using the service you paid for.

Even if exposing an API secret won’t hurt you directly, it likely hurts the API provider that gave you the API key. So, it’s important that you keep it secure, because one day, you might be the one creating the API! :]

How secrets get exposed

Your API secrets could be exposed to three groups of people:

  1. Anyone that downloads your app.
  2. Anyone with access to your Git repository.
  3. Other developers you work with.

When you make an API call in your app, you need to provide the secret as well. If the secret is coded into the app, that means your secret is tucked away on every iPhone that has your app installed. A user with enough know-how could potentially reverse-engineer your app to get to your secrets.

On top of that, you’re probably storing your code in some form of source control. Anyone with access to your app’s source control repository has access to the secrets stored within. If your repository is public, everyone has access! In fact, as a research team from North Carolina State University revealed, every day thousands of new public GitHub repositories expose new secrets.

Even if you make your repository private, you still run the risk of other developers on your team gaining access to secrets that weren’t meant for them. Sure, your fellow developer sitting beside you probably won’t use your API key for evil, but the fewer people that have access to important secrets, the better. If everyone on a team has access to credentials for a production database, for instance, the possibility of making mistakes with client data skyrockets.

Next, you’ll take a look at Emitron’s secret, and learn how you can prevent it from becoming exposed to other developers or the general public.

Secrets in Emitron

In Xcode, open AppDelegate.swift. In applicationDidFinishLaunching(_:), find the line that initializes guardpost:

guardpost = Guardpost(
  baseUrl: "https://accounts.raywenderlich.com",
  urlScheme: "com.razeware.emitron://",
  ssoSecret: "155bdf4d4f847e77aec11624ab9c17b4",
  persistenceStore: persistenceStore)

Guardpost is the raywenderlich.com API’s authentication service. The ssoSecret parameter ensures secure communication with the service. A consumer of the API should generate their own SSO secret, but the open-source Emitron app comes with a sample secret that you can use.

The sample secret is already hard-coded into the initializer for Guardpost:

ssoSecret: "155bdf4d4f847e77aec11624ab9c17b4"

The problem with hard-coded secrets

Secrets are subject to change. The SSO Secret that’s hard-coded in AppDelegate.swift is only a sample. If you were building your own app with the raywenderlich.com API, you’d need your own secret.

For larger apps, different build types use different secrets. The SSO Secret that the app uses for a release build might not be the same as the one used for an alpha build. What’s more, the secret can change between developers on the team.

When you’re swapping out a secret for one that’s specific to you, or specific to a particular build type, you need to edit the code that stores the secret.

If you leave your secret in code, its days as a concealed bit of information are numbered. Your secret is there to see for anyone looking at AppDelegate.swift. If you use Git or a different kind of version control, your secret is public to anyone that has access to the repository!

Swapping out secrets by build type can also lead to mistakes. You have to be careful to use the correct secret for the correct build type.

Instead, when using secrets, you want to store them somewhere that:

  1. Allows you to “set it and forget it” so you don’t have to change the secret when you change build types.
  2. Prevents other developers from changing the code to match their own secrets.
  3. Protects your secret from prying eyes looking at the code in a public repository.

As it turns out, build configuration files work for more than overriding build settings. They’re a solution for secrets management, too.

Secrets in configuration files

By putting your secrets into a build configuration file, it becomes easier to change them based on build types.

Storing secrets meant for alpha builds in Alpha.xcconfig and those for release builds in a Release.xcconfig will automatically swap out your secrets when you change build types.

So, using your existing build configuration files solves the first problem of storing secrets in code, if your secrets change depending on the build type. But, if different developers use different secrets, you still have to edit the configuration files to update them for your own secrets. For configuration files checked into Git, version control and sharing code are just as messy as they are when storing the secrets in code.

The solution is to create a new configuration file — one that isn’t shared or added to version control. Here’s the recommendation for how to handle secrets in your projects:

  1. Create Secrets.xcconfig to store your secrets.
  2. Keep the configuration file out of version control. Add it to .gitignore if you’re using Git.
  3. Reference your secrets in code.

Next, you’ll create and use Secrets.xcconfig to store Emitron’s SSO Secret.

Step 2 is important because, if your secret is version controlled, it’s available for anyone with access to the repository to see. The open-source Emitron app uses Git, but your sample project version of Emitron does not. You won’t have to change any .gitignore files this time.

Secrets and security

Keeping your secrets in a configuration file solves the problems mentioned above, but that still isn’t the most secure option.

If someone tries hard enough, there’s always a way to get to a secret that’s compiled into your app. Think of it like keeping a secret diary. You could hide the diary and put a lock on it, but someone determined enough can still find a way in.

The only true way to keep secrets from prying eyes is not to compile them with the app at all. Instead, think about fetching your secrets from a secure and trusted server.

So, without further ado, it’s time to learn how to store your deepest, darkest API secrets in a configuration file. :]

Storing the SSO secret

For the secrets configuration file, you’ll do something similar to Dev.xcconfig and Alpha.xcconfig.

In Xcode’s menu bar, click File ▸ New ▸ File… and choose the Configuration Settings File template.

Click Next. Change the name to Secrets and change the group to Configuration. Leave Targets unselected.

Next, replace the contents of Secrets.xcconfig with this:

SSO_SECRET = 155bdf4d4f847e77aec11624ab9c17b4

By doing this, you’ve created a brand new build setting named SSO_SECRET.

Whether in build configuration files or directly in Xcode’s UI, you aren’t limited to the many build settings that Xcode provides. You can create your own, too.

Applying the secrets configuration file

In the Project navigator, click on the Emitron project to reach the project screen. Make sure you’re on the project’s Info tab.

Now, in the Configurations section, click the icon next to the Debug configuration to expand it. Then, do the same for the Release and Alpha configurations.

Here, you’ll see that under the Debug configuration, the Emitron target’s configuration file is set to Dev. Above it, the Emitron project configuration file is set to None.

Click on the drop-down to the right of the Emitron project and change its value to Secrets.

If you wanted to set a different secrets configuration file for each build configuration, you’d do that here. But because you only have one secret, and that secret is the same for each build type, you can set Secrets.xcconfig for every build configuration.

So under the Release configuration, click the drop-down to the right of the Emitron project and change its value to Secrets as well. Then, do the same for the Alpha configuration.

Here’s how your configurations should look when you’re done:

No matter which build configuration you use, you’ll have a SSO_SECRET build setting that’s set to the sample value.

To prove this, change from the Info tab to the Build Settings tab. In the search bar, search for SSO_SECRET:

Your secret is set and ready to go.

Configuration file imports

While setting the project’s configuration file to Secrets.xcconfig, you may have noticed that you can’t have multiple configuration files at the same level.

You’ve set the secrets configuration file at the project level for each build configuration; that means you can’t use another configuration file at the project level. You also can’t apply Secrets.xcconfig at the target level because that level is already taken by the Dev and Alpha configuration files, respectively.

What if you already filled the slots for both the project level and target level? There would be nowhere to apply Secrets.xcconfig. Luckily, you can still import one build configuration file into another.

If you wanted to import Secrets.xcconfig into Alpha.xcconfig, you’d do so like this:

#include "./Secrets.xcconfig"

By adding the include statement in a configuration file, such as Alpha.xcconfig, you can use the build settings available there without applying Secrets.xcconfig in the project’s Configurations section like you did earlier.

Note that the include statement takes a path to the configuration file. "./Secrets.xcconfig" assumes that Secrets.xcconfig is in the same folder as the file that’s importing it.

Next, it’s time to use the secret in place of the hardcoded value in AppDelegate.swift.

Referencing build settings in code

Unfortunately, your Swift code can’t directly access any build settings. But, your code can read values from your app’s Info.plist, which is a file containing special metadata for your app.

In Xcode, open Info.plist. Here, you see some important metadata such as the bundle identifier, product name and app version.

See the image below:

But wait, isn’t the bundle identifier actually a build setting that you’ve been manipulating in your configuration files? It is, and the Bundle identifier key that you find in Info.plist is a reference to the PRODUCT_BUNDLE_IDENTIFIER build setting you worked with earlier.

See, an entry in Info.plist can reference a build setting. By adding the bundle identifier, product name and app version to Info.plist, you can indirectly reference the underlying build settings in code:

Your code can read Info.plist, which can then read the build settings.
Your code can read Info.plist, which can then read the build settings.

Adding the SSO secret to Info.plist

You need to put the SSO secret in Info.plist for it to be accessible in code.

At the top of the file and to the right of Information Property List, click the + button.

Change the Key to SSO_SECRET, leave the Type as String. Change the Value to $(SSO_SECRET).

Now, you have a value in your Info.plist that’ll work in code. The SSO_SECRET key references the SSO_SECRET build setting, so you can use whatever secret you’ve stored in your secrets configuration file.

Getting the value of the SSO secret in code

Open AppDelegate.swift. In applicationDidFinishLaunching(_:), replace initialization of guardpost:

guardpost = Guardpost(
  baseUrl: "https://accounts.raywenderlich.com",
  urlScheme: "com.razeware.emitron://",
  ssoSecret: "155bdf4d4f847e77aec11624ab9c17b4",
  persistenceStore: persistenceStore)

With this:

// 1.
let ssoSecret = Bundle.main.object(
  forInfoDictionaryKey: "SSO_SECRET") as? String
guardpost = Guardpost(
  baseUrl: "https://accounts.raywenderlich.com",
  urlScheme: "com.razeware.emitron://",
  // 2.
  ssoSecret: ssoSecret ?? "",
  persistenceStore: persistenceStore)

Here’s what’s happening:

  1. Get the SSO secret from Info.plist, which in turn references the SSO_SECRET build setting.
  2. Pass the secret to the initializer for guardpost instead of a hard-coded string.

Build and run. You need to be signed out to test the SSO secret. If you’re signed in, then either sign out or use a different simulator.

On Emitron’s login screen, tap Sign In. Without an SSO secret, you won’t be able to get to this screen:

So, if you get that far, your SSO secret is loading correctly from Secrets.xcconfig!

By referencing your secret in AppDelegate.swift, you’ve delved into the world of bringing your configurations into code via your app’s Info.plist.

Storing secrets in a build configuration file and keeping it out of source control lets different developers use different configuration files. Everyone on the team can have their own version of Secrets.xcconfig.

Because Secrets.xcconfig isn’t stored in version control, each developer’s copy of the file stays on their local machine, removing the danger of an embarrassing exposé of your secrets in a public GitHub repository.

Key points

  • Secrets don’t belong in code, but they can be stored in configuration files.
  • Leaking an API key isn’t as bad as leaking a database password, but you should take care with any secret.
  • You can create your own build settings and use them how you choose.
  • Build configuration files can import one another.
  • You can’t access build settings in Swift code directly, but you can access entries in your Info.plist.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.