Розробка авторизації по біометрії (Touch ID) у iOS-приложенні
Touch ID старіше за Face ID, але досі актуальний: iPhone SE 3rd gen, iPad mini 6, iPad 10th gen із бічним сенсором — все це дійсний парк пристроїв у реальних користувачів. LAContext один і той же, але поведінка відрізняється в деталях, які бьють у продакшені.
Специфіка Touch ID проти Face ID
Головна відмінність — мультипальцева реєстрація. У користувача може бути до п'яти відбитків. При biometryType == .touchID та успішній аутентифікації ви не знаєте, який саме палець був використаний — API цього не дає. Для більшості сценаріїв це нормально, але в enterprise-приложеннях з журналом аудиту іноді хочуть логувати "яке устрій було використано". Touch ID це не дозволяє за дизайном.
Друга відмінність — швидкість деградації. Вологі пальці, перчатки, порізи — Touch ID частіше йде в fallback, ніж Face ID. Це означає, що .userFallback надо обробляти якісно, а не показувати кнопку "Введіть пароль" без пояснень.
Помилка, яку зустрічаю регулярно: розробник перевіряє biometryType один раз при запуску та кешує результат. Користувач добавляє новий відбиток у налаштуваннях — кеш застарів, Keychain-запис з .biometryCurrentSet інвалідирована, приложення падає з errSecItemNotFound (-25300) при спробі отримати токен. Правильно: створювати новий LAContext перед кожною спробою аутентифікації та не зберігати контекст довше однієї транзакції.
Реалізація
Політика та сама — .deviceOwnerAuthenticationWithBiometrics. Але для Touch ID особливо важливий параметр localizedFallbackTitle на LAContext. Якщо поставити пусту рядок "" — кнопка fallback сховається повністю. Якщо не задати — з'явиться "Enter Password" (системний текст). Ставимо кастомний: "Увійти по PIN-коду" або "Використовувати пароль приложення" — залежно від того, що у вас реалізовано.
Зберігання секрету в Keychain під Touch ID:
var error: Unmanaged<CFError>?
guard let access = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.biometryCurrentSet, .privateKeyUsage],
&error
) else { /* handle */ }
Флаг .privateKeyUsage добавляється, якщо секрет використовується для крипто-операцій (підпис запитів). Для простого зберігання токена достатньо .biometryCurrentSet.
Боковий Touch ID (iPad, iPhone SE)
На iPad mini 6 та iPad 10th gen сенсор вбудований в кнопку живлення. Анімація розблокування інша — користувач прикладає палець до бічної кнопки, а не до нижньої. Це впливає на те, як ви позиціонуєте підказки в UI. biometryType повертає .touchID в обох випадках — різниць в API немає, тільки в UX-копірайтингу.
Тестування та edge cases
Обов'язково тестуємо:
- Відбиток не розпізнаний три рази подряд → lockout → коректний переходу до fallback
- Touch ID вимкнений у налаштуваннях пристрою →
.biometryNotAvailable - Passcode не задан →
canEvaluatePolicyповертаєfalseз помилкою.passcodeNotSet - Приложення йде в background під час очікування Touch ID →
.systemCancel
На симуляторі емулюємо через Features > Touch ID > Matching Touch / Non-matching Touch.
Терміни
Реалізація Touch ID-аутентифікації з Keychain-сховищем, обробкою всіх error states та unit-тестами — 2–5 робочих днів. Якщо потрібна інтеграція з існуючим auth-модулем або міграція з UserDefaults на Keychain — добавляємо 1–2 дні на аудит та рефакторинг.







