Safeguard
Application Security•

Where should a Swift app store secrets, and what never to ship?

Never ship API keys inside the app bundle. Store user secrets in the Keychain with the strictest accessibility that works, and fetch service keys from a server.

Safeguard Security Team
5 min read

A Swift app should keep user secrets such as tokens and passwords in the Keychain, choose the most restrictive accessibility setting that fits, and never embed service API keys or private keys in the app package. Anything inside an IPA can be recovered by whoever downloads the app, so shared secrets belong on a server.

What counts as a secret in an iOS app?

Two different things get called secrets. The first is per-user material: a refresh token, a session token, a saved password. The second is per-app material: an API key for a third-party service, a signing key, a database credential. They need different homes.

Per-user secrets are created at runtime on the device and belong in the Keychain. Per-app secrets are created by you before release. If you put them in the binary, a plist or an asset catalog, you have given them to every user.

Why can't you hide a key in the app?

OWASP's mobile weakness entry on hardcoded data (MASWE-0004) says it plainly: the weakness occurs when sensitive data is embedded in the app package and shipped with it, "where anyone who downloads the package can recover it." It lists API keys, credentials, session tokens and cryptographic keys as examples.

Obfuscation, string splitting and build-time injection change how hard the key is to find, not whether it can be found. A key injected from an xcconfig file at build time still ends up inside the compiled app. The question to ask is not "how do I hide it?" but "does the app need this key at all?"

The usual answer is to move the call behind your own backend. The app authenticates the user, the server holds the service key and makes the third-party call. If a key must live on the device, scope it tightly, make it revocable per install, and expect it to leak.

How do Keychain accessibility settings work?

Apple's documentation on restricting keychain item accessibility explains the choices. The default is accessible only while the device is unlocked, and it notes that a device without a passcode counts as always unlocked. The options, in decreasing restrictiveness:

  • When passcode set: the item can only be stored if the user has a passcode, and it is deleted automatically if the passcode is removed.
  • When unlocked: the default, suited to an app that needs the item only while in the foreground.
  • After first unlock: available once the user has unlocked the device after a restart, which suits background work.
  • Always: accessible regardless of lock state. Apple says this option is not recommended.

Attributes ending in "This Device Only" mean the item can be restored from a backup to the same device but is not migrated when restoring another device's backup. Apple's guidance is to use the most restrictive option that makes sense for your app.

Apple also describes demanding user presence: an access control object can require Face ID, Touch ID or the passcode immediately before the item is read. That protects a bank-style app even if the user hands over an unlocked phone.

What goes wrong in practice?

Three patterns. Tokens stored in UserDefaults, which is plain preference storage, not a secret store. Secrets written to log output, which OWASP lists as its own weakness. And the "always" accessibility class used because background fetch failed in testing, when "after first unlock" was the correct fix.

A constructed example: a fitness app stores a refresh token with the default class and also reads it from a background task. Reads fail after a reboot until the user opens the app. The developer switches to the "always" class. The right change was "after first unlock", which fixes the failure without exposing the token to a locked device.

What to do

  1. Inventory every secret in the repository and the built app. Run a secret scanner on source and on the unzipped IPA.
  2. Move per-app keys behind a server you control.
  3. Store per-user secrets in the Keychain, never in UserDefaults, files or logs.
  4. Pick the strictest accessibility class that works, and add "This Device Only" for secrets that should not migrate.
  5. Require user presence for high-risk actions.
  6. Plan revocation: tokens should be short-lived and revocable from the server.
  7. Check third-party Swift packages as well. The free scan at gold.safeguard.sh/scan covers up to 100 dependencies.

Quick check

Is it safe to store an API key in an obfuscated string?

No. Anything in the app package can be recovered by someone who downloads it. Move the call to a server.

Which Keychain class does Apple advise against?

The class that makes an item accessible regardless of the lock state. Apple says that option is not recommended.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.