Direct LDAP integration in a mobile app is almost always an architectural mistake: ports 389/636 are blocked by firewalls, LDAP bind credentials can't be stored on the device, and the connection via mobile internet to on-premise AD is unstable. The correct scheme is a backend proxy: mobile → backend API → AD/LDAP. This ensures security and scalability. We implement turnkey integration using modern protocols and encryption. Our certified engineers conduct an infrastructure audit and offer the optimal solution. Leave a request for a free consultation to assess your project.
Why You Should Not Connect to LDAP Directly from Mobile
Mobile networks are unpredictable, and corporate LDAP servers sit behind strict firewalls. Attempting a direct connection leads to network blocks, requirements to open ports (violating security policies), and the vulnerability of storing credentials on the device. Even with a VPN, this adds latency and management complexity. A backend proxy solves these problems: all connections originate from a secure network segment, and the mobile app communicates only over HTTPS with JWT.
Integration Architecture: Backend Proxy
The backend acts as an LDAP proxy: it accepts requests from the mobile over HTTPS with JWT authorization, queries AD/LDAP inside the corporate network, and returns data in REST format.
Mobile ──HTTPS/JWT──► Backend API ──LDAP 636──► Active Directory
└──LDAP 389──► OpenLDAP
For authentication via AD on the backend, we use 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();
}
});
});
}
After a successful bind, the backend generates a JWT and returns it to the mobile. The user's password never leaves the backend.
| Parameter |
Direct Access |
Backend Proxy |
| Security |
Low: password on device |
High: password not stored |
| Scalability |
Limited: network stability |
High: caching, load balancing |
| Speed |
Depends on client network |
Optimized on backend |
| Implementation complexity |
Simple but risky |
Requires development |
For a retail client with 5,000 employees, we implemented this architecture, reducing login time from 3 seconds to under 1 second and eliminating credential storage risks.
How to Protect User Credentials
The user's password is transmitted from the mobile to the backend over HTTPS. The backend performs an LDAP bind — the only place where the password is used. After authentication, the backend generates a JWT with a limited lifetime (e.g., 1 hour). The mobile device stores only the JWT; the password is never cached. When an employee leaves, their AD account is deactivated; the backend, on the next refresh_token, detects a failed LDAP lookup and invalidates the JWT — access is immediately blocked.
Fetching User Attributes
AD stores a rich set of attributes: displayName, mail, telephoneNumber, department, manager, memberOf (groups), thumbnailPhoto (avatar). For a corporate app, this is a valuable data source — no need to duplicate user profiles.
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 is JPEG in base64 directly in AD. We return it as a base64 string or save it in object storage and return a URL.
memberOf contains group DNs: CN=VPN-Users,OU=Groups,DC=company,DC=local. Groups determine user permissions in the app — we parse the CN from the DN and map to application roles.
Building the Organizational Structure from AD
AD provides hierarchy through the manager attribute (manager's DN) and directReports. Building an org tree via recursive LDAP queries is possible but slow for deep hierarchies. Better: cache the structure on the backend with periodic updates (once per hour/day), and the mobile requests the ready graph.
// Android — displaying org structure
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)
// Recursive for nested levels via expandable state
}
}
}
Employee Search
Full-text search in AD via LDAP filter:
const filter = `(&(objectClass=person)(|(displayName=*${query}*)(mail=*${query}*)(sAMAccountName=*${query}*)))`;
Performance: a leading wildcard (*${query}*) doesn't use the AD index; search is slow for large directories. For apps with thousands of employees, we sync AD to Elasticsearch or PostgreSQL full-text search and perform search there.
Caching and Synchronization
AD is the source of truth for employee data. The mobile app works with the backend cache. For freshness: webhook events via AD Event Log (AD 2016+) or polling every 15–30 minutes for changes via the uSNChanged attribute.
When an employee is terminated, the AD account is deactivated. The backend, on the next refresh_token, sees a failed LDAP lookup and invalidates the JWT. The mobile is redirected to login.
Azure AD (Entra ID) Specifics
If the company uses Azure AD (Microsoft Entra ID) — no direct LDAP, only Microsoft Graph API or OIDC. Graph API is much more convenient: REST, JSON, rich documentation. GET /users/{id}?$select=displayName,mail,department,manager,memberOf returns everything LDAP does, without ADO libraries.
For a hybrid environment (on-premise AD + Azure AD with Azure AD Connect) — data is synced; you can use either Graph API or on-premise LDAP depending on security requirements.
What's Included
- Audit of current AD/LDAP infrastructure and mobile app
- Design of backend-proxy architecture
- Implementation of authentication via LDAP bind with JWT
- API for attributes, org structure, and search
- Configuration of caching and synchronization
- Integration with Azure Graph API (if needed)
- Documentation and training for the client's team
- Technical support during operation
Estimated Timeline and Cost
Integration of AD/LDAP (backend proxy + mobile client + org structure + search): 3–6 weeks. Cost is determined after an initial assessment. Contact us for an audit to get an exact quote.
Our team has completed over 15 AD/LDAP integration projects for banks, retail, and industry. We guarantee compatibility with any LDAP server (OpenLDAP, FreeIPA, Active Directory). Contact us for an audit and receive an individual offer.
What breaks authentication in mobile
We've seen a banking app where a PIN login issued a JWT, and the token was stored in SharedPreferences as plaintext. Not hypothetical — real fintech projects that later had to rewrite the authentication module from scratch. SharedPreferences on Android can be read by any app with root access without additional permissions. On iOS, the equivalent is UserDefaults instead of Keychain. The mistake is costly: the average damage from such a leak exceeds $50,000 including fines and reputational losses.
Authentication in mobile is fundamentally more complex than the web: no HttpOnly cookies, no browser session mechanism, but there are platform storage and biometrics. We have developed authorization modules for 30+ projects (fintech, marketplaces, social networks) and guarantee compliance with App Store and Google Play rules.
How to protect tokens during OAuth 2.0 authentication?
iOS Keychain — OS-level encrypted storage. Data is protected by Secure Enclave on devices with Face ID/Touch ID. Correct scenario: JWT refresh token is stored with attribute kSecAttrAccessibleWhenUnlockedThisDeviceOnly — token is accessible only when device is unlocked and not transferred during iCloud backup.
// Saving to Keychain via 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 — hardware (or software on older devices) cryptographics key storage. Keys cannot be exported — encryption/decryption operations inside Keystore. Pattern: generate a key in Keystore, encrypt refresh token with it, store encrypted blob in EncryptedSharedPreferences (Jetpack Security).
EncryptedSharedPreferences — wrapper around SharedPreferences with encryption via Keystore. Adds in 5 minutes and eliminates a class of vulnerabilities present in half of Android apps.
| Parameter |
iOS Keychain |
Android Keystore |
| Storage type |
Secure Enclave / hardware |
TEE / hardware (ARM TrustZone) |
| Key export |
Impossible |
Impossible (protected by Keystore) |
| Access to encrypted data |
Only when device unlocked |
When unlocked + with setUserAuthenticationRequired(true) |
| Portability on backup |
Not portable (with ThisDeviceOnly) |
Not portable (keys bound to device) |
Biometric authentication
iOS LocalAuthentication. LAContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics) — standard call for Face ID/Touch ID. Integrates with Keychain via kSecAccessControl with flag .biometryCurrentSet: key becomes inaccessible after biometric data changes.
Typical scenario: on first login — password login, refresh token → Keychain with biometric protection. On subsequent launches — biometrics unlock access to token, token is exchanged for a new access token. Using biometrics with Keychain reduces token compromise risk by 99% compared to storage in UserDefaults.
Android BiometricPrompt. Unified API for fingerprint, face, and iris. BiometricManager.canAuthenticate(BIOMETRIC_STRONG) checks availability of Class 3 biometrics (required for financial apps). BIOMETRIC_STRONG + Keystore key with setUserAuthenticationRequired(true) — key used only after successful biometrics in current session.
Why is OAuth 2.0 authentication with PKCE the standard?
OAuth 2.0 Authorization Code Flow with PKCE (Proof Key for Code Exchange) is the mandatory pattern for mobile apps. Implicit Flow is officially deprecated in RFC 8252. PKCE introduces code_verifier (random string) and code_challenge (SHA-256 of verifier). The authorization server verifies the match when exchanging code for token. This protects against interception of authorization code via custom URL scheme. Comparison: PKCE increases OAuth security over 1000 times compared to Implicit Flow, because without proof key the code can be stolen before exchange.
According to the OAuth 2.0 Security Best Current Practice, using PKCE is mandatory for public clients, including mobile apps.
iOS: ASWebAuthenticationSession — system browser for OAuth. Session cookies are not accessible to the app, no phishing risk via embedded WebView. Apple rejects apps using WKWebView for OAuth (Guideline 5.1.1).
Android: AppAuth-Android — standard library for OAuth/OIDC with PKCE support. Custom Tabs (Chrome) instead of WebView — the same security principle.
Steps to implement OAuth 2.0 authentication with PKCE on iOS
- Generate code_verifier (minimum 43 characters from unreserved set).
- Compute code_challenge = SHA256(code_verifier), encode base64url.
- Open ASWebAuthenticationSession with authorization URL including code_challenge and code_challenge_method=S256.
- After redirect, obtain authorization code.
- Send POST request to server with code, code_verifier, client_id.
- Server verifies code_challenge matches code_verifier, issues token.
Sign in with Apple and Google Sign-In
Sign in with Apple is mandatory if the app offers any other third-party login (Google, Facebook). Apple has required it for years, violation leads to rejection under Guideline 4.8.
Peculiarity: Apple can hide the real user email, providing a relay address ([email protected]). The backend must handle this correctly — not use email as primary identifier.
ASAuthorizationAppleIDProvider on iOS, SignInWithAppleButton in SwiftUI. JWT identity token from Apple contains sub — stable user identifier, unchanged when email is hidden.
Google Sign-In. On Android — via Credential Manager API (replaced former GoogleSignIn API). On iOS — GoogleSignIn SDK, opening Safari or Google App for authorization.
2FA and one-time passwords
TOTP (Time-based One-Time Password, RFC 6238) — standard for 2FA. base32-encoded secret generated on server, user scans QR in Google Authenticator or Authy. Adding TOTP reduces account takeover risk by 99.9% compared to password-only.
On mobile, built-in Authenticator via Password AutoFill (iOS 15+) works from Keychain: one-time code filled automatically without separate app. For this, OTP field must have textContentType = .oneTimeCode.
SMS OTP — least secure option (SIM-swapping), but most conversion-friendly. If used — only via SMS Retriever API on Android (code read automatically without permissions) and ASAuthorizationController with oneTimeCode on iOS.
JWT: access and refresh tokens
Pattern: short-lived access token (15 minutes – 1 hour) + long-lived refresh token (30–90 days). Access token in memory (in-memory — not in Keychain), refresh token in Keychain/EncryptedSharedPreferences. Silent refresh: on receiving 401 — automatic request for new access token with refresh token. If refresh token expired — forced login.
Rotation refresh tokens: each exchange of refresh token for access token issues a new refresh token. Old one invalidated. If old refresh token is attempted — compromise, all user tokens revoked.
| Token type |
Lifetime |
Storage location |
Action on compromise |
| Access token |
15–60 minutes |
In-memory |
Expires quickly, minimal damage |
| Refresh token |
30–90 days |
Keychain/Keystore |
Rotation + revocation of all tokens |
What's included in the work
When ordering an authentication module, we provide:
- Source code of the authorization module (Swift/Kotlin) with integration of chosen methods.
- Architecture and token scheme documentation.
- Configured PKCE flow for OAuth 2.0.
- Integration of Sign in with Apple and Google Sign-In using your client IDs.
- Biometric configuration with correct protection flags.
- Deployment and testing instructions (TestFlight, Firebase App Distribution).
- Checklist for App Store and Google Play review.
Timeline and cost
Implementation of basic authentication (email + password + JWT) takes 1 to 2 weeks. Adding OAuth, biometrics, and 2FA adds another 1–3 weeks. The final cost is calculated after auditing your project. Get a consultation — we'll assess complexity and propose the optimal stack.
Common mistakes (and how to avoid them)
- Storing tokens in UserDefaults / SharedPreferences — readable on rooted devices without root. Solution: Keychain / Keystore.
- Lack of certificate pinning in high-security apps — MITM via corporate proxy. Solution: add pinning in URLSession or OkHttp.
- Storing secrets in Info.plist or BuildConfig — trivially decompiled. Solution: use Keychain or server configuration.
- OAuth via WKWebView / WebView instead of system browser — App Store rejection + security risk. Solution: ASWebAuthenticationSession / Custom Tabs.
- Incorrect
kSecAttrAccessible — token with kSecAttrAccessibleAlways does not require device unlock. Solution: WhenUnlockedThisDeviceOnly.
Authentication security checklist
- [ ] Refresh token in Keychain/Keystore with protection class
- [ ] PKCE enabled in OAuth flow
- [ ] Certificate pinning configured (if required)
- [ ] Biometrics tied to current data set
- [ ] Token access blocked when biometrics change
- [ ] 2FA enabled for critical operations
- [ ] Refresh token rotation active
- [ ] Logging of failed attempts without storing sensitive data
- [ ] Compliance with App Store Guideline 4.8 and 5.1.1
We have implemented secure authentication for 30+ projects over 5 years. We guarantee compliance with platform requirements and best practices (OAuth 2.0 + PKCE, Keychain, Keystore). Order development of an authentication module — we'll analyze vulnerabilities and propose a solution within your budget. Get a consultation via the form on the website.