iOS is one of the most secure consumer platforms in the world, but that security isn’t automatic. The hardware and system frameworks give you excellent building blocks - the Secure Enclave, the Keychain, App Transport Security, biometrics - and it’s the developer’s job to use them correctly. This checklist walks through the practices that matter most for protecting user data and passing App Store review, from where to keep secrets to how to handle privacy.
Store secrets in the Keychain, never UserDefaults
The single most common iOS security mistake is putting sensitive data in the wrong place. UserDefaults is a plist-backed store meant for preferences - it is not encrypted for confidentiality, and its contents can be read from a backup or a jailbroken device. Passwords, authentication tokens, refresh tokens and API secrets belong in the Keychain, which is encrypted and, on supported devices, protected by the Secure Enclave.
Just as important: never hardcode API keys or secrets in the binary. Anything compiled into your app can be extracted with basic tooling, so treat embedded strings as public. Keep secrets on a server you control and deliver them at runtime over authenticated HTTPS, caching anything sensitive in the Keychain rather than in files or defaults.
Secure everything on the network
All traffic should travel over HTTPS, and iOS helps enforce this through App Transport Security (ATS), which blocks insecure connections by default. Resist the temptation to add blanket ATS exceptions to silence a warning - each exception is a hole. For high-value APIs, consider certificate or public-key pinning, which rejects connections whose server certificate doesn’t match one you expect, defending against fraudulent or compromised certificates. Pinning is powerful but must be maintained, because a rotated certificate on the server can otherwise lock users out.
Biometrics and local authentication
For sensitive actions - unlocking the app, authorising a payment, revealing stored credentials - use Face ID or Touch ID through the LocalAuthentication framework. Biometric checks let the device verify the user without your app ever handling the biometric data itself, and you can bind Keychain items so they’re only released after a successful biometric check.
Face ID with LocalAuthentication
Here’s a minimal, correct pattern for gating a sensitive action behind biometric authentication. Notice that it checks whether biometrics are available, provides a clear reason string, and handles failure explicitly:
import LocalAuthentication
func authenticate() async -> Bool {
let context = LAContext()
var error: NSError?
// Confirm the device can actually do biometrics first
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
return false
}
do {
return try await context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Confirm it's you to view your account"
)
} catch {
// User cancelled or authentication failed - fail closed
return false
}
}
The important detail is that the function fails closed: any error or cancellation returns false, so the sensitive action never proceeds by accident.
Data protection and privacy
Enable Data Protection so files are encrypted at rest and tied to the device passcode - iOS offers this by default for many files, but choose the right protection class for sensitive data. Beyond storage, respect Apple’s privacy rules: minimise the data you collect, request only the permissions you genuinely need, follow App Tracking Transparency before tracking users across apps, and ship an accurate privacy manifest declaring the data you collect and the required-reason APIs you use. Finally, keep secure logging in mind - never print tokens, passwords or personal data to the console or crash logs, where they can leak.
Common mistakes to avoid
- Storing tokens in UserDefaults. Convenient, but the wrong place for anything sensitive - use the Keychain.
- Hardcoding API keys and secrets. They can be extracted from the binary; keep them server-side.
- Adding broad ATS exceptions. Each one weakens transport security; fix the endpoint instead.
- Logging sensitive data. Tokens and personal data in logs are a real-world leak vector.
- Ignoring jailbroken-device risk. On a compromised device your protections are weaker - treat such environments as higher-risk for sensitive apps.
Best practices checklist
- Keychain for all secrets. Passwords, tokens and keys never touch UserDefaults or plain files.
- HTTPS everywhere via ATS. No blanket exceptions; add pinning for sensitive APIs.
- Biometrics for sensitive actions. Gate them with LocalAuthentication and fail closed.
- Validate inputs and handle errors safely. Never trust remote data; degrade gracefully.
- Minimise data and follow privacy rules. ATT, a correct privacy manifest, and least-privilege permissions.
- Secure logging. Strip secrets and personal data from logs and crash reports.
Building an app that handles real user data? Our iOS app development service bakes these practices in from day one, with a security review before every App Store submission. For the language decisions underneath all this, our guide on Swift vs Objective-C is a useful companion read.



