Authentication in Mobile Applications
A bank application: user enters PIN, server responds with JWT, token is saved in SharedPreferences as plain text. This isn't a hypothetical example — it's a real story from several fintech startups. SharedPreferences is readable by any application with root access on Android without additional permissions. On iOS the analog is saving a token in UserDefaults instead of Keychain.
Authentication in mobile is more complex than web: no HttpOnly cookie, no session mechanism, there are platform-specific storage and biometrics.
Token Storage: Keychain and Android Keystore
iOS Keychain — encrypted storage at the OS level. Data is protected by Secure Enclave on devices with Face ID/Touch ID. Correct scenario: JWT refresh token is stored in Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly attribute — token is accessible only when the device is unlocked and doesn't transfer during iCloud backup restore.
// 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) module for storing cryptographic keys. Keys can't be exported — encryption/decryption operations happen within Keystore. Pattern: generate key in Keystore, encrypt refresh token with it, store encrypted blob in EncryptedSharedPreferences (Jetpack Security).
EncryptedSharedPreferences — a wrapper over SharedPreferences with encryption via Keystore. Added in 5 minutes, eliminates a class of vulnerabilities found in half of Android applications.
Biometric Authentication
iOS LocalAuthentication. LAContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics) — standard call for Face ID / Touch ID. Integrates with Keychain via kSecAccessControl with biometryCurrentSet flag: key becomes inaccessible after biometric data changes.
Typical scenario: on first login — email/password login, refresh token → Keychain with biometric protection. On subsequent launches — biometry unlocks token access, token is exchanged for new access token.
Android BiometricPrompt. Unified API for fingerprint, face, and iris. BiometricManager.canAuthenticate(BIOMETRIC_STRONG) checks Class 3 biometry availability (requirement for financial applications). BIOMETRIC_STRONG + Keystore key with setUserAuthenticationRequired(true) — key is used only after successful biometry in current session.
OAuth 2.0 and PKCE
OAuth 2.0 Authorization Code Flow with PKCE (Proof Key for Code Exchange) — the standard for mobile applications. Implicit Flow is officially deprecated in RFC 8252.
PKCE adds code_verifier (random string) and code_challenge (SHA-256 of verifier). Authorization server verifies match when exchanging code for token. This protects against authorization code interception via custom URL scheme.
iOS: ASWebAuthenticationSession — system browser for OAuth. Session cookies aren't accessible to the application, no phishing via embedded WebView. Apple rejects applications that use WKWebView for OAuth (Guideline 5.1.1).
Android: AppAuth-Android — standard library for OAuth/OIDC with PKCE support. Custom Tabs (Chrome) instead of WebView — same security principle.
Sign in with Apple and Google Sign-In
Sign in with Apple is mandatory if the application offers any other third-party login method (Google, Facebook). Apple requires it since 2019, violation means rejection by Guideline 4.8.
Special feature: Apple can hide user's real email, providing relay address ([email protected]). Backend must handle this correctly — don't use email as primary identifier.
ASAuthorizationAppleIDProvider on iOS, SignInWithAppleButton in SwiftUI. JWT identity token from Apple contains sub — stable user identifier, doesn't change when email is hidden.
Google Sign-In. On Android — via Credential Manager API (replaced old GoogleSignIn API from Android 14). On iOS — GoogleSignIn SDK, which opens 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 is generated on server, user scans QR in Google Authenticator or Authy.
On mobile, built-in Authenticator via Password AutoFill (iOS 15+) works from Keychain: one-time code fills automatically without separate app. For this, OTP field must have textContentType = .oneTimeCode.
SMS OTP — least secure option (SIM-swapping), but most conversion for users. If used — only via SMSRetriever API on Android (code reads 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: when receiving 401 — automatic request for new access token with refresh token. If refresh token expired — forced login.
Rotating refresh tokens: each exchange of refresh token for access token issues a new refresh token. Old one is invalidated. If old refresh token is attempted — compromise, all user tokens are revoked.
Typical Mistakes
- Storing tokens in
UserDefaults/SharedPreferences— readable without root on rooted devices - Absence of certificate pinning in high-security applications — MITM via corporate proxy
- Storing secrets in
Info.plistorBuildConfig— trivially decompiled - OAuth via
WKWebView/WebViewinstead of system browser — App Store rejection + security risk - Wrong
kSecAttrAccessible— token in Keychain withkSecAttrAccessibleAlwaysdoesn't require device unlock
Timeline
Authentication implementation takes 1 to 4 weeks depending on the set of methods: email/password login, OAuth (Google, Apple), biometry, 2FA. Backend for auth is often separate microservice or Auth0 / Firebase Auth as BaaS. Cost is calculated after clarifying security requirements and login method set.







