Safeguard
AppSec

How to Provide Security in Android Apps: A Developer Checklist

How to provide security in Android apps: a developer checklist covering network config, storage, component exposure, WebViews, and the dependency layer most mobile teams skip.

Aisha Rahman
Security Analyst
6 min read

How to provide security in an Android app reduces to five decisions made early: encrypt data in transit with a locked-down network security config, keep secrets in the Android Keystore rather than in code or preferences, minimize what your manifest exports, treat every WebView as a browser you shipped, and scan your dependency tree like you would a backend service. Android supplies strong primitives for each; the common failures are apps that never turn them on, or that undo them for debugging and ship that way.

This checklist is ordered the way an attacker with your APK would probe it, because that is the review your app eventually gets — decompiling a release build takes minutes with free tools.

Assume the APK is public source code

Start from the correct threat model: anything in your APK is readable. apktool and jadx reconstruct resources and near-original Java/Kotlin from any release build. Consequences:

  • No secrets in the app. API keys, signing material, and hardcoded credentials in strings.xml, BuildConfig, or native libraries are simply published. Keys that must ship (say, a maps key) should be restricted server-side to your app's package and certificate; keys that grant real authority belong behind your backend.
  • R8/ProGuard is shrinkage, not protection. Obfuscation raises effort mildly; it is not a security control against a motivated reader.
  • Verify what release builds contain: it is common to find debug endpoints, test credentials, and verbose logging (Log.d with tokens in it) surviving into production APKs. Strip logging in release builds via lint rules or R8.

Lock down the network layer

Cleartext traffic has been blocked by default for apps targeting Android 9 (API 28) and above, but a manifest override can quietly re-enable it. Make your policy explicit with a network security configuration:

<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <base-config cleartextTrafficPermitted="false" />
    <domain-config>
        <domain includeSubdomains="true">api.example.com</domain>
        <pin-set expiration="2026-07-01">
            <pin digest="SHA-256">base64encodedpinvalue=</pin>
            <pin digest="SHA-256">backupPinValueGoesHere=</pin>
        </pin-set>
    </domain-config>
</network-security-config>

Certificate pinning is a judgment call: it defeats interception through a compromised or user-installed CA, but a botched rotation bricks connectivity for every installed copy until users update. If you pin, always include a backup pin for your next key and set an expiration. Whatever you choose, never ship a custom TrustManager that accepts all certificates — the "temporary" development bypass that reaches production is one of the most common findings in Android app security reviews.

Storage: Keystore first, then scoped files

App security on Android storage follows a hierarchy:

  1. Android Keystore for cryptographic keys: keys generated there are non-exportable and can require user authentication or hardware backing (StrongBox where available). Encrypt sensitive data with Keystore-held keys.
  2. Internal app storage for everything else private — it is sandboxed per-app by the OS.
  3. Never world-readable locations or external storage for anything sensitive, and never SharedPreferences for tokens in plaintext; wrap them with encryption keyed from the Keystore.

Auth tokens deserve special care: prefer short-lived access tokens with refresh handled by your backend, so a leaked token has a small window. And check your backup settings — android:allowBackup and the auto-backup rules decide whether your app's private files can be extracted through backup tooling; exclude token stores explicitly.

Shrink the attack surface in the manifest

Every exported component is an API other apps can call. Since Android 12, components with intent filters must declare android:exported explicitly, which forces the question — answer it honestly:

  • Default every activity, service, and receiver to exported="false" unless another app genuinely must invoke it.
  • Protect legitimately exported components with permissions, and validate every incoming intent's extras as untrusted input.
  • Content providers: no grantUriPermissions wildcards, parameterize all provider queries (SQL injection through a provider is a classic), and export none by default.
  • Deep links: verify App Links with autoVerify so other apps cannot claim your URLs, and treat link parameters exactly like web query strings.

WebViews are browsers you now maintain

A WebView with setJavaScriptEnabled(true) loading remote content is a browser embedded in your process. Rules that prevent most WebView incidents: load only your own allowlisted origins; never pipe untrusted URLs (from intents or notifications) into a privileged WebView; be conservative with addJavascriptInterface, since every exposed method is callable by any page that ever loads; and disable file access flags unless specifically needed. If you only need to display a third-party page, Custom Tabs delegates rendering to the user's real browser and inherits its sandbox — usually the better call.

The dependency layer mobile teams skip

Android apps average dozens of Gradle dependencies plus transitive trees, and vulnerable-library risk applies exactly as it does server-side — an outdated networking, parsing, or crypto library ships inside your APK to every user. Yet mobile builds are far less likely than backend services to have scanning wired in. Add it: dependency scanning on the Gradle lockfile in CI, plus continuous monitoring so an advisory published next month against a version you shipped today still reaches you. A software composition analysis gate on the mobile repo costs one pipeline step; an SCA platform such as Safeguard will also catch the transitive cases where the vulnerable artifact arrives three levels below the library you chose. Given app-store review latency, you want that alert the day the advisory lands, not at the next manual audit.

Verify with the OWASP MASVS

For a structured target rather than folklore, the OWASP Mobile Application Security project provides MASVS (the verification standard) and the Mobile Security Testing Guide with per-check test procedures. Running its resiliency and storage test cases against your own release APK — decompile, inspect, instrument — is a worthwhile quarterly exercise, and much cheaper than learning the same facts from someone's writeup. Broader secure-coding practice for the team lives well alongside it; our academy tracks cover the shared web/API layer that most Android apps talk to, where the server-side half of these controls is enforced.

FAQ

How to provide security in an Android app that talks to third-party APIs?

Route third-party calls through your backend where feasible: the app authenticates to you, your server holds the third-party credentials. Shipping third-party keys in the APK publishes them.

Is certificate pinning required?

No — it is a tradeoff, not a baseline. TLS with a strict network security config is the baseline. Pin when your threat model includes interception via compromised CAs and you have the release discipline to rotate pins safely.

Does R8 obfuscation protect my code?

It deters casual inspection only. Treat every string, key, and algorithm in the APK as readable by anyone, and design so that reading them grants nothing.

What is the most common real-world Android app security failure?

Insecure data storage and leaked secrets — tokens in plaintext preferences, keys in code — followed by permissive TrustManagers left over from development. All are findable in minutes from a released APK.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.