Aus dem Quell-Repository gerendert; Überschriften, Beispiele, Code, Tabellen, Links und Bilder bleiben erhalten.
MFA on Mobile
Instructions
MFA on mobile covers both the app as the authenticator (TOTP / push) and the app as the user (consuming a second factor during login).
1. Factor Types
| Factor | Strength | Notes |
|---|---|---|
| SMS OTP | Weak | SIM swap, SS7. Avoid unless regulation forces it. |
| Email OTP | Weak–medium | Inherits email account security. |
| TOTP (RFC 6238) | Medium | No phishing resistance on its own. |
| Push approval | Medium | Good UX; vulnerable to MFA fatigue. |
| FIDO2 / passkeys | Strong | Phishing-resistant. Prefer where available. |
2. TOTP as an Authenticator App
- Use the standard RFC 6238 with SHA-1 or SHA-256, 6 digits, 30-second period for maximum compatibility. Document any deviation.
- Store the shared secret only in Keystore / Keychain, wrapped by a biometric-gated key.
- Never back up TOTP secrets to iCloud / Google Drive in plaintext.
fun generateTotp(secret: ByteArray, time: Long = System.currentTimeMillis()): String {
val counter = ByteBuffer.allocate(8).putLong(time / 1000 / 30).array()
val mac = Mac.getInstance("HmacSHA1").apply { init(SecretKeySpec(secret, "HmacSHA1")) }
val hash = mac.doFinal(counter)
val offset = hash.last().toInt() and 0x0f
val bin = ((hash[offset].toInt() and 0x7f) shl 24) or
((hash[offset + 1].toInt() and 0xff) shl 16) or
((hash[offset + 2].toInt() and 0xff) shl 8) or
(hash[offset + 3].toInt() and 0xff)
return "%06d".format(bin % 1_000_000)
}3. Push-Based MFA (Done Right)
Push MFA is a signing operation, not a boolean tap:
- Server sends a push with a
challenge_id— not an "approve / deny" payload alone. - App fetches the challenge detail (amount, merchant, IP, location) over an authenticated channel.
- User reviews human-readable context and confirms.
- App signs
challenge_id || context_hashwith a biometric-gated Keystore key. - Server verifies the signature against the registered device public key.
Never auto-approve. Never accept a push whose contents you didn't fetch over HTTPS from the server.
4. Preventing MFA Fatigue
- Require number matching (user types a 2-digit code shown on the origin device).
- Throttle push requests server-side; after N rapid prompts, fall back to a stronger factor.
- Log and surface unusual prompts (new country, new device) in an in-app audit log.
5. Consuming MFA During Login
If your app is an OAuth client:
- Prefer
acr_values/amrclaims in theid_tokenso the server tells you what factors were used. - Support the browser flow's step-up — do not re-implement the challenge in the native app.
- On step-up failure, clearly distinguish "wrong code" from "account locked".
6. Recovery UX
Recovery is where MFA projects usually fail:
- Provide backup codes at enrollment (one-time, downloadable, printable). Store the hash server-side.
- Support FIDO2 security keys as a non-lose-your-phone recovery option when feasible.
- Document an account-recovery flow that is slower and more verified than normal login (identity check, cooling-off period).
- Never allow "email a reset link" to fully bypass MFA on high-value accounts.
7. Platform APIs
- iOS:
ASAuthorizationController+ASAuthorizationPlatformPublicKeyCredentialProviderfor passkeys. - Android:
CredentialManagerfor passkeys / saved passwords. - Flutter:
credential_managerpackage. - React Native:
react-native-passkey.
Passkeys are strictly better than TOTP for most apps; ship them where the user base has device support.
Checklist
- [ ] SMS is not the only second factor offered on high-value accounts.
- [ ] TOTP secrets live in Keystore / Keychain, biometric-gated.
- [ ] Push MFA signs a server-provided challenge (not a boolean approval).
- [ ] Number matching or equivalent anti-fatigue control is in place.
- [ ] Backup codes and/or passkey recovery are offered at enrollment.
- [ ] Account recovery is explicitly slower / more verified than normal login.
- [ ] Passkeys are supported where the platform allows.

