Mobile Security
The client is in the attacker's hands: what that means for your binary, your traffic, your storage, and your API.
After this lesson you can explain
- Why anything shipped in the app binary is effectively published, and what that means for API keys and business logic.
- Which of the three attackers (device owner, third parties, remote attackers) each mobile defence actually stops.
- What certificate pinning and attestation buy, what they cost, and why authorization always lives server-side.
1. The threat model: three different attackers
Every mobile defence protects against a specific attacker, and muddling them is how teams end up defending the wrong thing. Keep three separate:
| Attacker | Target | Typical moves |
|---|---|---|
| (a) The device owner | Your business | Cracking premium features, extracting keys, scripting your API, cheating |
| (b) Third parties | Your users | Malware, stolen phones, hostile WiFi, phishing |
| (c) Remote attackers | Your backend | Abusing whatever the app reveals: keys, endpoints, weak authorization |
The recurring confusion: defences against (b), like certificate pinning, get deployed as if they stopped (a). They do not, because the device owner controls the device. The two rules below make that concrete.
2. Rule one: the binary is public
An APK is literally a zip file. Free tools recover something close to source in minutes:
- jadx / apktool decompile Android bytecode into readable Java/Kotlin-shaped code.
- One command dumps every string in the binary: keys, URLs, feature flags, snarky comments.
- iOS is more locked down but not meaningfully safer against a motivated analyst (Frida for runtime hooking, class-dump for structure). Assume symmetry.
Obfuscation (R8/ProGuard on Android) renames checkEntitlement() to a.b() and raises the effort floor. Enable it (it is free and also shrinks the binary), but understand what it buys: it delays reading, it does not prevent it.
Therefore: anything you ship in the app, you have published. API keys, "hidden" endpoints, algorithms, the admin-mode flag, the discount logic: all of it is public the moment the app is downloadable. Decisions that must stay secret or must be enforced belong on the server, full stop.
3. API keys in apps: what they really expose
Keys split into two kinds, and the split decides everything:
Identifying keys
A Google Maps key, a Firebase config, an analytics write key: they say which project is calling and are designed to be embedded, provided you use the platform's restrictions to bind them to your app's package name and signing certificate (and/or per-API quotas). Unrestricted identifying keys are the ones that end up mined into someone else's bill.
Secret keys
A Stripe secret key, a Claude or OpenAI API key, service-account credentials, a database password: these grant spending or data authority to whoever holds them. Shipping one in an app is publishing it (§2). The scraped-key pipeline is automated: bots trawl app binaries and public repos continuously, and the time from leak to abuse is hours, not weeks.
The correct architecture is the one this course has been building all along:
App ──(user auth: bearer token)──► YOUR backend ──(vendor secret key)──► Stripe / Claude / etc.
│
└── the only place secrets live;
per-user rate limits + logging here
The app authenticates users to your backend; your backend holds the vendor secrets and proxies the calls, which also gives you per-user rate limiting, usage attribution, and one place to rotate a leaked key.
4. Rule two: your API is public too
Anyone can put an intercepting proxy (mitmproxy, Charles, Burp) between their own phone and the internet, install the proxy's root certificate on the device, and read every request your app makes. HTTPS protects against third parties on the network; it does not protect against the device owner, who controls the trust store.
So the moment your app ships, your entire API surface is documented for anyone curious: every endpoint, parameter, header, and response shape. Consequences:
- There are no hidden endpoints. "The app never calls it with other IDs" is not a security property.
- Security-by-obscurity is void. Assume attackers replay, reorder, and mutate every call your app can make.
- Authorization is enforced server-side on every request. Does this token's user get to do this action on this resource? Checked every time, never inferred from "the app wouldn't send that."
Fourth appearance of the course's rule. Login: verify the ID token server-side. Sync: the server owns truth. Payments: verify the receipt server-side. Security: authorize every call server-side. The client is a view of what the user may do; it is never the enforcement of it.
5. Certificate pinning: what it buys and what it costs
What it is: the app refuses TLS unless the server presents your specific certificate or public key, rather than anything the device's trust store would accept.
| Pinning helps | Pinning does not help | |
|---|---|---|
| Against | Attacker (b): hostile networks, rogue or compromised CAs, casual traffic snooping of your users | Attacker (a): a determined device owner strips pinning with a Frida script in minutes |
What it costs: operational risk. Pin the leaf certificate carelessly, rotate it, and every installed copy of your app bricks its own connectivity until users update: a self-inflicted outage with a store-review delay on the fix. If you pin: pin the CA or intermediate rather than the leaf, ship backup pins, and keep a remotely-toggleable escape hatch. Banks and messengers pin; a content app reasonably may not. It is a trade-off, not a default.
6. On-device storage: a hierarchy, not a binary
| Tier | What | Safe for |
|---|---|---|
| Keychain / Keystore | Hardware-backed secure storage; keys can be non-exportable | Tokens, refresh tokens, encryption keys: the only correct home for credentials |
| App-private storage | Sandboxed files/DB, OS-encrypted at rest | Cached content, preferences (on a non-rooted device). Assume readable on a rooted/jailbroken one |
| External / shared storage | World-readable areas | Nothing sensitive, ever |
Then the leak channels people forget, the source of most real-world mobile data leaks, well ahead of broken cryptography:
- Logs: a token printed to logcat rides along in bug reports and crash uploads. Strip logging of secrets in release builds.
- Analytics events quietly carrying emails, tokens, or content into a third-party dashboard.
- Backups: device backups hoover up app data unless you exclude sensitive files.
- The app-switcher screenshot: the OS snapshots your UI on backgrounding; banking apps blank the screen first for exactly this reason.
- The clipboard: readable by other apps on older Android versions; don't leave secrets on it.
The cheapest storage defence is upstream: data never stored cannot leak. Cache aggressively for UX (the sync lesson), but keep genuinely sensitive material server-side, fetched on demand over the authenticated API; that is also exactly the notify-then-fetch reasoning from the push lesson.
7. Attestation: the modern arms race
Play Integrity (Android) and App Attest / DeviceCheck (iOS) let your backend ask the platform a question no app-side check can answer honestly: "is this request coming from my unmodified app, installed from the store, on a genuine device?" The app obtains a signed verdict from the OS; your server validates it with Google/Apple before honouring sensitive requests.
- Use it for: raising the cost of bots, credential-stuffing, fake-purchase fraud, and modified clients on your high-value endpoints (login, purchase, promo redemption).
- Don't mistake it for a wall: it is an arms race with active bypass communities, and it can false-positive on legitimate rooted or de-Googled devices; decide deliberately whether those users matter to you.
- Client-side root/jailbreak detection alone is the weak version of this: it runs on hardware the attacker controls, so treat it as a signal, never a gate for anything valuable.
8. The 80/20 checklist
| Do | Against | Cost |
|---|---|---|
| Server-side authorization on every endpoint | (a), (c) | Discipline, not code volume; the single highest-value item |
| No secret keys in the binary; proxy vendor calls through your backend | (a), (c) | One extra endpoint per vendor |
| Verify purchases/receipts server-side (payments lesson §2) | (a) | Already required for correctness |
| Tokens in Keychain/Keystore only | (b) | A library call |
| Nothing sensitive in logs, analytics, or backups | (b) | Release-build config + review habit |
| TLS everywhere; pin only with a rotation plan | (b) | Free / operational risk respectively |
| Obfuscation (R8/ProGuard) on | (a) | Free; raises the floor |
| Rate limits + anomaly monitoring on the API | (a), (c) | Server-side; catches what everything above misses |
| Attestation on high-value endpoints, as a cost-raiser | (a), (c) | Moderate; mind false positives |
9. Common gotchas
| Symptom | Cause |
|---|---|
| A shocking vendor bill from usage you never generated | Secret key shipped in the binary and scraped by bots |
| Users accessing each other's data by changing an ID | Endpoint trusted the app to only send the user's own IDs (§4) |
| Cracked builds with premium unlocked circulating | Entitlement checked client-side only (payments lesson, same row) |
| The app stops working for everyone after a certificate renewal | Leaf-certificate pinning without backup pins (§5) |
| Tokens showing up in crash reports | Debug logging left on in release builds |
| Sensitive screen visible in the phone's app switcher | No snapshot blanking on backgrounding |
| Fraud bots surviving every client-side check you add | Checks run on hardware the attacker controls; move the decision server-side, add attestation as cost |
| Legitimate users on rooted phones locked out | Attestation used as a hard gate where a risk signal was appropriate |
10. The arc so far
Ten lessons, one arc. The app is an API client (lesson 2). Its login is token verification (3). Its notifications are two API integrations joined at a token (4). Its offline story is a contract of deltas and idempotency (7). Its payments are receipts verified server-side (8). Its real-time layer is a delta feed over the same source of truth (9). And its security model reduces to the same sentence every one of those lessons kept arriving at:
The client reports; the server verifies and decides. The binary is public, the traffic is public, the device belongs to someone else. The server is the only ground you control, which means mobile security is mostly API security, and an API developer learning mobile is not entering foreign territory. They are meeting their own subject from the other side.
One lesson remains: the case studies, which test this whole mental model against real apps.
Check your understanding
- Which attacker does certificate pinning actually defend against, and which one can strip it in minutes?
- A Stripe secret key and a Google Maps key both sit in your codebase: which one may ship in the binary, and under what condition?
- Why is client-side root detection a signal rather than a gate, and what does attestation change about that?
Sources and further reading
Reviewed 4 August 2026.