Developing Two-Factor Authentication (2FA) in Mobile App
2FA adds a second factor after successful login/password entry. For financial apps, corporate tools, and any service with valuable data — this is not optional, but a basic security requirement. The question is not "do we need 2FA", but "which method suits the audience".
Choosing a 2FA method
TOTP (Time-based One-Time Password) — user scans a QR code in Google Authenticator, Microsoft Authenticator or any TOTP app. Every 30 seconds a new 6-digit code is generated. Standard RFC 6238. Does not require internet after setup, works without SMS, free. Best choice for technically savvy audience.
SMS OTP — code arrives via SMS on each login. Familiar to mass audience, but: can be intercepted (SIM swapping), depends on coverage, costs money per SMS. For B2C apps — often the only option users find intuitive.
Push notification with confirmation — on login attempt, user receives push to trusted device "Confirm login?" with Yes/No buttons. Implemented via FCM/APNs + backend. Good UX, but depends on push delivery reliability.
Email OTP — SMS alternative, similar implementation, higher delivery rate, lower sense of urgency.
TOTP implementation
On server, when enabling 2FA, we generate secret (20 bytes of random data in Base32). Form otpauth://totp/AppName:[email protected]?secret=BASE32SECRET&issuer=AppName — this is URI for QR code.
# Python — secret generation and code verification
import pyotp
secret = pyotp.random_base32() # Save in DB, tied to user
totp = pyotp.TOTP(secret)
# Verify entered code
is_valid = totp.verify(user_input_code, valid_window=1)
# valid_window=1 allows codes ±30 seconds from current — compensates for clock skew
Generate QR code on server as PNG and return to client via protected endpoint (authenticated user only). Show once at setup — showing QR again is unsafe.
On mobile client — 6-digit code input screen, similar to OTP field from SMS authorization. Automatic submit on 6th digit entry.
Backup codes
When setting up TOTP — always generate 8–10 one-time backup codes (recovery codes). If user loses phone with Authenticator — only they allow account access. Show once, offer to save. Store as bcrypt hashes.
This is critical part often postponed. Without backup codes, lost phone = permanent account loss. Support will be overwhelmed with requests.
UX: when to require 2FA
Three strategies:
On every login — maximum security, minimum UX. Justified for financial and corporate apps.
Only on new device — trusted devices are remembered via device_token, stored in Keychain/Keystore. On login from new device or data clear — require 2FA. Best balance for most apps.
On risky operations — step-up authentication: login without 2FA, but on money transfer/password change — additional check. More complex architecturally, correct for fintech.
Trusted device mechanism
// iOS — device token generation after successful 2FA
let deviceToken = UUID().uuidString
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "device_token",
kSecAttrService as String: "com.yourapp.auth",
kSecValueData as String: deviceToken.data(using: .utf8)!,
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
]
SecItemAdd(query as CFDictionary, nil)
On Android — EncryptedSharedPreferences with AndroidKeyStore key for encrypting device token.
Device token is sent on login. Server checks: if token exists and matches user — 2FA not required. Trusted device lifetime: 30–90 days, then — repeat 2FA.
Disabling and changing 2FA
Allow user to disable 2FA carefully. Minimum: confirmation with current password + current 2FA code. Otherwise attacker with session access can disable protection.
Changing TOTP device: generate new secret, show old as active for 5 more minutes in parallel (migration window), then invalidate.
Timeframe: 1.5 to 3 weeks. TOTP with backup codes and trusted devices — toward upper bound. SMS 2FA without device trust — about a week.







