Mobile OAuth, Deep Links, and IPC Hardening: A 90‑Day Engineering Playbook
Step‑by‑step defaults, checklists, and tests that block token interception and cross‑app data leakage on Android and iOS
Mobile authentication flows often break in predictable ways: missing PKCE in public clients, permissive redirect allowlists that accept any subdomain or scheme, fragile state/nonce handling, unverified App/Universal Links, and mutable PendingIntents that can be hijacked by other apps. Each flaw independently exposes users to account takeover; together, they form a straight line from a crafted link to intercepted tokens and cross‑app data leakage. Attackers exploit these mistakes because they don’t need to root devices or defeat platform sandboxes—just trick the app into handing over credentials. The risk remains high across Android and iOS and is amplified by third‑party SDKs and inconsistent link verification.
This playbook delivers a practical, 90‑day path to close those gaps. It shows exactly how to harden OAuth for public clients, lock down deep links on iOS and Android, and set safer defaults for inter‑process communication. It also lays out token lifecycle controls, test harnesses and CI gates, manual QA scripts for adversarial scenarios, and operational runbooks and telemetry that let teams roll out defenses without breaking user journeys. Expect prescriptive checklists over theory, with security‑first defaults you can adopt immediately. 🔒
Architecture/Implementation Details
Threat paths to intercept tokens and exfiltrate data
- OAuth misconfigurations: Public clients without PKCE, weak or missing state/nonce, and permissive redirect URI allowlists allow code/token interception during the authorization code flow. If the app accepts a generic custom scheme or a wildcard redirect, a malicious app can claim the same handler and receive the code.
- Deep link hijacks: Unverified Android App Links and loosely scoped iOS Universal Links degrade to custom URL schemes or other ambiguous handlers. This enables cross‑app redirection to an attacker‑controlled endpoint and silent theft of authorization codes or session tokens.
- IPC abuse: Mutable PendingIntents, implicit broadcasts, and unsecured receivers give other apps the chance to alter intent contents or impersonate callers, resulting in privilege confusion and data leakage.
- Long‑lived tokens and weak revocation: Excessively long token TTLs, refresh tokens stored without device binding, and slow invalidation across devices extend the window for token replay after interception.
OAuth flow hardening for public clients
- Enforce PKCE everywhere with high‑entropy code verifiers and SHA‑256 code challenges. Treat any authorization response lacking PKCE as invalid.
- Maintain strict per‑environment redirect allowlists. Explicitly enumerate production, staging, and test URIs; avoid wildcards and generic custom schemes that any app can register.
- Require robust state and nonce: 128‑bit randomness at a minimum, tied to the user session and verified server‑side. Reject responses with missing or mismatched parameters; fail closed.
- Apply least‑privilege token scopes and short lifetimes. Narrow scopes limit blast radius; short TTLs shrink the replay window if a token leaks.
- Bind sessions to device/app attributes: Where feasible, integrate attestation so a stolen token is less valuable off‑device.
iOS Universal Links done right
- Use Universal Links as the primary deep link mechanism. Configure the Associated Domains entitlement with exact domains and paths that the app should handle.
- Serve a strong apple-app-site-association file with precise path scoping. Avoid catch‑all patterns unless strictly necessary; keep the surface small.
- Design safe fallback behavior. When Universal Link verification fails (e.g., first launch, network delay), resist falling back to a generic custom URL scheme that can be hijacked. Offer a trustworthy in‑app path or a secure web fallback instead.
- Validate before trust: Confirm Universal Link verification status at runtime and degrade gracefully if the domain is not verified. Document recovery steps in QA and support playbooks.
Android App Links with verification
- Prefer Android App Links over custom schemes. Publish a precise assetlinks.json file that binds your app to domains you own, scoping to the specific packages and certificate fingerprints.
- Check domain verification status during onboarding and before high‑risk flows. If verification isn’t established, guide the user through a secure browser‑based path rather than accepting ambiguous handlers.
- Scope handlers tightly: Restrict
<intent-filter>patterns to required hosts and paths; avoid wildcards that unintentionally match third‑party domains or broad URL sets. - Account for OEM variance: Verification timing and UX can differ across devices. Instrument telemetry to detect unverified states and provide in‑app guidance rather than silently falling back to vulnerable schemes.
PendingIntent and IPC defenses
- Make FLAG_IMMUTABLE the default for PendingIntents. Only use mutable when absolutely necessary for legitimate updates; otherwise, prevent external mutation.
- Prefer explicit intents targeting known components over implicit broadcasts. When implicit behaviors are required, apply strong receiver permissions to limit who can receive or trigger them.
- Verify the caller by UID/signature on exported components and bound services. Fail closed when the caller’s identity cannot be established.
- Minimize and validate payloads: Avoid placing sensitive data in intents; when unavoidable, validate every field server‑side before acting.
Token lifecycle and revocation
- Choose short token lifetimes for access tokens and rotate quickly after platform advisories. Treat refresh tokens as high‑sensitivity secrets.
- Apply refresh token protections: tie to device/app attestation, rate‑limit exchanges, and require step‑up auth for sensitive scope upgrades.
- Design rapid invalidation across devices: server‑side session management must support immediate logout and revocation, with clear user messaging and reliable on‑device state clearing.
Comparison Tables
Insecure defaults vs hardened defaults
| Area | Insecure default | Hardened default |
|---|---|---|
| OAuth (public clients) | No PKCE; accepts auth responses without checks | PKCE mandatory with SHA‑256; reject responses lacking valid code_challenge/code_verifier |
| Redirect URIs | Wildcards, generic custom schemes | Strict per‑environment allowlists with exact URIs |
| State/nonce | Omitted or weak randomness; not validated | High‑entropy state/nonce tied to session; server‑side validation; fail closed |
| Token scopes | Broad, permanent scopes | Least‑privilege scopes aligned to task; step‑up for sensitive actions |
| Token TTLs | Long‑lived access tokens | Short lifetimes; rapid rotation after risk signals |
| iOS deep links | Reliance on custom URL schemes | Universal Links with tight associated domains and entitlements |
| iOS fallback | Silent downgrade to scheme | Secure web fallback or in‑app path; no automatic scheme downgrade |
| Android deep links | Custom schemes; broad patterns | Verified App Links with assetlinks.json and tight intent‑filters |
| Domain verification | Not checked | Runtime status checks; guide user when unverified |
| PendingIntent | Mutable by default | FLAG_IMMUTABLE by default; mutable only when necessary |
| IPC intents | Implicit broadcasts | Explicit intents; receiver permissions; minimal payloads |
| Caller trust | Assumed | Verify UID/signature; reject unknown callers |
| Storage/logging | Tokens in preferences/logs | Hardware‑backed storage; no token/PII logging; analytics redaction |
| Revocation | Slow, per‑device | Server‑side invalidation and cross‑device logout |
Best Practices: The 90‑Day Engineering Playbook
Days 0–30: Lock down the obvious gaps
- Inventory and baselines
- Enumerate all OAuth redirect URIs per environment; list deep link handlers (iOS entitlements, Android intent‑filters); audit PendingIntent creation sites; catalog exported components and IPC endpoints.
- Identify token lifetimes, scopes, storage locations, and refresh workflows.
- Quick wins
- Enforce PKCE for every public client flow; reject non‑PKCE responses.
- Replace wildcard or generic redirect patterns with strict allowlists by environment.
- Default every PendingIntent to FLAG_IMMUTABLE; justify and document any mutable uses.
- Add receiver permissions to exported receivers; convert implicit broadcasts to explicit where feasible.
- Enable Android App Links and iOS Universal Links for the most sensitive flows; publish association files (assetlinks.json and apple‑app‑site‑association) with tight scoping.
- CI guardrails
- Introduce lint rules that fail builds on insecure defaults: mutable PendingIntents without justification, exported components without permissions, wildcard redirect URIs, and missing state/nonce hooks in OAuth flows.
Days 31–60: Build durable controls and tests
- Token lifecycle hardening
- Shorten access token TTLs; narrow scopes; require re‑authentication for sensitive transitions.
- Add refresh token protections: device/app attestation checks before issuing new tokens; server‑side rate‑limits; anomaly detection on refresh patterns.
- Deep link verification and UX
- Instrument domain verification checks. If verification isn’t established, send users through a secure browser‑based flow; do not auto‑fallback to schemes.
- Harden iOS fallback: ensure the app handles unverified states with a clear, safe path that resists hijacking.
- Automated security tests 🧪
- Create link‑hijack tests that simulate malicious apps claiming your schemes or domains; verify that flows do not hand off codes/tokens to untrusted handlers.
- Add PendingIntent mutation tests to assert immutability and detect any mutable instances that change intent extras before dispatch.
- Run negative OAuth vectors: replayed or mismatched state/nonce, missing PKCE parameters, unregistered redirect URIs, and unexpected scopes. The client and server should consistently fail closed.
- Manual adversarial QA scripts
- Provide step‑by‑step tests for crafted malicious links, unverified link states, and IPC caller spoofing. Include device matrices that reflect known edge conditions.
Days 61–90: Operationalize, measure, and roll out safely
- CI/CD gates and policy‑as‑code
- Make security tests gating for release trains. Treat violations as release blockers for flows that can expose tokens or PII.
- Apply policy‑as‑code scanners to catch drift in deep link association files and exported components.
- Telemetry and leading indicators
- Track Universal/App Link verification status at runtime; alert on unexpected downgrades or high rates of unverified states.
- Monitor state/nonce mismatches, rejected PKCE exchanges, and blocked redirect URIs as indicators of interception attempts.
- Log failed caller verification on IPC endpoints and denied mutable PendingIntent creation as signals of local adversaries.
- Rollout strategy
- Use staged feature flags to enable hardened paths for a small cohort first; expand as telemetry confirms stability.
- Measure breakage versus security gains: track success rates for auth, link opens, and IPC flows alongside blocked/hardened events.
- Operational runbooks
- Prepare live response steps for link/OAuth abuse: revoke tokens rapidly across devices, invalidate risky sessions, and rotate credentials safely.
- Align user communications to minimize churn when tokens are invalidated—clear prompts to re‑authenticate and explanations for why.
- Meet regulatory expectations when personal data confidentiality is at risk; ensure teams can notify within required timelines where applicable.
What “good” looks like at Day 90
- Every public client flow enforces PKCE, state/nonce, least‑privilege scopes, and short‑lived tokens.
- Universal Links and App Links are verified, narrowly scoped, and backed by runtime status checks with safe, non‑hijackable fallbacks.
- PendingIntent is immutable by default; IPC endpoints require explicit intents, receiver permissions, and caller verification by UID/signature.
- CI gates prevent insecure defaults from shipping; automated and manual adversarial tests run per release.
- Telemetry surfaces leading indicators of interception; operational runbooks support rapid revocation and clear user messaging.
Conclusion
Mobile account takeover through OAuth, deep link, and IPC weaknesses remains a high‑probability, high‑impact threat because it doesn’t require defeating the OS sandbox. The fixes are clear but demand discipline: PKCE everywhere, strict redirect allowlists, robust state/nonce, verified Universal/App Links with safe fallbacks, immutable PendingIntents, explicit intents with strong receiver permissions, and caller verification. Token lifecycle controls—short lifetimes, device‑bound refresh, and rapid invalidation—shrink the replay window when issues occur. Teams that back these controls with automated tests, CI gates, and staging/telemetry can ship protections quickly without breaking user journeys.
Key takeaways:
- Make PKCE, strict redirects, and state/nonce non‑negotiable for every public client.
- Treat Universal/App Links as first‑class and verified; avoid silent downgrades to schemes.
- Default PendingIntent to immutable and verify callers on IPC endpoints.
- Shorten token lifetimes and bind refresh to device/app attestation.
- Use tests, CI gates, and telemetry to catch regressions and measure security gains.
Next steps:
- Start with an inventory and implement quick wins in the first 30 days.
- Build automated link‑hijack and PendingIntent mutation tests and make them gating.
- Instrument verification status and rejected OAuth parameters as leading indicators of attack attempts.
- Prepare runbooks for token revocation and user communications to respond rapidly when abuse is detected.
Security isn’t a one‑time refactor; it’s a release‑over‑release muscle. With hardened defaults, rigorous tests, and measured rollouts, teams can reduce interception risk dramatically while keeping authentication flows fast and reliable.