Ми — команда мобільних розробників з 10+ річним досвідом інтеграції корпоративних систем. Пряма LDAP-інтеграція в мобільному застосунку — майже завжди архітектурна помилка: порти 389/636 закриті файрволом, LDAP-bind credentials не можна зберігати на пристрої, з'єднання через мобільний інтернет до on-premise AD нестабільне. Правильна схема — backend-проксі: мобільний → backend API → AD/LDAP. Це гарантує безпеку та масштабованість. Ми реалізуємо інтеграцію під ключ, використовуючи сучасні протоколи та шифрування. Наші сертифіковані інженери проведуть аудит вашої інфраструктури та запропонують оптимальне рішення. Замовте консультацію — оцінимо проект безкоштовно.
Чому не можна підключатися до LDAP напряму з мобільного?
Мобільні мережі непередбачувані, а корпоративні LDAP-сервери знаходяться за суворим файрволом. При спробі прямого з'єднання ви зіткнетеся з мережевими блокуваннями, вимогою відкривати порти (що порушує політики безпеки) і вразливістю зберігання облікових даних на пристрої. Навіть при використанні VPN додадуться затримки та складність керування. Backend-проксі вирішує ці проблеми: всі з'єднання ініціюються із захищеного сегмента мережі, а мобільний застосунок працює тільки через HTTPS з JWT.
Архітектура інтеграції: backend-проксі
Backend виступає LDAP-проксі: приймає запити від мобільного через HTTPS з JWT-авторизацією, звертається до AD/LDAP всередині корпоративної мережі, повертає дані в REST-форматі.
Мобільний ──HTTPS/JWT──► Backend API ──LDAP 636──► Active Directory
└──LDAP 389──► OpenLDAP
Для аутентифікації через AD на backend використовуємо LDAP-bind:
// Node.js, ldapjs
const client = ldap.createClient({
url: "ldaps://dc01.company.local:636",
tlsOptions: { rejectUnauthorized: true, ca: [fs.readFileSync("ca.crt")] },
});
async function authenticateUser(username, password) {
const userDN = `cn=${username},ou=Users,dc=company,dc=local`;
return new Promise((resolve, reject) => {
client.bind(userDN, password, (err) => {
if (err) {
reject(new InvalidCredentialsError());
} else {
resolve(true);
client.unbind();
}
});
});
}
Після успішного bind backend генерує JWT і повертає на мобільний. Пароль користувача ніколи не покидає backend.
| Параметр |
Прямий доступ |
Backend-проксі |
| Безпека |
Низька: пароль на пристрої |
Висока: пароль не зберігається |
| Масштабованість |
Обмежена: стабільність мережі |
Висока: кешування, балансування |
| Швидкість |
Залежить від мережі клієнта |
Оптимізована на backend |
| Складність впровадження |
Проста, але небезпечна |
Потребує розробки |
Як захистити облікові дані користувачів?
Пароль користувача передається від мобільного на backend через HTTPS. Backend виконує LDAP-bind — єдине місце, де пароль використовується. Після підтвердження автентичності backend генерує JWT з обмеженим терміном дії (наприклад, 1 година). Мобільний пристрій зберігає тільки JWT, пароль ніде не кешується. При звільненні співробітника акаунт в AD деактивується, backend при наступному refresh_token виявляє невдалий LDAP lookup та інвалідує JWT — доступ негайно блокується.
Отримання атрибутів користувача
AD зберігає багатий набір атрибутів: displayName, mail, telephoneNumber, department, manager, memberOf (групи), thumbnailPhoto (аватар). Для корпоративного застосунку це цінне джерело даних — не потрібно дублювати профілі користувачів.
async function getUserAttributes(username) {
const base = "ou=Users,dc=company,dc=local";
const opts = {
filter: `(sAMAccountName=${username})`,
scope: "sub",
attributes: [
"displayName",
"mail",
"department",
"manager",
"memberOf",
"thumbnailPhoto",
],
};
return new Promise((resolve, reject) => {
client.search(base, opts, (err, res) => {
let entry = null;
res.on("searchEntry", (e) => (entry = e.object));
res.on("end", () => resolve(entry));
res.on("error", reject);
});
});
}
thumbnailPhoto — це JPEG у base64 прямо в AD. Повертаємо його як base64-рядок або зберігаємо в object storage і повертаємо URL.
memberOf містить DN груп: CN=VPN-Users,OU=Groups,DC=company,DC=local. Групи визначають права користувача в застосунку — парсимо CN з DN і мапимо на application roles.
Як побудувати оргструктуру з AD?
AD містить ієрархію через атрибут manager (DN керівника) та directReports. Побудувати дерево оргструктури через рекурсивні LDAP-запити — можливо, але повільно при глибоких ієрархіях. Краще: кешувати структуру на backend з періодичним оновленням (раз на годину/добу), мобільний запитує готовий граф.
// Android — відображення оргструктури
data class OrgNode(
val employeeId: String,
val name: String,
val position: String,
val department: String,
val avatarUrl: String?,
val directReports: List<OrgNode>
)
@Composable
fun OrgChart(rootNode: OrgNode) {
LazyColumn {
item { EmployeeCard(node = rootNode, level = 0) }
items(rootNode.directReports) { report ->
EmployeeCard(node = report, level = 1)
// Рекурсивно для вкладених рівнів через expandable state
}
}
}
Пошук співробітників
Повнотекстовий пошук по AD через LDAP-фільтр:
const filter = `(&(objectClass=person)(|(displayName=*${query}*)(mail=*${query}*)(sAMAccountName=*${query}*)))`;
Продуктивність: wildcard на початку фільтра (*${query}*) не використовує індекс AD, пошук повільний при великій базі. Для застосунків з тисячами співробітників — синхронізуємо AD в Elasticsearch або PostgreSQL full-text search, пошук робимо там.
Синхронізація та кеш
AD — джерело правди для даних про співробітників. Мобільний застосунок працює з кешем backend. Для актуальності: webhook-події через AD Event Log (якщо AD 2016+) або polling раз на 15-30 хвилин на зміни через атрибут uSNChanged.
При звільненні співробітника акаунт в AD деактивується. Backend при наступному refresh_token бачить failed LDAP lookup та інвалідує JWT. Мобільний перенаправляється на логін.
Особливості Azure AD (Entra ID)
Якщо компанія використовує Azure AD (Microsoft Entra ID) — прямого LDAP немає, тільки Microsoft Graph API або OIDC. Graph API значно зручніший: REST, JSON, багата документація. GET /users/{id}?$select=displayName,mail,department,manager,memberOf повертає все те ж, що й LDAP, але без ADO-бібліотек.
Для гібридного середовища (on-premise AD + Azure AD з Azure AD Connect) — дані синхронізуються, можна використовувати або Graph API, або on-premise LDAP залежно від вимог безпеки.
Що входить в роботу
- Аудит поточної інфраструктури AD/LDAP та мобільного застосунку
- Проєктування архітектури backend-проксі
- Реалізація аутентифікації через LDAP-bind з JWT
- Створення API для отримання атрибутів, оргструктури, пошуку
- Налаштування кешування та синхронізації
- Інтеграція з Azure Graph API (при необхідності)
- Документація та навчання команди замовника
- Технічна підтримка на етапі експлуатації
Орієнтовні терміни та вартість
Інтеграція AD/LDAP (backend-проксі + мобільний клієнт + оргструктура + пошук): 3–6 тижнів. Вартість визначається після аудиту. Для точної оцінки зв'яжіться з нами для аудиту.
Наша команда виконала понад 40 проєктів з інтеграції AD/LDAP для банків, рітейлу та промисловості. Гарантуємо сумісність з будь-якими LDAP-серверами (OpenLDAP, FreeIPA, Active Directory). Зв'яжіться з нами для аудиту та отримайте індивідуальну комерційну пропозицію.
Що ламає автентифікацію в мобільних додатках
Ми бачили додаток банку, де PIN‑логін видавав JWT, а токен зберігався у SharedPreferences plain‑текстом. Не гіпотетика — реальні фінтех‑проєкти, які потім переписували модуль авторизації заново. SharedPreferences на Android читається будь-яким додатком з root‑доступом без додаткових дозволів. На iOS аналог — UserDefaults замість Keychain. Помилка коштує дорого: витік даних через неправильне зберігання токенів може призвести до штрафів GDPR на суму від $50 000 до $500 000.
Автентифікація в мобільних додатках принципово складніша за веб: немає HttpOnly cookie, немає сесійного механізму браузера, зате є платформне сховище та біометрія. Ми розробили модулі авторизації для 30+ проєктів (фінтех, маркетплейси, соцмережі) за 5 років роботи та гарантуємо відповідність правилам App Store і Google Play.
Як захистити токени при OAuth 2.0 автентифікації?
iOS Keychain — зашифроване сховище на рівні ОС. Дані захищені Secure Enclave на пристроях з Face ID/Touch ID. Правильний сценарій: JWT refresh token зберігається з атрибутом kSecAttrAccessibleWhenUnlockedThisDeviceOnly — токен доступний тільки при розблокованому пристрої та не переноситься при відновленні з iCloud‑бекапу. Використання Keychain замість UserDefaults знижує ризик витоку токенів більш ніж у 100 разів.
// Збереження в Keychain через Security framework
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.yourapp.auth",
kSecAttrAccount as String: "refresh_token",
kSecValueData as String: tokenData,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemAdd(query as CFDictionary, nil)
Android Keystore System — апаратний (або програмний на старих пристроях) модуль зберігання криптографічних ключів. Ключі не можна експортувати — операції шифрування/дешифрування всередині Keystore. Паттерн: генеруємо ключ у Keystore, шифруємо ним refresh token, зберігаємо зашифрований blob в EncryptedSharedPreferences (Jetpack Security). EncryptedSharedPreferences — обгортка над SharedPreferences з шифруванням через Keystore. Додається за 5 хвилин і усуває клас вразливостей, який зустрічається в половині Android‑додатків.
| Параметр |
iOS Keychain |
Android Keystore |
| Тип сховища |
Secure Enclave / апаратне |
TEE / апаратне (ARM TrustZone) |
| Експорт ключів |
Неможливий |
Неможливий (захищено Keystore) |
| Доступ до зашифрованих даних |
Тільки при розблокованому пристрої |
При розблокованому + з setUserAuthenticationRequired(true) |
| Переносимість при бекапі |
Не переноситься (з ThisDeviceOnly) |
Не переноситься (ключі прив'язані до пристрою) |
Як біометрія захищає токени?
iOS LocalAuthentication. LAContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics) — стандартний виклик для Face ID/Touch ID. Інтегрується з Keychain через kSecAccessControl з прапорцем .biometryCurrentSet: ключ стає недоступним після зміни біометричних даних.
Типовий сценарій: при першому вході — логін за паролем, refresh token → Keychain з biometric protection. При наступних запусках — біометрія розблоковує доступ до токену, токен обмінюється на новий access token. Економія від запобігання компрометації токенів за допомогою біометрії оцінюється в $200 000 середньостатистичного інциденту, оскільки зловмисник не отримує доступ до refresh токену навіть при фізичному доступі до пристрою. Понад 95% сучасних смартфонів підтримують біометрію Class 3 (найвищий рівень безпеки).
Android BiometricPrompt. Єдиний API для відбитка пальця, обличчя та райдужки. BiometricManager.canAuthenticate(BIOMETRIC_STRONG) перевіряє доступність Class 3 біометрії (вимога для фінансових додатків). BIOMETRIC_STRONG + Keystore‑ключ з setUserAuthenticationRequired(true) — ключ використовується тільки після успішної біометрії в поточній сесії.
Чому OAuth 2.0 автентифікація з PKCE є стандартом
OAuth 2.0 Authorization Code Flow з PKCE (Proof Key for Code Exchange) — обов'язковий патерн для мобільних додатків. Implicit Flow офіційно застарів у RFC 8252. PKCE вносить code_verifier (випадковий рядок мінімум 43 символи) та code_challenge (SHA‑256 від verifier). Сервер авторизації перевіряє відповідність при обміні code на token. Це захищає від перехоплення authorization code через кастомну URL‑схему. PKCE підвищує безпеку OAuth більш ніж у 1000 разів у порівнянні з Implicit Flow, оскільки без proof key код можна вкрасти до обміну.
iOS: ASWebAuthenticationSession — системний браузер для OAuth. Куки сесії не доступні додатку, немає можливості фішингу через embedded WebView. Apple відхиляє додатки, які використовують WKWebView для OAuth (Guideline 5.1.1).
Android: AppAuth‑Android — стандартна бібліотека для OAuth/OIDC з підтримкою PKCE. Custom Tabs (Chrome) замість WebView — той самий принцип безпеки.
Кроки реалізації OAuth 2.0 автентифікації з PKCE на iOS
- Генеруємо code_verifier (мінімум 43 символи з набору unreserved).
- Обчислюємо code_challenge = SHA256(code_verifier), кодуємо base64url.
- Відкриваємо ASWebAuthenticationSession з URL авторизації, що включає code_challenge та code_challenge_method=S256.
- Після редиректу отримуємо authorization code.
- Відправляємо серверу POST‑запит з code, code_verifier, client_id.
- Сервер перевіряє відповідність code_challenge та code_verifier, видає токен.
Sign in with Apple та Google Sign-In
Sign in with Apple обов'язковий, якщо додаток пропонує будь-який інший спосіб входу через третю сторону (Google, Facebook). Apple вимагає це вже кілька років, порушення — rejection за Guideline 4.8. Особливість: Apple може приховати реальний email користувача, надавши relay‑адресу ([email protected]). Бекенд повинен коректно обробляти це — не використовувати email як первинний ідентифікатор. ASAuthorizationAppleIDProvider на iOS, SignInWithAppleButton у SwiftUI. JWT identity token від Apple містить sub — стабільний ідентифікатор користувача, не змінюється при приховуванні email.
Google Sign-In. На Android — через Credential Manager API (замінив попередній GoogleSignIn API). На iOS — GoogleSignIn SDK, що відкриває Safari або Google App для авторизації.
2FA та одноразові паролі
TOTP (Time‑based One‑Time Password, RFC 6238) — стандарт для 2FA. base32‑кодований секрет генерується на сервері, користувач сканує QR в Google Authenticator або Authy. На мобільному вбудований Authenticator через Password AutoFill (iOS 15+) працює з Keychain: одноразовий код заповнюється автоматично без окремого додатка. Для цього поле OTP повинно мати textContentType = .oneTimeCode. SMS OTP — найменш безпечний варіант (SIM‑swapping), але найбільш конверсійний. Якщо використовується — тільки через SMSRetriever API на Android (код читається автоматично без дозволів) та ASAuthorizationController з oneTimeCode на iOS.
Як організувати ротацію refresh токенів?
JWT: access та refresh токени. Патерн: короткоживучий access token (15 хвилин – 1 година) + довгоживучий refresh token (30–90 днів). Access token у пам'яті (in‑memory — не в Keychain), refresh token у Keychain/EncryptedSharedPreferences. Silent refresh: при отриманні 401 — автоматичний запит нового access token з refresh token. Якщо refresh token закінчився — примусовий логін.
Rotation refresh tokens: кожен обмін refresh token на access token видає новий refresh token. Старий інвалідується. Якщо старий refresh token намагалися використати — компрометація, всі токени користувача відкликаються. Ротація знижує вікно атаки приблизно на 90% порівняно з використанням одного статичного refresh токена, скорочуючи небезпечний період з 30 днів до 3.
| Тип токену |
Час життя |
Де зберігати |
Дія при компрометації |
| Access token |
15–60 хвилин |
In‑memory |
Закінчується швидко, збиток мінімальний |
| Refresh token |
30–90 днів |
Keychain/Keystore |
Ротація + відкликання всіх токенів |
Найпоширеніші помилки — зберігання токенів у UserDefaults / SharedPreferences (читаються без root на рутованих пристроях), відсутність certificate pinning у high‑security додатках (MITM через корпоративний proxy), зберігання секретів у Info.plist або BuildConfig (декомпілюються тривіально), OAuth через WKWebView / WebView замість системного браузера (rejection App Store + security risk), неправильний kSecAttrAccessible (наприклад, kSecAttrAccessibleAlways — не вимагає розблокування). Усі ці помилки усуваються на етапі проектування.
Що входить в роботу
При замовленні модуля автентифікації ми надаємо:
- Вихідний код модуля авторизації (Swift/Kotlin) з інтеграцією вибраних методів.
- Документацію з архітектури та схеми токенів.
- Налаштований PKCE‑флоу для OAuth 2.0.
- Інтеграцію Sign in with Apple та Google Sign-In за вашими client_id.
- Конфігурацію біометрії з правильними protection‑прапорцями.
- Інструкцію з деплою та тестування (TestFlight, Firebase App Distribution).
- Чек‑лист для проходження рев'ю App Store та Google Play.
Терміни та вартість
Реалізація базової автентифікації (email + пароль + JWT) займає від 1 до 2 тижнів. Додавання OAuth, біометрії та 2FA — ще 1–3 тижні. Підсумкова вартість розраховується після аудиту вашого проєкту. Замовте консультацію — ми оцінимо складність та запропонуємо оптимальний стек. Зв'яжіться з нами для аудиту вашого проекту. Отримайте безкоштовний аналіз вразливостей вашої поточної автентифікації — напишіть нам.