Розробка авторизації по біометрії (Face ID) у iOS-приложенні
Face ID в iOS працює через Local Authentication framework — конкретно через LAContext та метод evaluatePolicy(_:localizedReason:reply:). Звучить просто, поки не почнеш розбиратися з обробкою помилок, fallback-сценаріями та поведінкою на пристроях без Face ID.
На ревю в App Store приложення з некоректною біометрією заворачують за гайдлайном 5.1.1 (Privacy) — якщо reason string не пояснює користувачу, навіщо потрібен доступ, або якщо .biometryNotAvailable призводить до краш-петлі замість graceful degradation.
Де частіше за все помиляються
Найчастіша проблема — виклик evaluatePolicy на main thread без перевірки canEvaluatePolicy. Приложення зависає на 0.5–1 секунду в момент ініціалізації LAContext, якщо пристрій щойно заблокувався. На iPhone 14 Pro це непомітно, на iPhone SE 2nd gen — помітно.
Друга проблема — неправильна обробка LAError. Помилка має п'ять стану, які потребують різного UX: .userCancel, .userFallback, .systemCancel, .biometryLockout, .biometryNotAvailable. Розробники часто сваливають все в один catch і показують generic "помилка авторизації". Користувач після трьох невдалих спроб Face ID отримує lockout — біометрія блокується до введення passcode. Приложення обов'язково це обробити і запропонувати fallback, інакше користувач просто застрявне.
Третя — зберігання токенів після успішної біометрії. Нерідко вижу, як access token кладуть в UserDefaults. Правильно — Keychain з атрибутом kSecAttrAccessControl, створеним через SecAccessControlCreateWithFlags з флагом .biometryCurrentSet або .userPresence. При зміні біометрії (додавання нового пальця, перереєстрація обличчя) .biometryCurrentSet інвалідує запис автоматично.
Як будуємо реалізацію
Працюємо на LAContext з політикою .deviceOwnerAuthenticationWithBiometrics для чистої біометрії або .deviceOwnerAuthentication якщо потрібен fallback на passcode пристрою.
Базовий flow:
- Перевіряємо
canEvaluatePolicy— отримуємо тип біометрії черезcontext.biometryType(.faceID,.touchID,.opticIDна Vision Pro). - Запускаємо
evaluatePolicyна фоновому потоці (GCD або async/await зTask.detached). - У
reply-блоці обробляємо всі варіантиLAError— кожен окремим case. - При успіху достаємо токен з Keychain через
SecItemCopyMatching.
Для Swift Concurrency-стека обертаємо LAContext в withCheckedThrowingContinuation. Важлива момент: LAContext не є Sendable, тому при роботі з async/await потрібно або тримати його на MainActor, або використовувати @unchecked Sendable з явною синхронізацією.
Keychain-запис з біометричною захистом:
let access = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
.biometryCurrentSet,
nil
)
Флаг kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly гарантує, що дані не попадуть в iCloud Backup і не відновляться на іншому пристрої.
Тестування
Симулятор підтримує Face ID — через меню Features > Face ID можна емулювати успіх та помилку. Але .biometryLockout на симуляторі не відтворюється — тестуємо тільки на фізичних пристроях. Для UI-тестів використовуємо протокол-обертку над LAContext, який підмінюємо mock-об'єктом у XCTest.
Інтеграція з архітектурою приложення
У VIPER та Clean Architecture біометричний модуль виносимо в окремий Interactor (BiometricAuthInteractor) з залежністю через протокол BiometricServiceProtocol. У SwiftUI + MVVM — як @MainActor-клас, що публікує @Published var authState: AuthState.
Підтримуємо сценарії: первинна реєстрація біометрії (користувач ще не включив Face ID у налаштуваннях приложення), переключення на PIN-код, повне вимкнення біометрії. Всі стани персистуємо в UserDefaults як булеів флаг isBiometricEnabled — не сам токен, тільки метадані про вибір користувача.
Етапи роботи
Аудит поточного auth-модуля (якщо є) → проектування сценаріїв (happy path + всі error cases) → розробка сервісного шару з unit-тестами → інтеграція з UI → QA на реальних пристроях (iPhone SE, iPhone 15 Pro, iPad з Face ID) → ревю перед сабмітом в App Store.
Терміни реалізації з нуля — від 3 до 7 робочих днів залежно від складності існуючої архітектури та кількості точок входу в приложення.







