Back to Cheat Sheets

🔐 Insecure AuthenticationMobile 2016

OWASP Mobile Top 10 2016 - M4

CRITICAL RISK

📋 What Is It?

Insecure Authentication is the failure to establish who the user is in a way the client cannot fake. In mobile the temptation is structural: the app is right there, it has a login screen, and it feels natural to let it decide whether the user is authenticated. But the app runs on hardware the attacker owns - they can hook the check, edit local storage, or skip the app entirely and call the back end directly.

The category also covers the shortcuts mobile UX invites: 4-digit offline PINs, "remember me" tokens that never expire, device identifiers used as credentials, and biometric prompts whose result is a boolean rather than the unlock of a cryptographic key.

M4 OWASP Rank 2016
Server Must Decide
Bypass Via Direct API Calls

⚠️ Common Exploits

  • Client-Side Login: credentials compared inside the app
  • Local Flag Flip: isLoggedIn=true written to preferences
  • Boolean Biometrics: success callback hooked with Frida
  • Offline PIN Only: a 4-digit code checked on device, unlimited attempts
  • Device ID as Credential: IMEI/UUID treated as proof of identity
  • Immortal Sessions: tokens that never expire or cannot be revoked
  • Weak Password Policy: mobile-friendly rules that permit trivial secrets
  • Skip the App: the API is called directly, bypassing every screen

🔴 Attack Flow

1. Decompile the app and locate the authentication logic
↓
2. Look for checks that never leave the device
↓
3. Hook the method or edit local state to declare yourself logged in
↓
4. Or drop the app entirely and call the API with a crafted request
↓
5. IMPACT: Authenticated access without ever presenting a valid credential!

❌ Vulnerable Code

// Bad: the decision is made on the device fun login(user: String, pass: String): Boolean { val stored = prefs.getString("pass_$user", null) ← credential lives locally return stored == pass ← hook this and return true } // Bad: a local flag is the whole session prefs.edit().putBoolean("isLoggedIn", true).apply() // Bad: biometrics that gate UI, not data biometricPrompt.authenticate(info) // onAuthenticationSucceeded → showAccounts() // Bad: the device identifier is the credential api.get("/api/profile?device_id=" + deviceId) ← spoofable, guessable, permanent // Bad: PIN checked offline, unlimited attempts, no server involvement if (enteredPin == prefs.getString("pin", "")) unlock()

✅ Secure Code

// Good: the server authenticates; the app just carries the result val res = api.login(LoginRequest(user, pass)) // over TLS, rate-limited server-side secureStore.put("refresh_token", res.refreshToken) // Keystore/Keychain, encrypted // access token is short-lived; every request is re-authorised by the server // Good: biometrics unlock a key, so bypassing the UI yields nothing val spec = KeyGenParameterSpec.Builder("session_key", PURPOSE_DECRYPT) .setUserAuthenticationRequired(true) .setInvalidatedByBiometricEnrollment(true) .build() biometricPrompt.authenticate(info, BiometricPrompt.CryptoObject(cipher)) // the token only decrypts after a genuine biometric unlock // Good: a PIN is a local convenience that unwraps a server-issued token val key = deriveKey(pin, salt, iterations = 210_000) // PBKDF2/Argon2, device-bound val token = decrypt(storedToken, key) // wrong PIN → useless bytes enforceLocalAttemptLimit(max = 5, thenWipe = true) # Good: server side - throttle, MFA, revoke, and never trust device ids # identity comes only from a verified token; device_id is telemetry, not auth

✓ Prevention Checklist

  • Authenticate on the server; the app only presents credentials and carries tokens
  • Assume every client-side check can be bypassed - never make it the control
  • Store tokens in the Keychain/Keystore, never a plain flag or file
  • Bind biometric authentication to a cryptographic key (CryptoObject / LAContext)
  • Treat a local PIN as convenience: it should unwrap a server-issued token
  • Keep access tokens short-lived; rotate refresh tokens and support revocation
  • Never use device identifiers, phone numbers, or IMEI as credentials
  • Rate-limit and lock out on the server, including OTP verification
  • Offer MFA and require re-authentication for sensitive operations
  • Invalidate sessions server-side on logout, password change, and device loss
  • Test the API directly, without the app, to see what it accepts

🔍 Detection & Tools

Dynamic:

Frida objection Burp Suite

Static & Identity:

jadx / MobSF OAuth 2.0 + PKCE AppAuth Keystore / Keychain

How to Test:

  • Hook the login and biometric callbacks - does the app unlock anyway?
  • Flip local flags in preferences and relaunch
  • Call the API directly with no app and see what is accepted
  • Replay an old token after logout and after a password change
  • Brute-force the PIN/OTP path and check for server-side limits

🌍 Real-World

These are documented incident classes - recurring patterns rather than single CVEs.

  • Offline credential checks: apps that validate a password or PIN locally are unlocked with a one-line runtime hook.
  • Biometric UI gating: "Face ID protected" screens that reveal data as soon as the success callback is forced true, because no key was involved.
  • Device identifiers as credentials: back ends that trust a supplied device id return another user's data when the value is changed.
  • Never-expiring "remember me" tokens: a token from a resold or stolen phone still authenticates months later.
  • API-only bypass: flows enforced entirely in the UI are skipped by calling the endpoints directly.

📌 Quick Tips

  • DO NOT authenticate on the device
  • DO NOT gate access with a local boolean
  • DO NOT use a device id as identity
  • DO bind biometrics to a real key
  • DO keep tokens short-lived and revocable
  • DO test the API with the app removed

📜 Compliance

Related Standards:

  • OWASP Mobile Top 10 2016 - M4
  • Maps to M3:2024 Insecure Authentication/Authorization
  • OWASP MASVS AUTH-1, AUTH-2, AUTH-3
  • CWE-287 Improper Authentication
  • CWE-306 Missing Authentication for Critical Function
  • NIST SP 800-63B Digital Identity Guidelines