App Store review does not make an iOS app secure: it does not scan your code for hard-coded secrets, and it does not stop you storing tokens in the wrong Keychain class or switching App Transport Security off. iOS gives developers strong building blocks, including Data Protection, the Keychain, the Secure Enclave, App Transport Security (ATS) and App Attest, but each one depends on choices made in code, entitlements and Info.plist. This guide is the iOS-specific companion to the cross-platform Mobile App Security Guide. It gives the constants, keys and server-side verification steps an iOS engineer needs, plus privacy manifest duties, a rollout plan, metrics and failure patterns. It is written for iOS developers, mobile security engineers and the backend teams that verify what iOS apps send them.
Key takeaways
- Pick the Data Protection and Keychain class deliberately. Third-party app files default to Class C (protected until first user authentication) and Keychain items default to "when unlocked". Use the most restrictive class that still lets the feature work, and the
ThisDeviceOnlyvariants for tokens. - Secure Enclave keys cannot leave the device. They are NIST P-256 only, must be created inside the enclave and cannot be imported, which makes them the right anchor for device-bound tokens.
- ATS is on by default; every exception is a finding. ATS requires TLS 1.2 or later with forward secrecy, and
NSAllowsArbitraryLoadsand similar exceptions need justification at App Store review. - App Attest only helps if the server verifies it. The backend must check the certificate chain, nonce, App ID hash, environment and an increasing counter on every assertion.
- Never ship secrets in the IPA. Cybernews found hard-coded secrets in 71% of the iOS apps it analysed. Attest the app, then exchange the attestation for a short-lived token bound to a Secure Enclave key.
Data Protection classes for files
Every file on iOS is encrypted with a per-file key wrapped by a class key. The class decides when the file can be read. Apple's Platform Security guide describes four classes:
| Class | File protection constant | When the data is readable | Typical use |
|---|---|---|---|
| A: Complete Protection | NSFileProtectionComplete | Only while unlocked; the class key is discarded shortly after lock | Health records, documents, cached personal data |
| B: Protected Unless Open | NSFileProtectionCompleteUnlessOpen | Files can be written while locked; key wiped when the file closes | Downloads or recordings finishing in the background |
| C: Protected Until First User Authentication | NSFileProtectionCompleteUntilFirstUserAuthentication | After the first unlock following a restart, until the next restart | Default for third-party app data; data needed by background tasks |
| D: No Protection | NSFileProtectionNone | Always; protected only by the device UID | Avoid for anything sensitive |
Because Class C is the default, data written by an app stays readable while the phone is locked in a pocket after its first unlock. Set Class A explicitly for files that do not need background access, either through the Data Protection entitlement or per file when you write it. Also review where data leaks outside your files: caches, WebView storage, screenshots taken when the app moves to the background, and logs.
Keychain accessibility and access control
The Keychain is an encrypted SQLite database. Apple's guide describes per-row AES-256-GCM keys that need a round trip through the Secure Enclave, and access control lists evaluated inside the Secure Enclave. Each item has an accessibility class that mirrors the file classes.
| Accessibility value | Equivalent file class | Backup and migration | Use for |
|---|---|---|---|
kSecAttrAccessibleWhenUnlocked (default) | Class A | Migrates to a new device | Foreground-only credentials that may move with the user |
kSecAttrAccessibleWhenUnlockedThisDeviceOnly | Class A | Restorable only to the same device | Session and refresh tokens, device-bound keys |
kSecAttrAccessibleAfterFirstUnlock / ...ThisDeviceOnly | Class C | Plain variant migrates; ThisDeviceOnly does not | Tokens needed by background refresh |
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly | Class A, passcode required | Never synced to iCloud Keychain, never backed up; unusable if the passcode is removed | Highest-value secrets |
kSecAttrAccessibleAlways | Class D | Migrates | Not recommended by Apple |
- Use ThisDeviceOnly for tokens: Apple's documentation states that
ThisDeviceOnlyitems are restored only to the device that made the backup, so a stolen backup or a new phone does not inherit the session. - Add user presence where it matters: access control can require Face ID, Touch ID, Optic ID or the passcode, and can invalidate the item when biometric enrolment changes. Use biometrics to release a key or token, not as a yes or no answer that the app then trusts.
- Scope sharing with access groups: Keychain items can only be shared between apps from the same developer, enforced through the
keychain-access-groups, application identifier and app group entitlements. Do not put tokens in a shared group unless every app in it needs them.
Secure Enclave keys
The Secure Enclave is a hardware key manager isolated from the main processor, present on iPhones from the iPhone 5s (A7) onwards. Apple's documentation is precise about its limits: it supports only 256-bit NIST P-256 elliptic curve keys, for signing and for ECDH key agreement; keys must be generated inside it and cannot be imported; and the app never sees the private key, only signatures or decryption results.
let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage, .biometryAny], nil)!
let attributes: NSDictionary = [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits: 256,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
kSecPrivateKeyAttrs: [kSecAttrIsPermanent: true,
kSecAttrApplicationTag: tag,
kSecAttrAccessControl: access]]
// SecKeyCreateRandomKey(attributes, &error), then SecKeyCopyPublicKey to register it
- Include .privateKeyUsage: Apple notes that without it key generation succeeds but signing fails.
- Use the key for proof of possession: register the public key with your backend at sign-in and sign each sensitive request or token refresh. CryptoKit's
SecureEnclavetypes offer the same capability with a simpler API. - Plan for loss: because keys never leave the device, a new phone or a restore means re-enrolment. Make that path as strong as first sign-in.
App Transport Security and network trust
ATS is on for apps built with the iOS 9 SDK or later and applies to the URL loading system (URLSession). Beyond normal certificate validation, Apple lists these extended requirements: TLS 1.2 or later, AES-128 or AES-256, forward secrecy through ECDHE key exchange, RSA keys of at least 2048 bits or ECC keys of at least 256 bits, and SHA-256 or stronger certificate digests.
Info.plist key under NSAppTransportSecurity | Effect | Review position |
|---|---|---|
NSAllowsArbitraryLoads | Disables ATS for all connections not listed in NSExceptionDomains | Needs App Store justification; block in release |
NSAllowsArbitraryLoadsInWebContent | Disables ATS for web views only | Needs justification; only for browsers or apps showing arbitrary web content |
NSAllowsArbitraryLoadsForMedia | Disables ATS for AV Foundation media | Needs justification; encrypted, non-personal media only |
NSAllowsLocalNetworking | Allows loads from local resources | Acceptable for local device setup flows |
NSExceptionDomains with NSExceptionAllowsInsecureHTTPLoads or NSExceptionMinimumTLSVersion | Weakens rules for one named domain | Needs justification; scope to the exact host and set an end date |
NSPinnedDomains | Declares the certificates ATS expects for named domains | Strengthens trust; ship backup keys |
- Fix the server, not the app: Apple's guidance is to upgrade server security before adding exceptions and to use the narrowest exception possible. The
nscurltool shows which exceptions a given server needs. - Remember what ATS does not cover: lower-level networking and custom trust evaluation outside
URLSessionare your responsibility. Never return success from a server trust challenge without evaluating it. - Pin with care: pinning through
NSPinnedDomainsor code needs a backup key and a forced-update path, or a certificate change will break every installed copy.
App Attest and DeviceCheck
App Attest, part of the DeviceCheck framework, lets your server confirm that a request comes from a genuine instance of your app on genuine Apple hardware. DeviceCheck's older per-device bits serve a different purpose.
Client flow
- Check
DCAppAttestService.shared.isSupported. If it is false, degrade gracefully and tell the server not to expect assertions for that client. Only some app extension types are supported. - Call
generateKeyonce per user account per device and store the returned key identifier; it cannot be recovered later. - Fetch a one-time server challenge of at least 16 bytes, hash it with SHA-256 and call
attestKey(_:clientDataHash:). Send the attestation object to the server. Retry with the same key onserverUnavailable; for other errors discard the key and create a new one. - For sensitive requests, combine a fresh challenge with the request data, hash it and call
generateAssertion. Send the assertion with the request.
Server verification
| Check | Attestation | Assertion |
|---|---|---|
| Certificate chain | x5c chain validates to Apple's App Attest root | Signature verifies with the stored public key |
| Nonce | SHA-256 of authData plus clientDataHash equals the value in extension OID 1.2.840.113635.100.8.2 | Signature is over SHA-256 of authenticatorData plus clientDataHash; embedded challenge matches |
| App identity | RP ID equals SHA-256 of TeamID.BundleID | Same |
| Counter | Equals 0 | Strictly greater than the last stored value |
| Environment | aaguid is appattestdevelop or appattest (production) | Key belongs to the expected environment |
| Binding | Key ID matches the hash of the public key; key not already linked to another user | Request belongs to the user the key was registered to |
- Know the key lifetime: attested keys survive app updates but not reinstall, device migration or restore from backup. A user with many new keys in a short time is a fraud signal.
- Use the receipt: the attestation receipt lets your server ask Apple for a fraud risk metric showing how many attested keys a device has, which helps spot one compromised device serving many app instances.
- Roll out gradually: Apple advises apps with large user bases to onboard users gradually rather than calling
attestKeyfor everyone at once. - Use DeviceCheck bits for coarse state:
DCDevicegenerates a token the server sends to Apple'squery_two_bits,update_two_bitsandvalidate_device_tokenendpoints, authenticated with an ES256 JWT. Two bits per device suit flags such as "promotion already claimed".
Treat App Attest as one signal. It shows the app is genuine, not that the user is honest or that the device is free of malware. See the Device Intelligence and Fingerprinting Guide for combining signals.
Where identity fits: secrets, tokens and passkeys
An iOS app is a public client. The iOS apps hard-coded secrets research found more than 815,000 secrets across 156,080 App Store apps, including 19 Stripe secret keys, and 836 storage endpoints that needed no authentication. Symantec's earlier hard-coded AWS keys research found that about 98% of 1,859 affected apps were iOS apps, many with keys inherited from shared SDKs.
- Keep provider keys on the server: payment, cloud storage and AI keys belong in a vault behind your API. See the Secrets Management Guide.
- Attest, then exchange: the backend verifies App Attest and user authentication, then issues a short-lived token bound to a Secure Enclave public key. The app stores it with a
ThisDeviceOnlyclass and signs refreshes with the enclave key. - Restrict keys that must ship: analytics or mapping keys should be limited by bundle ID and API scope in the provider console and monitored. See the API Key Management Guide.
- Use passkeys for people:
ASAuthorizationPlatformPublicKeyCredentialProvidercreates and uses passkeys for a relying party ID, and requires an associated domain with thewebcredentialsservice. See the Passwordless and Passkeys Guide. - Protect distribution credentials: signing certificates and App Store Connect API keys used by CI can publish code to every user; keep them in a managed signing service with named owners. See the Code Signing Security Guide.
Privacy manifests and required-reason APIs
Apple requires a PrivacyInfo.xcprivacy property list in apps and in third-party SDKs that collect data or use certain APIs. Security teams should treat it as a declared data-flow inventory and check it against observed traffic.
- NSPrivacyTracking and NSPrivacyTrackingDomains: declare whether data is used for tracking and list the tracking domains. If the user has not granted tracking permission, requests to those domains fail.
- NSPrivacyCollectedDataTypes: the data types collected, which should match the App Store privacy label.
- NSPrivacyAccessedAPITypes: each required-reason API category used, with approved reason codes. Since 1 May 2024 App Store Connect does not accept apps that use these APIs without declaring reasons. Apple prohibits fingerprinting whether or not the user grants tracking permission.
- SDKs declare their own use: each SDK must report its own required-reason API use in its own manifest, and SDKs on Apple's list of commonly used SDKs need a manifest and a signature. Use Xcode's privacy report to review the combined result at release.
Choosing controls by app risk
| Control | Low-risk app | Regulated or high-value app |
|---|---|---|
| Files | Default Class C, nothing sensitive cached | Class A for personal data; background data reviewed case by case |
| Tokens | Keychain, WhenUnlockedThisDeviceOnly | Token bound to a Secure Enclave key, biometric gate for high-value actions |
| Network | ATS with no exceptions | ATS with no exceptions plus pinning with backup keys |
| Integrity | App Attest at sign-in, observe only | Assertions on sensitive requests, fraud risk metric, enforcement |
| Verification | OWASP MASVS baseline | MASVS including the resilience group, independent MASTG testing |
Small teams should focus on removing embedded secrets, choosing ThisDeviceOnly Keychain classes and keeping ATS exception-free. Larger organisations with several apps should run one shared attestation and token service so verification logic is written and reviewed once.
Implementation roadmap
- Inventory: list apps, SDKs, entitlements, ATS keys and Keychain classes in use; scan the built IPA for secrets.
- Remove and rotate secrets: move provider keys server-side, rotate everything that has shipped, and use remote configuration so future rotation does not break installed versions.
- Fix storage classes: migrate tokens to
ThisDeviceOnlyitems and set Class A on sensitive files in one release, with a read-old, write-new migration. - Remove ATS exceptions: upgrade the servers, then delete exceptions; fail the build if
NSAllowsArbitraryLoadsappears in a release Info.plist. - Deploy App Attest in observe mode: verify on the server and log results without blocking, using the development environment first and gradual onboarding in production.
- Enforce in stages: require assertions for the most sensitive actions first. Roll back by switching server policy to observe, which needs no app release.
- Gate releases: MASTG-based tests, a secrets scan and a privacy report review on every release, plus a minimum supported version.
Detection and measurement
- Log on the server: attestation and assertion failures by reason (chain, nonce, counter, environment), keys per user, and requests without a valid device-bound token.
- Watch counters and keys: a repeated or decreasing assertion counter means replay; bursts of new keys for one account suggest automation.
| Metric | Target |
|---|---|
| Hard-coded secrets found in release IPAs | Zero; any finding blocks release |
| ATS exceptions in release builds | Zero without a recorded justification and end date |
Tokens stored with a ThisDeviceOnly class | 100% |
| Sensitive API calls with a verified assertion | Above 95% of supported devices |
| SDKs with a reviewed privacy manifest | 100% |
Framework mapping
| Area | OWASP MASVS group | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | MITRE ATT&CK |
|---|---|---|---|---|
| Data Protection and Keychain | MASVS-STORAGE | SC-28 | PR.DS-01 | T1409 Stored Application Data; T1634 Credentials from Password Store (Mobile) |
| Secure Enclave keys | MASVS-CRYPTO | SC-12 | PR.DS-01 | n/a |
| App Transport Security | MASVS-NETWORK | SC-8 | PR.DS-02 | T1638 Adversary-in-the-Middle (Mobile) |
| Embedded secrets and token exchange | MASVS-AUTH | IA-5 | PR.AA-01 | T1552.001 Credentials In Files (Enterprise) |
| App Attest | MASVS-RESILIENCE | SI-7 | PR.PS-06 | n/a |
| Privacy manifests | MASVS-PRIVACY | SA-11 | PR.PS-06 | n/a |
The mapping is NHI Mgmt Group's own. For the risk view, see the OWASP Mobile Top 10 Guide.
Common failure patterns
- Secrets in the bundle: Stripe, cloud and Firebase credentials in Info.plist, config files or code, as the iOS apps hard-coded secrets research showed at scale.
- Default classes left alone: refresh tokens in
kSecAttrAccessibleAfterFirstUnlockorWhenUnlockedwithoutThisDeviceOnly, so they migrate with backups, or tokens in UserDefaults and plists. - Global ATS exceptions:
NSAllowsArbitraryLoadsadded for one legacy server and never removed. - App Attest without server checks: the app calls
attestKeybut the server never checks the counter, the RP ID or the environment, so development keys and replays pass. - Biometric yes or no: the app trusts a
LocalAuthenticationresult as the security decision instead of using it to release a Keychain item or enclave key. - Unreviewed SDKs: an SDK collects data or uses required-reason APIs the app's own declarations do not mention, which shows up in review or in traffic analysis.
Practitioner checklist
- Scan every release IPA for secrets and rotate any key that has ever shipped.
- Store tokens in the Keychain with a
ThisDeviceOnlyclass and set Class A on sensitive files. - Bind sessions to Secure Enclave P-256 keys and sign refreshes with them.
- Keep ATS exception-free in release builds and block
NSAllowsArbitraryLoadsin CI. - Verify App Attest on the server: chain, nonce, RP ID, environment and increasing counter.
- Roll out attestation in observe mode first, with server-side rollback.
- Adopt passkeys with
ASAuthorizationPlatformPublicKeyCredentialProviderand thewebcredentialsassociated domain. - Maintain
PrivacyInfo.xcprivacyfor the app and require manifests from every SDK. - Protect signing certificates and App Store Connect API keys as privileged non-human credentials.
Standards and references
- Apple Platform Security: Data Protection classes
- Apple Platform Security: Keychain data protection
- Apple Developer: Protecting keys with the Secure Enclave
- Apple Developer: Preventing insecure network connections
- Apple Developer: Establishing your app's integrity
- Apple Developer: Validating apps that connect to your server
- Apple Developer: Privacy manifest files
- OWASP: Mobile Application Security Verification Standard
Related NHI Mgmt Group resources: Mobile App Security Guide · Android App Security Guide · Mobile App Security Testing Guide · Mobile Device Security Guide · Secrets Management Guide