Description
UserDefaults stores application preferences and small settings. Saving sensitive values such as authentication tokens, session IDs, API keys, or passwords in cleartext in UserDefaults or NSUbiquitousKeyValueStore can expose them through device backups, debug extraction, jailbroken devices, or iCloud key-value synchronization.
Potential impact
- Authentication-token or session theft
- Exposure of API keys and client secrets
- Spread of sensitive data through iCloud synchronization or backups
- Account access using stolen values
Remediation
- Avoid storing sensitive values on the device where possible.
- If storage is necessary, use Keychain Services or a vetted encrypted store with separate key management. Secure Enclave protects supported cryptographic keys; it is not a general store for secret values.
- Keep only non-sensitive preferences, such as theme, language, or onboarding status, in
UserDefaults. - Use
NSUbiquitousKeyValueStoreonly for non-sensitive settings that need iCloud synchronization.
Examples
Before
swift
func saveToken(token: String) {
UserDefaults.standard.set(token, forKey: "access_token")
}
After
swift
import Foundation
import Security
enum KeychainStorageError: Error {
case unexpectedStatus(OSStatus)
}
func saveToken(token: Data) throws {
let item: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app.auth",
kSecAttrAccount as String: "access_token",
kSecAttrAccessible as String:
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecValueData as String: token
]
let addStatus = SecItemAdd(item as CFDictionary, nil)
if addStatus == errSecSuccess { return }
guard addStatus == errSecDuplicateItem else {
throw KeychainStorageError.unexpectedStatus(addStatus)
}
let match: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app.auth",
kSecAttrAccount as String: "access_token"
]
let updates: [String: Any] = [
kSecAttrAccessible as String:
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecValueData as String: token
]
let updateStatus = SecItemUpdate(
match as CFDictionary, updates as CFDictionary)
guard updateStatus == errSecSuccess else {
throw KeychainStorageError.unexpectedStatus(updateStatus)
}
}
Explanation:
- Before: A cleartext authentication token in
UserDefaultsmay be exposed through application sandbox extraction, backups, or debugging environments. - After: Keychain Services stores the value with device-only, passcode-required accessibility. The code checks insertion results and explicitly updates both value and accessibility for an existing item. Other failures are returned to the caller instead of falling back to weaker storage.