Mobile
Lesson 7 of 8About 4 min readSuggest an edit

Mobile app security

Your app runs on devices you don’t control. Anyone can unpack it, read its strings, run it on a rooted or jailbroken phone, and change its traffic through a proxy. Mobile security starts from that assumption: the client is not trusted.

Enforce rules on the server

Anything the app checks, an attacker can skip; hiding a button does not protect the endpoint behind it. Authorisation, prices, limits and validation belong on the server.

Store sensitive data securely

Tokens and personal data must not sit in plain files or preferences, which end up in backups and are readable on a compromised device.

  • On iOS, use the Keychain. Choose an accessibility level such as kSecAttrAccessibleWhenUnlockedThisDeviceOnly when the item should not move to another device through a backup.
  • On Android, use the Android Keystore to hold encryption keys. Keys can be hardware-backed on supported devices and cannot be extracted from them. Encrypt the data itself with those keys, for example with Tink and DataStore. The older EncryptedSharedPreferences from Jetpack Security has been deprecated, so prefer this approach for new code.
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "refresh_token",
    kSecAttrAccessible as String:
        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
    kSecValueData as String: Data(token.utf8),
]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
    throw KeychainError(status: status)
}

Store as little as you can: keep tokens, never the user’s password.

Never ship secrets in the binary

Every string in your app can be extracted. A payment provider’s secret key, a database password or an admin API token in the binary is effectively published.

  • Put calls that need a secret behind your own backend.
  • Keys that must live in the app, such as a maps SDK key, should be treated as public and restricted on the provider’s side, for example to your app’s package name and signing certificate.

TLS, and when to pin

All traffic should use HTTPS. iOS enforces this by default through App Transport Security, and Android blocks cleartext traffic by default for apps targeting Android 9 and later. Don’t ship exceptions added for a development server.

Certificate pinning makes the app accept only specific certificates or public keys for your domain, which defends against a trusted but compromised or rogue certificate authority. It also carries real risk: if you rotate certificates without planning, every installed app loses its connection, and you cannot fix it without an update. If you pin, pin the public key rather than the certificate, include at least one backup key, and have a plan for rotation. For most apps, standard TLS validation is enough; pinning is worth it for high-value targets such as banking.

A deep link is input from outside your app, and anyone can craft one. Custom URL schemes such as myapp:// can be registered by any app. Prefer Universal Links on iOS and App Links on Android, which are verified against a file on your domain.

Treat every parameter as untrusted: validate ids, never load arbitrary URLs into a WebView, and never perform an action, like a payment or account change, without the user confirming it inside the app.

Logging hygiene

Logs leak into crash reports, analytics and support tickets, and on a device with debugging enabled anyone with a cable can read them. Never log tokens, passwords, full request bodies or personal data. Strip verbose logging from release builds, and scrub sensitive fields before events leave the device.

Obfuscation has limits

R8 on Android renames and shrinks code; Swift binaries are already compiled to machine code. Both make reverse engineering slower, not impossible. Server-side checks of device and app integrity, such as the Play Integrity API and Apple’s App Attest, raise the cost of abuse, but they are signals to weigh, not guarantees.

Checklist

  • Authorisation and validation live on the server.
  • Tokens in the Keychain or Keystore-backed encryption, never plain storage.
  • No secrets in the binary, the repository or the logs.
  • HTTPS everywhere; pin only with backup keys and a rotation plan.
  • Verified app links, validated parameters and confirmed actions.
  • Obfuscation treated as friction, not protection.

Next: Releasing mobile apps

Store review, versioning, staged rollouts, remote config, forced updates and crash monitoring without rollback.