Fetching latest headlinesโ€ฆ
Mobile App Security Best Practices in 2026
NORTH AMERICA
๐Ÿ‡บ๐Ÿ‡ธ United Statesโ€ขJune 29, 2026

Mobile App Security Best Practices in 2026

1 views0 likes0 comments
Originally published byDev.to
  • Most mobile breaches aren't sophisticated. They're a hardcoded API key, a forgotten debug flag, or a token in plaintext AsyncStorage.
  • The OWASP Mobile Top 10 (2024) is your checklist โ€” work it every release.
  • Tokens live in the OS keychain via expo-secure-store, never AsyncStorage. Period.
  • Certificate pinning for sensitive endpoints, pinned to the SPKI hash, with a backup pin and a rotation plan.
  • AI-generated code is untrusted input โ€” review auth, storage, and network code with the same rigor as a new contributor's PR.
  • CI is where security lives: semgrep + eslint-plugin-security + npm audit + MobSF on every release artifact.

Mobile attacks are up. Regulators are watching. AI is writing more of your code than ever โ€” and the patterns it reproduces aren't always the secure ones. Here's the practical checklist I run through for every React Native / Expo app, organized around the OWASP Mobile Top 10 (2024).

This is the working version of a longer guide โ€” focused on what to actually change in your codebase this week.

1. Credentials: nothing sensitive in the bundle, nothing sensitive in AsyncStorage

// โŒ Don't
await AsyncStorage.setItem('access_token', token);

// โœ… Do
import * as SecureStore from 'expo-secure-store';
await SecureStore.setItemAsync('access_token', token, {
  keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});

Anything in the bundle can be extracted with apktool in minutes. Anything in AsyncStorage is plaintext on disk. Tokens go in the OS keychain via expo-secure-store or react-native-keychain. Period.

Refresh tokens rotate on every use. Access tokens live 15 minutes. The backend is the trust boundary, not the client.

2. Supply chain: assume your dependencies are hostile

# In CI, on every PR
npm ci
npm audit --audit-level=high

A clean package.json doesn't mean a clean app. Post-install scripts run with your dev-machine privileges. Native modules run with full app privileges.

  • Lockfiles in source control. npm ci in CI, never npm install.
  • Add Socket or Snyk for behavioral analysis (what npm audit misses).
  • Audit native modules personally if they touch storage, networking, or credentials.

3. Auth: PKCE, short JWTs, server-side authorization

import * as AuthSession from 'expo-auth-session';

// PKCE is the default in expo-auth-session โ€” don't disable it.
const request = new AuthSession.AuthRequest({
  clientId,
  scopes: ['openid', 'profile'],
  usePKCE: true,
  redirectUri,
});

OAuth 2.0 with PKCE for third-party identity. JWTs with 15-minute access tokens and rotated refresh tokens. Every endpoint validates the caller server-side. Hiding UI is not authorization.

Add MFA via expo-local-authentication for anything touching payments, identity, or health data.

4. Input validation: deeplinks are user input

// Treat the URL params as hostile
const handleDeepLink = (url: string) => {
  const parsed = new URL(url);
  const action = parsed.searchParams.get('action');
  if (action && /^[a-z_]{1,32}$/.test(action) && KNOWN_ACTIONS.has(action)) {
    routeTo(action);
  }
};

Deeplinks, push payloads, clipboard, QR codes, WebView messages โ€” all untrusted. Validate type, length, format. Parameterized queries for local SQLite. originWhitelist on every WebView.

5. TLS 1.3 + certificate pinning

<!-- android/app/src/main/res/xml/network_security_config.xml -->
<network-security-config>
  <domain-config cleartextTrafficPermitted="false">
    <domain includeSubdomains="true">api.yourapp.com</domain>
    <pin-set>
      <pin digest="SHA-256">{base64-spki-hash}</pin>
      <pin digest="SHA-256">{backup-spki-hash}</pin>
    </pin-set>
  </domain-config>
</network-security-config>

Pin to the SPKI hash, not the leaf cert. Ship a backup pin. Have a rotation plan. Reject TLS 1.0/1.1 server-side.

In Expo, usesCleartextTraffic: false. Verify no allowsArbitraryLoads snuck into production.

6. Privacy controls aren't optional

Maintain a data inventory. Apply data minimization. Request permissions just-in-time with context. Build account-delete-and-export flows that actually delete and export. Audit analytics/ad SDKs quarterly โ€” they change practices on their schedule.

7. Binary hardening: Hermes, R8/ProGuard, App Attest

  • Ship Hermes bytecode โ€” much harder to reverse than plain JS.
  • R8 with shrinking and obfuscation on Android.
  • babel-plugin-transform-remove-console in release builds.
  • jail-monkey for rooted/jailbroken detection (signal, not block).
  • Apple's App Attest + Google's Play Integrity API to verify the app calling your backend is the one you shipped.

8. Configuration hygiene

  • __DEV__ guards on every debug code path.
  • android:exported="true" only when truly needed.
  • URL schemes and Universal Links audited as entry points.
  • CI check that fails the build if known test-account strings hit the binary.

9. Encrypted storage, intentional sensitivity tiers

Sensitivity Storage
Credentials, keys iOS Keychain / Android Keystore via expo-secure-store
Structured PII SQLCipher or encrypted Realm
Non-sensitive Regular filesystem or AsyncStorage

Disable backup for sensitive paths. Mask app-switcher screenshots on sensitive screens via expo-screen-capture.

10. Modern crypto, vetted libraries

import { randomBytes } from 'react-native-quick-crypto';
// AES-256-GCM. Never CBC without auth. Never ECB. Ever.

Argon2id for password hashing. HKDF for key derivation. SHA-1 and MD5 are dead. Start tracking your post-quantum migration โ€” NIST's ML-KEM and ML-DSA are finalized.

11. Treat AI-generated code as untrusted input

This is the one most security frameworks haven't caught up to. LLMs reproduce the most common pattern in their training data โ€” often the most common flawed pattern.

  • Maintain a .cursorrules or .github/copilot-instructions.md with your secure defaults.
  • Review AI-generated auth, storage, and network code with the same rigor as a new contributor's PR.
  • Run semgrep and eslint-plugin-security on AI output before merge.
  • Pen-test AI-generated payment and auth flows specifically.

12. CI is where security lives

Every PR:

  • semgrep, eslint-plugin-security, Android Lint
  • npm audit + Socket/Snyk
  • MobSF on release-build artifacts

Annually: pen test. Quarterly: SDK audit. Always: an incident response plan that includes key rotation, token revocation, and an OTA push.

Most mobile breaches aren't sophisticated. They're a hardcoded key, a forgotten debug flag, a plaintext token. The OWASP Top 10 is your checklist โ€” work it every release.

For the longer breakdown โ€” including the AI-safe-code generation patterns and the full OWASP-mapped CI workflow โ€” see the RapidNative blog.

What's the security gotcha you've shipped to production and then quietly patched? Drop it in the comments โ€” I'm collecting the failure modes that don't make it into the OWASP examples for a follow-up.

Comments (0)

Sign in to join the discussion

Be the first to comment!