Ticker

6/recent/ticker-posts

Mobile Security: Protecting User Data on Devices



Building a mobile app is one thing. Building one that users can trust is entirely different.

Every time your app handles a password, a payment method, health data, or location information, you're taking on a responsibility. Get it wrong, and you're not just dealing with angry users—you're facing app store rejections, legal exposure, and potentially devastating security breaches.

This guide walks you through mobile security the way it actually works in production apps, not just in tutorials. You'll learn what Apple and Google actually expect, which mistakes get apps rejected, and how to implement security that protects both your users and your business.

1. Official Mobile Security Processes & Standards

Before you write a single line of security code, you need to understand what the platforms actually require.

The Core Documentation You Must Know

Apple's Security Framework:

Google's Security Framework:

Cross-Platform Security Standards:

What These Standards Actually Mean

Let's break down the key requirements that affect every mobile app:

Secure Data Storage: Both platforms require that sensitive data is never stored in plaintext. This includes:

  • Authentication tokens and session identifiers
  • Passwords (even temporarily)
  • Credit card numbers or payment information
  • Personal health information
  • Any Personally Identifiable Information (PII)

Network Security:

  • All network communication containing user data must use TLS 1.2 or higher
  • iOS enforces App Transport Security (ATS) by default—older protocols are blocked unless explicitly overridden (which you shouldn't do)
  • Android 9+ blocks cleartext HTTP traffic by default

Permission Models:

  • Request only the permissions your app actually needs
  • Provide clear, contextual explanations before requesting permissions
  • Handle permission denials gracefully
  • On iOS 14+, App Tracking Transparency (ATT) requires user consent before tracking

Privacy Disclosures:

  • Both platforms require detailed privacy policies
  • iOS apps must include Privacy Nutrition Labels showing data collection practices
  • Android apps must complete the Data Safety section in Play Console
  • GDPR (Europe) and CCPA (California) add legal requirements around consent and data deletion

👉 What this means for your app's success: Apps that fail to meet these requirements don't just get rejected—they get suspended mid-lifecycle. We've seen apps pulled from stores after updates because developers didn't keep up with evolving privacy requirements. Build compliance into your security architecture from day one, not as an afterthought.

2. What Really Happens Behind the Scenes (What Tutorials Don't Tell You)

Security tutorials often focus on what to implement, but rarely explain why or what happens when you get it wrong. Here's the reality.

Why "HTTPS Everywhere" Isn't Enough

Yes, using HTTPS is non-negotiable. But here's what most developers miss:

The man-in-the-middle gap: Even with HTTPS, a sophisticated attacker can intercept traffic using tools like Charles Proxy or mitmproxy if your app doesn't validate certificates properly. On a compromised network (think coffee shop WiFi), users can be vulnerable without realizing it.

What happens in practice:

  • Attackers install root certificates on public WiFi networks
  • Your app connects via HTTPS and sees a "valid" certificate (it's the attacker's)
  • All traffic—including authentication tokens—flows through the attacker's proxy
  • User credentials are compromised

The solution: Implement certificate pinning for critical endpoints (authentication, payments). This ensures your app only trusts your server's specific certificate, not just any valid certificate.

How Rooted and Jailbroken Devices Expose Poor Security

When a device is rooted (Android) or jailbroken (iOS), all of your app's security assumptions break down:

  • The secure storage mechanisms (Keychain, Keystore) can be bypassed
  • Memory can be read in real-time
  • Network traffic can be intercepted regardless of pinning
  • Your app's code can be modified at runtime

What tutorials don't tell you: Approximately 5-10% of users have rooted or jailbroken devices. You can't ignore them, but you also can't assume your security works the same way.

What production apps actually do:

  • Detect root/jailbreak status at runtime
  • Warn users or restrict sensitive features on compromised devices
  • Implement additional server-side verification for high-risk transactions
  • Use runtime integrity checks (more on this in section 6)

The Illusion of Security in Local Storage

Many developers think that because their data is "on the device," it's safe. It's not.

SharedPreferences (Android) and UserDefaults (iOS): These are stored in plaintext XML/plist files. Anyone with access to the device file system—including malware, backups, or debugging tools—can read them.

SQLite databases without encryption: Standard SQLite databases store data in plaintext. A simple file copy exposes everything.

What we've seen go wrong:

  • Auth tokens stored in SharedPreferences, compromised through Android backup
  • Credit card details in unencrypted SQLite, extracted via malware
  • API keys in UserDefaults, exposed through device backups to cloud storage

⚠️ Real mistake we've seen—and how to avoid it: A fintech app stored transaction history in an unencrypted SQLite database "because it's just for display." When a user's phone was stolen, the thief accessed months of financial data, including partial card numbers. The fix: migrate to SQLCipher (encrypted SQLite) and store only references to server-side data, never the raw sensitive information.

How Logging, Analytics, and Crash Tools Leak Sensitive Data

Your security can be perfect, but your logging can undo it all.

Common logging mistakes:

java
// DON'T DO THIS
Log.d("Auth", "User token: " + authToken);
Log.d("Payment", "Processing card: " + cardNumber);

Where logs end up:

  • Device system logs (readable by other apps on rooted devices)
  • Crash reporting tools (Firebase Crashlytics, Sentry)
  • Analytics platforms (Mixpanel, Amplitude)
  • Your own server logs

The hidden risk: Crash reports often include variable states at the time of crash. If sensitive data is in memory, it gets logged. If your analytics tracks screen views with parameters, tokens in URLs get tracked.

What to do instead:

  • Never log sensitive data—no exceptions
  • Redact data in crash reports using your tool's filtering features
  • Review all analytics events to ensure no PII is captured
  • Implement structured logging with security levels

Why App Store Reviewers Flag Apps for Indirect Security Issues

Apple and Google reviewers look for patterns, not just obvious violations:

Patterns that trigger rejection:

  • Requesting unnecessary permissions (e.g., camera access when your app doesn't use it)
  • Vague privacy policy descriptions ("we may collect data to improve services")
  • Network requests to unknown domains during review
  • Hardcoded credentials visible in binary analysis
  • Collecting more data than your privacy manifest declares

What this means: Security isn't just about preventing hacks—it's about demonstrating trustworthiness to platform reviewers who are specifically trained to spot red flags.

3. Common Mobile Security Mistakes & Rejection Triggers

Let's get specific about what actually breaks in production and gets apps rejected.

Storing Tokens in Plaintext

The mistake:

kotlin
// Android - DON'T DO THIS
val prefs = context.getSharedPreferences("auth", Context.MODE_PRIVATE)
prefs.edit().putString("access_token", token).apply()
swift
// iOS - DON'T DO THIS
UserDefaults.standard.set(token, forKey: "access_token")

Why it fails:

  • Tokens are accessible via Android backups (if not disabled)
  • Tokens appear in device file system
  • Malware can read SharedPreferences/UserDefaults
  • Device backups to cloud may expose tokens

The fix: Use platform-specific secure storage:

  • iOS: Keychain Services
  • Android: EncryptedSharedPreferences or Keystore

🚫 Real mistake we've seen—and how to avoid it: A social media app stored OAuth tokens in SharedPreferences. A malicious app exploited a backup vulnerability and extracted tokens from thousands of users. The company faced a class-action lawsuit. They migrated to EncryptedSharedPreferences and implemented token rotation—should have been there from launch.

Using Insecure Local Databases

The mistake: Storing sensitive information in standard SQLite databases without encryption.

Why it's a problem:

  • Database files are stored in plaintext on the device
  • Accessible through device file explorer on rooted/jailbroken devices
  • Captured in device backups
  • Extractable via malware

The fix:

  • Android: Use Room with SQLCipher, or EncryptedFile API for smaller datasets
  • iOS: Use Core Data with encryption, or encrypted file storage
  • Cross-platform: Use encrypted storage plugins (flutter_secure_storage, react-native-encrypted-storage)

Hardcoding API Keys and Secrets

The mistake:

javascript
// React Native - DON'T DO THIS
const API_KEY = "sk_live_abc123xyz789";
const BASE_URL = "https://api.myapp.com";
```

**Why it's catastrophic:**
- API keys are visible in decompiled app binaries
- Attackers extract keys using tools like jadx (Android) or Hopper (iOS)
- Keys give direct access to your backend services
- Rotating compromised keys breaks all app versions in production

**The fix:**
- Never store secrets in client code
- Use environment-specific configurations
- Implement API key rotation on the backend
- Authenticate requests using user-specific tokens, not shared API keys
- For truly sensitive operations (payments), use server-side processing exclusively

### Over-Requesting Permissions

**The mistake:**
Requesting all potentially useful permissions upfront, "just in case."

**Why it triggers rejection:**
- Apple explicitly rejects apps that request permissions without clear justification
- Users abandon apps that request intrusive permissions unnecessarily
- Violates principle of least privilege

**Common culprits:**
- Location "always" when "when in use" would suffice
- Camera/microphone access for apps that don't need them
- Contacts access for single-user apps

**The fix:**
- Request permissions contextually, when the user needs the feature
- Provide clear, specific explanations (required on iOS 14+)
- Use permission request dialogs that explain *why* you need access
- Only request "always" location if your core functionality depends on it

🔍 **If you're targeting iOS 14+, here's what to watch for:** The App Tracking Transparency (ATT) framework requires explicit user consent before tracking. Apps that circumvent ATT by using device fingerprinting or other tracking methods face rejection. Implement ATT properly and respect user choices—this isn't optional.

### Poor Session Handling

**The mistake:**
- Sessions that never expire
- No token refresh mechanism
- Storing session state only client-side
- Not invalidating sessions on logout

**Why it's dangerous:**
If a token is compromised, the attacker has indefinite access. If a user logs out, their session should be *immediately* invalidated server-side.

**The fix:**
- Implement short-lived access tokens with refresh tokens
- Store refresh tokens in secure storage only
- Validate tokens server-side on every request
- Implement server-side session invalidation
- Clear all local auth data on logout

### Insecure Deep Links

**The mistake:**
Accepting deep links without validation, allowing attackers to trigger sensitive actions via malicious links.

**Example attack:**
```
myapp://payment/transfer?amount=1000&to=attacker@evil.com

If your app processes this without authentication, you've just enabled payment fraud.

The fix:

  • Validate all deep link parameters
  • Require authentication before processing sensitive actions
  • Use universal links (iOS) and App Links (Android) with domain verification
  • Never pass sensitive data (tokens, passwords) via deep links

Improper Biometric Implementation

The mistake: Using biometrics as the only authentication factor, or storing success/failure state client-side.

Why it fails:

  • Biometrics can be spoofed (though increasingly difficult)
  • Not all devices support biometrics
  • Storing "authentication passed" as a boolean is easily bypassed

The fix:

  • Use biometrics to unlock a secure token, not as standalone auth
  • Always validate credentials server-side
  • Implement fallback mechanisms (PIN, password)
  • Use platform-specific secure biometric APIs (BiometricPrompt on Android, LocalAuthentication on iOS)

4. Tactical, Experience-Based Tips from Mobile Experts

Now that you know what not to do, here's how to implement security correctly.

Secure Storage: Keychain, Keystore, EncryptedSharedPreferences

iOS: Keychain Services

The Keychain is iOS's secure, encrypted storage container. Use it for:

  • Authentication tokens
  • Passwords
  • Encryption keys
  • Certificates

Implementation:

swift
import Security

func saveToKeychain(key: String, value: String) -> Bool {
    let data = value.data(using: .utf8)!
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecValueData as String: data,
        kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    
    // Delete old value if exists
    SecItemDelete(query as CFDictionary)
    
    // Add new value
    let status = SecItemAdd(query as CFDictionary, nil)
    return status == errSecSuccess
}

func getFromKeychain(key: String) -> String? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecReturnData as String: true
    ]
    
    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)
    
    guard status == errSecSuccess,
          let data = result as? Data else {
        return nil
    }
    
    return String(data: data, encoding: .utf8)
}

Key attribute: kSecAttrAccessibleWhenUnlockedThisDeviceOnly ensures data is encrypted, only accessible when the device is unlocked, and doesn't sync to iCloud or backups.

Android: Keystore and EncryptedSharedPreferences

Android offers two primary secure storage mechanisms:

1. EncryptedSharedPreferences (easiest for most use cases):

kotlin
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys

val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)

val sharedPreferences = EncryptedSharedPreferences.create(
    "secure_prefs",
    masterKeyAlias,
    context,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

// Use like normal SharedPreferences
sharedPreferences.edit().putString("access_token", token).apply()
val token = sharedPreferences.getString("access_token", null)

2. Android Keystore (for encryption keys themselves): Use this when you need direct control over cryptographic keys for encrypting large amounts of data.

Cross-Platform: Flutter and React Native

Flutter:

dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

final storage = FlutterSecureStorage();

// Write
await storage.write(key: 'access_token', value: token);

// Read
String? token = await storage.read(key: 'access_token');

// Delete
await storage.delete(key: 'access_token');

React Native:

javascript
import * as Keychain from 'react-native-keychain';

// Save
await Keychain.setGenericPassword('access_token', token);

// Retrieve
const credentials = await Keychain.getGenericPassword();
if (credentials) {
  const token = credentials.password;
}

// Delete
await Keychain.resetGenericPassword();

Proper Use of Biometrics (Face ID, Touch ID)

Biometrics should unlock a token, not replace authentication.

iOS Implementation:

swift
import LocalAuthentication

func authenticateWithBiometrics(completion: @escaping (Bool) -> Void) {
    let context = LAContext()
    var error: NSError?
    
    guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
        completion(false)
        return
    }
    
    context.evaluatePolicy(
        .deviceOwnerAuthenticationWithBiometrics,
        localizedReason: "Authenticate to access your account"
    ) { success, error in
        DispatchQueue.main.async {
            if success {
                // Biometric auth successful - now retrieve token from Keychain
                if let token = getFromKeychain(key: "access_token") {
                    // Proceed with authenticated session
                    completion(true)
                }
            } else {
                completion(false)
            }
        }
    }
}

Android Implementation:

kotlin
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat

val executor = ContextCompat.getMainExecutor(this)
val biometricPrompt = BiometricPrompt(this, executor,
    object : BiometricPrompt.AuthenticationCallback() {
        override fun onAuthenticationSucceeded(
            result: BiometricPrompt.AuthenticationResult
        ) {
            super.onAuthenticationSucceeded(result)
            // Retrieve token from EncryptedSharedPreferences
            val token = securePrefs.getString("access_token", null)
            // Proceed with authenticated session
        }

        override fun onAuthenticationFailed() {
            super.onAuthenticationFailed()
            // Handle failure
        }
    })

val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("Biometric Authentication")
    .setSubtitle("Authenticate to access your account")
    .setNegativeButtonText("Use Password")
    .build()

biometricPrompt.authenticate(promptInfo)

👉 What this means for your app's success: Biometrics provide excellent UX, but they're a convenience layer over real authentication. Always have a fallback (password/PIN), and never trust biometric success without retrieving a securely stored token.

Token Lifecycle Management

Proper token management prevents unauthorized access and improves security posture.

Best practices:

  1. Use short-lived access tokens (15-60 minutes)
  2. Implement refresh tokens with longer lifetimes (days to weeks)
  3. Store refresh tokens in secure storage only
  4. Never store tokens in logs, URLs, or network requests visible in plain text
  5. Implement automatic token refresh before expiration
  6. Invalidate tokens server-side on logout

Example refresh flow:

kotlin
suspend fun refreshAccessToken(): String? {
    val refreshToken = securePrefs.getString("refresh_token", null) ?: return null
    
    val response = apiClient.refreshToken(refreshToken)
    
    if (response.isSuccessful) {
        val newAccessToken = response.body()?.accessToken
        val newRefreshToken = response.body()?.refreshToken
        
        securePrefs.edit()
            .putString("access_token", newAccessToken)
            .putString("refresh_token", newRefreshToken)
            .apply()
        
        return newAccessToken
    }
    
    return null
}

Certificate Pinning: Trade-offs and Implementation

Certificate pinning adds a layer of protection against man-in-the-middle attacks, but it comes with operational complexity.

When to use certificate pinning:

  • Financial transactions
  • Health data transmission
  • Authentication endpoints
  • Any app handling highly sensitive data

When NOT to pin:

  • General content apps
  • Apps without sensitive data
  • Early-stage apps where infrastructure may change rapidly

The trade-off: If your certificate expires or changes, pinned apps will stop working until users update. This requires careful certificate rotation planning.

iOS Implementation (using URLSession):

swift
func urlSession(
    _ session: URLSession,
    didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
    guard let serverTrust = challenge.protectionSpace.serverTrust else {
        completionHandler(.cancelAuthenticationChallenge, nil)
        return
    }
    
    let policies = [SecPolicy.ssl(false, host: challenge.protectionSpace.host)]
    SecTrustSetPolicies(serverTrust, policies as CFTypeRef)
    
    // Pin to public key hash (more flexible than certificate pinning)
    let expectedPublicKeyHash = "YOUR_PUBLIC_KEY_HASH_HERE"
    
    if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0),
       let publicKey = SecCertificateCopyKey(serverCertificate),
       let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) as Data? {
        
        let publicKeyHash = publicKeyData.sha256Hash() // Implement SHA256 hashing
        
        if publicKeyHash == expectedPublicKeyHash {
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
            return
        }
    }
    
    completionHandler(.cancelAuthenticationChallenge, nil)
}

Android Implementation (using OkHttp):

kotlin
val certificatePinner = CertificatePinner.Builder()
    .add("api.yourdomain.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build()

val okHttpClient = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

Optional—but strongly recommended by Simplifytechhubs mobile experts: Pin to public key hashes, not certificates. Public keys change less frequently than certificates, giving you more operational flexibility. Use multiple pins (backup pins) to allow graceful rotation.

Secure API Communication Patterns

Beyond HTTPS and pinning, implement these patterns:

1. Request signing: Sign requests with a secret known only to your app and server, preventing request tampering.

2. Implement rate limiting: Prevent brute-force attacks by limiting request frequency per user/device.

3. Use API versioning: Allows you to deprecate insecure endpoints without breaking existing apps.

4. Validate all inputs server-side: Never trust client data—validate everything on the backend.

5. Implement CORS properly: Restrict which domains can access your API.

Handling Offline Data Securely

Mobile apps often cache data for offline use. Do this securely:

Encrypt cached sensitive data:

kotlin
// Use EncryptedFile API for files
val encryptedFile = EncryptedFile.Builder(
    context,
    File(context.filesDir, "user_data.enc"),
    context,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

encryptedFile.openFileOutput().use { outputStream ->
    outputStream.write(sensitiveData.toByteArray())
}

Implement data expiration: Cached data should expire and be refreshed, especially for sensitive information.

Clear cache on logout: Always clear all cached sensitive data when the user logs out.

Protecting Against Reverse Engineering

Your app binary contains your code and logic. Attackers will decompile it.

Basic protections:

1. Code obfuscation:

  • iOS: Enable Bitcode and symbol stripping in Xcode
  • Android: Enable ProGuard or R8 in your build.gradle:
gradle
android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

2. String encryption: Encrypt sensitive strings (API endpoints, keys) at compile time, decrypt at runtime.

3. Root/jailbreak detection: Detect compromised devices and restrict functionality:

kotlin
// Android root detection (simplified)
fun isDeviceRooted(): Boolean {
    return checkSuBinary() || checkDangerousProps() || checkRootApps()
}

4. Anti-debugging: Detect when your app is running under a debugger and prevent it from functioning.

Realistic expectation: Determined attackers will bypass these protections. The goal is to raise the bar high enough that casual attacks aren't worth the effort, and to buy time to detect and respond to serious threats.

5. Platform-Specific & App-Type Insights

Different platforms and app categories have unique security considerations.

iOS-Specific Security

Keychain Best Practices:

  • Use kSecAttrAccessibleWhenUnlockedThisDeviceOnly for maximum security
  • Use kSecAttrAccessibleAfterFirstUnlock if data needs to be accessible in background
  • Never use kSecAttrAccessibleAlways—it's deprecated and insecure
  • Enable Keychain sharing only if needed for app groups

App Transport Security (ATS): ATS is enabled by default on iOS 9+ and blocks insecure HTTP connections.

Only disable for legitimate reasons:

xml
<!-- Info.plist - AVOID THIS -->
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

If you must allow specific insecure domains:

xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>legacy-api.example.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

⚠️ Real mistake we've seen—and how to avoid it: A developer disabled ATS globally during testing and forgot to remove it before submission. The app was rejected. Always use domain-specific exceptions and document why they exist.

Privacy Manifests (iOS 17+): Apple now requires apps to declare what data they collect in a privacy manifest file.

Create a PrivacyInfo.xcprivacy file:

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSPrivacyAccessedAPITypes</key>
    <array>
        <dict>
            <key>NSPrivacyAccessedAPIType</key>
            <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
            <key>NSPrivacyAccessedAPITypeReasons</key>
            <array>
                <string>CA92.1</string>
            </array>
        </dict>
    </array>
</dict>
</plist>

🔍 If you're targeting iOS 17+, here's what to watch for: Apps that access certain APIs (like UserDefaults, file timestamps) without declaring them in a privacy manifest will be flagged during App Store review. Include this file in your Xcode project before submission.

Android-Specific Security

Scoped Storage (Android 10+): Android restricts direct file system access. Apps must use:

  • MediaStore API for media files
  • Storage Access Framework (SAF) for user-selected files
  • App-specific storage for private files

Don't request MANAGE_EXTERNAL_STORAGE unless absolutely necessary—it triggers additional scrutiny during review.

Keystore Usage: Android Keystore stores cryptographic keys in hardware-backed security (on supported devices).

kotlin
val keyGenerator = KeyGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_AES,
    "AndroidKeyStore"
)

val keyGenParameterSpec = KeyGenParameterSpec.Builder(
    "my_key_alias",
    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setUserAuthenticationRequired(true)
    .setUserAuthenticationValidityDurationSeconds(300)
    .build()

keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()

Play Integrity API: Replaces SafetyNet and verifies:

  • App authenticity (not modified)
  • Device integrity (not rooted)
  • Account integrity

Use this for high-security operations:

kotlin
val integrityManager = IntegrityManagerFactory.create(context)
val request = IntegrityTokenRequest.builder()
    .setNonce(generateNonce())
    .build()

integrityManager.requestIntegrityToken(request)
    .addOnSuccessListener { response ->
        val token = response.token()
        // Verify token on your backend
    }

Network Security Configuration: Android 7+ allows fine-grained network security settings via XML:

Create res/xml/network_security_config.xml:

xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    
    <domain-config>
        <domain includeSubdomains="true">api.yourdomain.com</domain>
        <pin-set>
            <pin digest="SHA-256">base64PublicKeyHash==</pin>
            <pin digest="SHA-256">backupPublicKeyHash==</pin>
        </pin-set>
    </domain-config>
</network-security-config>

Reference in AndroidManifest.xml:

xml
<application
    android:networkSecurityConfig="@xml/network_security_config">
</application>

Cross-Platform Apps (Flutter, React Native)

Cross-platform frameworks introduce unique security challenges.

Native Security Gaps: JavaScript bridges (React Native) and platform channels (Flutter) can expose vulnerabilities if not secured properly.

Best practices:

  1. Validate all data crossing the native-JS boundary
  2. Use native modules for security-critical operations
  3. Don't implement cryptography in JavaScript—use native implementations
  4. Keep dependencies updated—cross-platform libraries have frequent security patches

Plugin Risks: Third-party plugins may have security vulnerabilities or excessive permissions.

Before adding a plugin:

  • Review its permissions
  • Check its maintenance status and community
  • Audit its code if handling sensitive data
  • Prefer plugins from trusted publishers

Flutter-specific:

dart
// Use platform-specific secure storage
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

// Use platform channels for sensitive operations
static const platform = MethodChannel('com.yourapp/security');

Future<String> secureOperation() async {
  try {
    return await platform.invokeMethod('performSecureOp');
  } on PlatformException catch (e) {
    // Handle error
  }
}


Visit Simplifytechhubs' Mobile Development Simplified resource center for additional guides on user retention, app analytics, and engagement optimization. Or connect with our mobile development experts for personalized guidance on building notification systems that drive real results for your specific app and audience.

Post a Comment

0 Comments