Storing API keys securely on iOS
Updated 2026-08-04
If an app holds a credential on a phone, where it puts it is the whole security story.
The wrong places
AsyncStorage / UserDefaults. Plain, unencrypted. On React Native, AsyncStorage is a file in the app's container. Convenient, and completely unsuitable for a credential.
A plain file. Same problem, more obviously.
In a JS variable, persisted to a log. Credentials leaking into crash reports and analytics payloads is a real and common failure. Never log a key, not even truncated in development — those lines outlive the debugging session.
The right place
The Keychain on iOS, Keystore on Android. Both give you:
- Encryption at rest, backed by hardware where available
- Access control tied to device unlock
- Exclusion from unencrypted backups
- Isolation from other apps
In Expo that's expo-secure-store:
import * as SecureStore from "expo-secure-store";
await SecureStore.setItemAsync("stripe_key", key, {
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
const key = await SecureStore.getItemAsync("stripe_key");
The accessibility option matters
WHEN_UNLOCKED_THIS_DEVICE_ONLY is the right default for a revenue credential:
WHEN_UNLOCKED— readable only while the device is unlockedTHIS_DEVICE_ONLY— never migrates to a new device via backup
The second half is the important one. Without it, a credential can travel to a restored device — which is convenient and not what you want for a key that reads your revenue.
Face ID as a second gate
For genuinely sensitive values, require biometric authentication before reading:
import * as LocalAuthentication from "expo-local-authentication";
const { success } = await LocalAuthentication.authenticateAsync({
promptMessage: "Unlock your vault to view API keys",
});
if (!success) return;
FRGMNT gates viewing stored keys behind Face ID, while ordinary data refresh does not prompt — a balance between security theatre and actual friction.
What the Keychain does not protect against
Being honest about the limits:
- A jailbroken device. Keychain guarantees depend on OS integrity.
- A compromised device with the passcode. If someone unlocks your phone, they're inside the trust boundary.
- Malicious code inside your own app, including a compromised dependency.
Which is exactly why the credential itself should be narrow. A read-only restricted key that is extracted is a disclosure; a secret key that is extracted is a loss.
Defence in depth: secure storage, plus a credential that can't do damage.
Why on-device at all
The alternative is a server holding thousands of users' keys — a much larger target, where one breach exposes everyone. Storing on-device means the blast radius of any single compromise is one person.
That trade-off, and its costs, in why FRGMNT has no backend.
Frequently asked
Where should an iOS app store an API key?
In the Keychain, which is encrypted, hardware-backed and excluded from unencrypted backups. AsyncStorage and UserDefaults are unencrypted and unsuitable for credentials.
Is the iOS Keychain safe on a jailbroken device?
Less so. Keychain protections depend on the operating system's integrity, and a jailbroken device weakens them. This is one reason to use read-only credentials that cannot move money even if extracted.
Read next
- Why FRGMNT has no backendThe design decision behind the app: no server, no database, no account. What it buys, what it costs, and how i…
- Read-only API keys for revenue toolsHow to connect analytics to Stripe, Lemon Squeezy, Gumroad, PayPal and App Store Connect using credentials tha…
- Revenue tracking without handing over your dataEvery hosted analytics tool stores a copy of your revenue. Here's what that actually means, when it matters, a…