How to Debug Broken Authentication in Mobile Apps

Debugging broken authentication starts with reproducing the failure reliably, then isolating whether the problem lives in credential handling, token validation, session management, or backend communic

February 03, 2026 · 16 min read · Common Issues

How to Debug Broken Authentication in Mobile Apps

Debugging broken authentication starts with reproducing the failure reliably, then isolating whether the problem lives in credential handling, token validation, session management, or backend communication. The following guide walks you through a reproducible workflow, the signals to collect, the tools that expose those signals, and concrete fixes for the most common root causes.

---

How to Debug Broken Authentication in Mobile Apps: Understanding the Vulnerability Surface

What “broken authentication” means in practice

Authentication flaws appear when an app lets an attacker bypass login, reuse a stale token, or extract credentials through side‑channels. OWASP Mobile Top 10 lists them under M2: “Insecure Authentication”. Typical symptoms include:

Primary root‑cause categories

CategoryTypical code patternWhat goes wrong
Credential validationif (username.equals(input) && password.equals(input)) { … }Uses weak comparison, hard‑coded values, or trusts client‑side hash.
Token issuanceString token = JWT.create().withClaim("sub", userId).sign(algorithm);Uses weak algorithm (none/HMAC with exposed key) or omits expiration.
Session storageSharedPreferences.putString("auth_token", token);Stores token in mode MODE_WORLD_READABLE.
Token renewalif (token.isExpired()) refreshToken();Refresh endpoint does not bind token to device or user.
Transport securityHttpURLConnection.setSSLSocketFactory(factory);Accepts self‑signed certificates or disables hostname verification.
Multi‑factor bypassif (mfaRequired) { showMfaScreen(); } else { grantAccess(); }Logic error lets mfaRequired stay false after a failed challenge.

Understanding these buckets helps you focus logs, breakpoints, and network filters on the relevant code paths.

---

How to Debug Broken Authentication in Mobile Apps: Building a Reproducible Test Harness

Device or emulator preparation

  1. Reset stateadb shell pm clear com.example.app removes SharedPreferences, databases, and cache.
  2. Enable debugging – In Developer Options turn on “USB debugging” and “Verify apps over USB”.
  3. Install a trust store – If you plan to intercept TLS, install your proxy’s CA certificate on the device (adb push mitmproxy-ca-cert.cer /sdcard/ then Settings → Security → Install from storage).

Test matrix for authentication flows

Flow stepInputExpected outcomeNegative testPass/Fail criteria
Launch appShows login screenUI renders within 2 s
Enter valid credsuser1 / P@ssw0rd!Auth token received, home screen shownToken ≠ null, expiration > now
Enter invalid credsbad / badLogin error toastNo token, error message displayed
Empty fields` / `Validation errorNo network request, UI shows “required”
SQLi attempt' OR 1=1-- / anyRejected by backendBackend returns 401, no token
Token replayUse token from previous valid session after logoutAccess denied (401)Re‑use tokenRequest to protected endpoint returns 401
Token tamperingFlip a bit in JWT signatureSignature verification failsModify tokenBackend returns 401
MFA bypassSubmit login, then directly hit /home endpointBlocked, redirected to MFASkip MFA screenRequest returns 403 or redirect to MFA
Credential leakageLogin, then inspect logcat / file systemNo plain‑text password appearsgrep for passwordNo matches in logcat or /data/data/*/files

Execute each row, capture the device log (adb logcat -v threadtime > auth_debug.log) and network trace (see next section). A single step that deviates from the expected outcome flags a candidate defect.

---

How to Debug Broken Authentication in Mobile Apps: Logging and Tracing Strategies

Enabling verbose auth‑module logs

Most mobile SDKs expose a logger you can switch at runtime. For Android, add this to your Application.onCreate():


if (BuildConfig.DEBUG) {
    Logger.getLogger("com.example.auth").setLevel(Level.FINE);
    Logger.getLogger("org.apache.http.wire").setLevel(Level.FINE);
}

For iOS (Swift) you can set:


#if DEBUG
    AuthLogger.shared.logLevel = .verbose
#endif

These statements dump:

Adding correlation IDs

When you instrument the login flow, generate a UUID at the start of the flow and attach it to every log line:


String flowId = UUID.randomUUID().toString();
Logger.getLogger("com.example.auth").info("[{}] Starting login", flowId);
// later
Logger.getLogger("com.example.auth").info("[{}] Token received: {}", flowId, token);

Correlation lets you grep the massive logcat output for a single attempt:


adb logcat | grep \"[$flowId]\"

Stack‑trace capture on exceptions

Wrap authentication calls in a try/catch that logs the full stack:


try {
    val token = authService.login(username, password)
} catch (e: Exception) {
    Logger.getLogger("com.example.auth").error("Login failed", e)
    throw e   // rethrow so UI still shows error
}

If a NullPointerException occurs inside the token‑creation library, the stack will reveal whether the failure is due to a missing secret key or a malformed claim.

---

How to Debug Broken Authentication in Mobile Apps: Using Network Proxies and Packet Captures

Setting up mitmproxy (or Charles)

  1. Install mitmproxy on your workstation: pip install mitmproxy.
  2. Launch it on port 8080: mitmproxy --mode transparent --showhost.
  3. On the device, configure Wi‑Fi to point to the host’s IP and port 8080 (manual proxy).
  4. Install the mitmproxy CA certificate as described earlier.

Filtering auth‑related traffic

In mitmproxy’s console view press f to add a filter:


~u /oauth/token || ~u /api/v1/auth || ~h "Authorization: Bearer"

This shows only token requests, refresh calls, and any request that carries an Authorization header.

Inspecting request/response details

Select a request and press Enter to see:

If you see a 200 OK response to a request with "username":"" or "password":"" you have identified a credential‑validation bypass.

Detecting token leakage

Search the response bodies for the token string in places it shouldn’t appear:


~b "eyJ"   // matches JWT start

If the token appears in a Location header redirect or in a JSON field named debug_token, you have a leakage vector.

Capturing raw TLS with tcpdump (for low‑level analysis)

When you suspect certificate pinning failures, run on the device (requires root):


adb shell tcpdump -i any -s 0 -w /sdcard/auth.pcap port 443

Then pull the file: adb pull /sdcard/auth.pcap . and open in Wireshark. Apply the display filter http2 || ssl.handshake.type == 1 to see the ClientHello. If the server sends a certificate that is not pinned, you’ll see the server’s cert chain in the clear—useful for confirming pinning bypass.

---

How to Debug Broken Authentication in Mobile Apps: Profiling and Runtime Analysis

Using Android Studio Profiler for auth‑related CPU spikes

  1. Run the app from Android Studio with profiling enabled.
  2. Navigate to the CPU tab, start recording, perform a login attempt, stop.
  3. Look for methods in com.example.auth.* that consume > 30 % of the CPU during the token‑generation step.

Tracking object allocations with the Memory Profiler

During a login flow, watch for large allocations of byte[] or String that retain the password after the method returns. If you see a char[] lingering in the heap, the app is likely storing the password in plain text (perhaps for “remember me”).

Instrumenting with Frida for runtime inspection

Frida lets you inject JavaScript into a running process without recompiling. Example script to intercept the login method:


Java.perform(function () {
    var AuthManager = Java.use('com.example.auth.AuthManager');
    AuthManager.login.overload('java.lang.String', 'java.lang.String').implementation = function (user, pass) {
        console.log('[Auth] login called with user: ' + user + ', pass: ' + pass);
        var result = this.login(user, pass);
        console.log('[Auth] token returned: ' + result);
        return result;
    };
});

Run with:


frida -U -f com.example.app -l auth_trace.js --no-pause

The console output gives you the exact credentials passed in and the token returned, even if the app obfuscates them in release builds.

Detecting insecure storage with MobSF

Run the Mobile Security Framework (MobSF) static analysis on the APK:


docker run -it --rm -v $(pwd):/src opensecurity/mobsf:latest \
    python /opt/mobsf/run.py apkscan -f /src/app.apk

MobSF will flag:

---

How to Debug Broken Authentication in Mobile Apps: Manual Debugging Workflow

Step 1 – Isolate the failure mode

From the test matrix, pick the first failing step (e.g., “login succeeds with empty password”). Note the exact UI interaction and the expected network call.

Step 2 – Capture a clean log set


adb logcat -c   # clear buffer
adb logcat -v threadtime > clean.log

Perform the failing action, then stop logging (Ctrl+C).

Step 3 – Grep for the correlation ID

If you injected a UUID, find it:


grep "\[a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8\]" clean.log > flow.log

Step 4 – Examine the log flow

Read flow.log top‑to‑bottom. Look for:

Step 5 – Set breakpoints

In Android Studio, place a breakpoint at the start of the validation method (AuthValidator.checkCredentials). Debug the app, step over each line, and watch the variables:

Step 6 – Verify network payload

In mitmproxy, find the corresponding request. Confirm whether the payload actually contains the empty fields you sent. If the client side is sending correct data but the server still returns a token, the bug is server‑side; otherwise it’s client‑side logic.

Step 7 – Validate token contents

Decode the JWT (using jwt.io or the command line):


echo "<token>" | cut -d'.' -f1,2 | base64 -d | jq .

Check the exp, nbf, aud, and iss claims. If exp is far in the future or missing, the token issuance logic is flawed.

Step 8 – Re‑test with a fix

Apply a minimal code change (e.g., add if (TextUtils.isEmpty(password)) return false;), rebuild, reinstall (adb install -r app-debug.apk), and repeat steps 2‑5. The flow should now fail as expected.

---

How to Debug Broken Authentication in Mobile Apps: Automated Detection with Autonomous Exploration (SUSA)

How SUSA surfaces authentication gaps

When you point SUSA at an APK or a web URL, its exploration engine builds a state‑machine of screens, inputs, and dialogs. For each discovered login‑like screen it automatically:

  1. Generates a matrix of credential combinations (valid, invalid, empty, SQLi, long strings).
  2. Sends each combination to the backend while recording responses, logs, and network traces.
  3. Flags any attempt that results in a successful authenticated state (e.g., navigation to a home screen, receipt of a non‑expired token) despite invalid input.
  4. Checks token storage locations (SharedPreferences, Keychain, file system) for plain‑text secrets after each flow.
  5. Verifies that protected endpoints reject requests with stale or tampered tokens.

Because SUSA drives the app with varied personas (e.g., “impatient” who spam the login button, “adversarial” who inject special characters), it often hits edge cases that manual testers miss, such as a race condition where a rapid double‑tap bypasses a loading spinner and sends a second request before the first token is invalidated.

Running SUSA from the CLI


pip install susatest-agent
susatest run \
    --apk ./app-release.apk \
    --personas curious impatient adversarial \
    --output ./susatest-report.json \
    --log-level debug

The CLI exits with a non‑zero code if any authentication‑related finding is marked FAIL. The JSON report contains:

You can feed this report into your CI pipeline:


if ! susatest run --apk ./app.apk --fail-on-auth; then
    echo "Authentication regression detected"
    exit 1
fi

Limitations to keep in mind

---

How to Debug Broken Authentication in Mobile Apps: Fixing Common Root Causes

1. Weak or missing credential validation

Problem: The login method returns true if either username *or* password matches a hard‑coded value, or it trims input before comparison, allowing SQL‑style injection.

Fix:


public boolean validateCredentials(String user, String pass) {
    if (user == null || pass == null) return false;
    if (user.isEmpty() || pass.isEmpty()) return false;
    // Use prepared statements or parameterized queries on the backend
    return authRepository.checkUser(user, pass); // backend does real verification
}

Add unit tests that attempt empty strings, whitespace-only strings, and Unicode homoglyphs.

2. Token issued without expiration or with weak algorithm

Problem: JWT created with Algorithm.none() or HS256 using a key that is embedded in the APK.

Fix:


// Server‑side (Java/JJWT)
RSAPrivateKey privateKey = KeyUtil.loadPrivateKey("private.pem");
String token = Jwts.builder()
        .setSubject(userId)
        .setIssuedAt(now)
        .setExpiration(Date.from(now.plusMinutes(15)))
        .signWith(privateKey, SignatureAlgorithm.RS256)
        .compact();

3. Insecure token storage

Problem: SharedPreferences.putString("jwt", token); with default mode (MODE_PRIVATE is okay, but if the app mistakenly uses MODE_WORLD_READABLE or writes to external storage).

Fix:


MasterKey masterKey = new MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build();

EncryptedSharedPreferences encryptedPrefs = EncryptedSharedPreferences.create(
        context,
        "encrypted_auth_prefs",
        masterKey,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

encryptedPrefs.edit().putString("jwt", token).apply();

4. Missing token binding to device or session

Problem: Refresh endpoint accepts any valid refresh token, enabling token theft across devices.

Fix:


{
  "sub": "user123",
  "device_id": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8",
  "exp": 1735689600
}

5. Insufficient transport security

Problem: App uses OkHttpClient.Builder().certificatePinner(null) disabling pinning, or accepts all SSL certificates via X509TrustManager.

Fix:


CertificatePinner pinner = new CertificatePinner.Builder()
        .add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
        .build();

OkHttpClient client = new OkHttpClient.Builder()
        .certificatePinner(pinner)
        .build();

6. Multi‑factor authentication bypass via state confusion

Problem: After a failed MFA challenge, the app still sets isAuthenticated = true because the flag was set earlier in the flow.

Fix:

---

How to Debug Broken Authentication in Mobile Apps: Preventive Practices and Checklist

Checklist for each release

✅ ItemHow to verify
Credential fields reject empty/whitespace onlyUnit test with assertFalse(validator.validate("", "pwd"))
Password never logged or stored in plain textgrep -R "password" in source; run MobSF report; inspect logcat after login
Access token has exp ≤ 15 min, refresh token has longer life + device bindingDecode JWTs from mitmproxy; verify claims
Token stored in EncryptedSharedPreferences / KeychainCheck code for EncryptedSharedPreferences or Keychain usage
Certificate pinning active for all network domainsRun ./gradlew :app:assembleDebug then inspect generated network_security_config.xml or OkHttp client
MFA gate cannot be bypassed by direct navigation to protected UIAttempt to launch home screen via adb shell am start -n com.example.app/.HomeActivity without completing MFA
Refresh endpoint validates device‑id claimSend a refresh token from another device; expect 401
Error messages do not reveal whether username or password is wrongEnsure login failure toast is generic (“Invalid credentials”)
No hard‑coded cryptographic keys in APKRun grep -r "BEGIN PRIVATE KEY" on unpacked APK; also run MobSF secret‑scan
App clears auth state on logout / device rebootAfter logout, verify SharedPreferences no longer contains token; after reboot, confirm login screen appears

CI integration

Add a step that runs SUSA’s authentication persona matrix and fails the build on any AUTH_BYPASS or TOKEN_LEAK finding. Example GitHub Actions snippet:


- name: Install SUSA
  run: pip install susatest-agent
- name: Run Auth Checks
  run: |
    susatest run \
        --apk ./app/build/outputs/apk/release/app-release.apk \
        --personas curious impatient adversarial \
        --fail-on-auth \
        --output auth-report.json

Runtime defenses

---

How to Debug Broken Authentication in Mobile Apps: Real‑World Examples

Example 1 – Empty‑password bypass in a banking app

Symptom: Users could log in with any username and a blank password, gaining access to account balances.

Root cause: The validation method used if (password.isEmpty()) return true; to “avoid null‑pointer exceptions”.

Discovery:

Fix: Removed the early return, added if (TextUtils.isEmpty(password)) return false;. Regression test added to CI.

Example 2 – JWT with none algorithm accepted

Symptom: Attackers could forge admin tokens by signing with the none algorithm.

Root cause: Server-side JWT library allowed Algorithm.none() when no explicit algorithm was enforced.

Discovery:

Fix: Configured the JWT verifier to reject any token lacking a valid signature (require(HMAC256(secret)) or require(RSA256(publicKey))). Added unit test that attempts to forge a none‑algorithm token and expects 401.

Example 3 – Token leakage via Android backup

Symptom: Rooted users could extract the auth token via adb backup and reuse it on another device.

Root cause: The app stored the JWT in a SharedPreferences file named auth_prefs.xml with MODE_PRIVATE, but had android:allowBackup="true" in the manifest.

Discovery:

Fix: Set android:allowBackup="false" in the manifest and added a backup agent that clears auth data before backup. Also migrated to EncryptedSharedPreferences.

Example 4 – MFA bypass through rapid double‑tap

Symptom: Tapping the login button twice in quick succession allowed users to skip the MFA screen.

Root cause: The UI disabled the button after the first click, but a race condition let the second click invoke the login API before the first request completed, causing the server to issue a session token that the client treated as fully authenticated.

Discovery:

Fix: Disabled the button and set a flag loginInProgress = true that is cleared only after *both* credential verification and MFA verification complete. Also disabled the screen via window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, ...) while waiting for server response.

---

How to Debug Broken Authentication in Mobile Apps: Closing Takeaways

By treating authentication as a state‑machine with clear entry and exit criteria, instrumenting every transition with logs and correlation, and validating both the client and server sides of the contract, you turn a nebulous “login sometimes works” issue into a deterministic, repeatable debugging cycle that yields reliable fixes and prevents future regressions.

---

*End of article.*

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free