When storing sensitive user data—such as passwords, biometric login tokens, or secure API keys—standard file storage or @AppStorage (UserDefaults) is highly insecure.
Values in UserDefaults are stored in plain text and can easily be extracted from an iPhone backup.
To protect sensitive information, Apple provides the Keychain Services API.
The Keychain is a specialized, highly encrypted database provided by iOS.
Data stored in the Keychain is encrypted using hardware-level keys.
Even if a hacker steals the physical device and extracts the file system, they cannot decrypt the Keychain without the user's passcode.
Interacting with the Keychain involves passing dictionaries of specific Sec (Security) constants.
You define the class of the item (e.g., a generic password), the account name, the actual secret data, and the accessibility requirements.
import Foundation import Securityfunc saveToKeychain(account: String, secret: String) { let secretData = secret.data(using: .utf8)! let query: [String: Any] = [ // We are storing a generic password kSecClass as String: kSecClassGenericPassword, // The identifier (e.g., username) kSecAttrAccount as String: account, // The actual secret data kSecValueData as String: secretData, // Security level: Only available when the device is unlocked kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked ] // Add the item to the Keychain let status = SecItemAdd(query as CFDictionary, nil) if status == errSecSuccess { print("Successfully saved to Keychain!") } else { print("Error saving to Keychain: \(status)") } }
The kSecAttrAccessible key dictates exactly when the secret can be read.
Using kSecAttrAccessibleWhenUnlocked ensures the data is strictly encrypted the moment the user locks their phone screen.
If your app does background tasks and needs the API key while the phone is locked in the user's pocket, you would use kSecAttrAccessibleAfterFirstUnlock instead.
To read the data back, you construct a search query matching the account name, and request that the Keychain returns the raw data.
Because the underlying C-based API is quite old and complex, most modern Swift developers use open-source wrapper libraries (like KeychainAccess or Valet) to make interacting with the Keychain as simple as writing to a standard dictionary!
Why should you avoid using UserDefaults (`@AppStorage`) for saving user passwords?