Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Self-contained reference for storing authentication tokens in Keychain and protecting them with biometric authentication (Face ID / Touch ID). Covers the patterns most commonly needed alongside Sign in with Apple and OAuth flows.
The Keychain is the ONLY correct place to store tokens, passwords, API keys, or secrets. Never store these in UserDefaults, files, or Core Data.
func saveToKeychain(account: String, data: Data, service: String) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecAttrService as String: service,
kSecValueData as String: data,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
]
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecDuplicateItem {
let updateQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecAttrService as String: service
]
let updates: [String: Any] = [kSecValueData as String: data]
let updateStatus = SecItemUpdate(updateQuery as CFDictionary, updates as CFDictionary)
guard updateStatus == errSecSuccess else {
throw KeychainError.updateFailed(updateStatus)
}
} else if status != errSecSuccess {
throw KeychainError.saveFailed(status)
}
}func readFromKeychain(account: String, service: String) throws -> Data {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecAttrService as String: service,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let data = result as? Data else {
throw KeychainError.readFailed(status)
}
return data
}func deleteFromKeychain(account: String, service: String) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: account,
kSecAttrService as String: service
]
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw KeychainError.deleteFailed(status)
}
}| Value | When Available | Device-Only | Use For |
|---|---|---|---|
kSecAttrAccessibleWhenUnlocked | Device unlocked | No | General credentials |
kSecAttrAccessibleWhenUnlockedThisDeviceOnly | Device unlocked | Yes | Sensitive credentials |
kSecAttrAccessibleAfterFirstUnlock | After first unlock | No | Background-accessible tokens |
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly | After first unlock | Yes | Background tokens, no backup |
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly | Passcode set + unlocked | Yes | Highest security |
Rules:
ThisDeviceOnly variants for sensitive data. Prevents backup/restore to other devices.AfterFirstUnlock for tokens needed by background operations.WhenPasscodeSetThisDeviceOnly for most sensitive data. Item is deleted if passcode is removed.kSecAttrAccessibleAlways (deprecated and insecure).Use LAContext from LocalAuthentication for Face ID / Touch ID prompts before
accessing sensitive data or performing protected actions.
import LocalAuthentication
func authenticateWithBiometrics() async throws -> Bool {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics, error: &error
) else {
// Biometrics not available -- fall back to passcode
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
return try await context.evaluatePolicy(
.deviceOwnerAuthentication,
localizedReason: "Authenticate to access your account"
)
}
throw AuthError.biometricsUnavailable
}
return try await context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Authenticate to access your account"
)
}You MUST include NSFaceIDUsageDescription in Info.plist:
<key>NSFaceIDUsageDescription</key>
<string>Authenticate to access your secure data</string>Missing this key causes a crash on Face ID devices.
let context = LAContext()
context.localizedFallbackTitle = "Use Passcode"
context.touchIDAuthenticationAllowableReuseDuration = 30
let currentState = context.evaluatedPolicyDomainState // Compare to detect enrollment changesProtect keychain items so they require biometric authentication to read:
let access = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
.biometryCurrentSet,
nil
)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "auth-token",
kSecValueData as String: tokenData,
kSecAttrAccessControl as String: access,
kSecUseAuthenticationContext as String: LAContext()
]| Flag | Behavior |
|---|---|
.biometryCurrentSet | Requires biometry, invalidated if enrollment changes. Most secure. |
.biometryAny | Requires biometry, survives enrollment changes. |
.userPresence | Biometry or passcode. Most flexible. |
Use .biometryCurrentSet for high-security items (tokens, keys). Use
.userPresence when you want to allow passcode fallback without a separate
LAContext evaluation.
enum KeychainError: Error {
case saveFailed(OSStatus)
case updateFailed(OSStatus)
case readFailed(OSStatus)
case deleteFailed(OSStatus)
var localizedDescription: String {
switch self {
case .saveFailed(let status),
.updateFailed(let status),
.readFailed(let status),
.deleteFailed(let status):
return SecCopyErrorMessageString(status, nil) as String? ?? "Unknown error"
}
}
}skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references