Skip to main content

Mobile Security

Mobile Security

Project: Drop — Fintech Payment App Version: 0.1.0 Date: 2026-02-23 Author: John (AI Director, ALAI) Status: Draft Reviewers: Alem Bašić (CEO)

Document History

Version Date Author Changes
0.1 2026-02-23 John Initial draft — current state analysis + Phase 2 security roadmap

1. Mobile Threat Model

Context: Drop is a PSD2-regulated payment app. It handles real money movements from users' bank accounts. Drop never stores funds, but it stores authentication tokens that could initiate payments if compromised.

Threat Actor Goal Attack Vector Likelihood Impact Mitigation
Malicious app on device Steal Bearer token Shared AsyncStorage access Medium Critical Phase 2: migrate token to expo-secure-store
Network interceptor (MITM) Intercept payment API calls Rogue WiFi, proxy Medium Critical TLS 1.3 enforced; certificate pinning Phase 2
Reverse engineer Extract API keys, business logic APK/IPA decompilation Medium Medium Hermes bytecode; no hardcoded secrets
Stolen/lost device Initiate payments as victim Physical access + stored token High Critical Phase 2: biometric lock, token binding
Rooted/jailbroken device Bypass security controls OS privilege escalation Low High Root/jailbreak detection Phase 2
Session hijacking Replay stolen token Network capture Low Critical Token expiry (7 days); HTTPS enforced
Social engineering Steal BankID credentials Phishing Medium Critical User education; BankID handles its own auth

Assets to protect (priority order):

  1. Bearer token (allows payment initiation from user's bank account)
  2. User PII (name, email, phone — BankID-verified identity)
  3. Transaction history (financial data)
  4. Exchange rate API access
  5. App integrity (prevent tampered builds)

2. Authentication

2.1 Biometric Authentication

Current state (Phase 1): Not implemented. Login is email + password only.

Phase 2 implementation:

// Phase 2 — lib/biometrics.ts
import * as LocalAuthentication from 'expo-local-authentication';

export async function authenticateWithBiometrics(): Promise<boolean> {
  const hasHardware = await LocalAuthentication.hasHardwareAsync();
  const isEnrolled = await LocalAuthentication.isEnrolledAsync();

  if (!hasHardware || !isEnrolled) {
    return promptPINFallback();
  }

  const result = await LocalAuthentication.authenticateAsync({
    promptMessage: 'Bekreft identiteten din',
    fallbackLabel: 'Bruk PIN',
    cancelLabel: 'Avbryt',
    disableDeviceFallback: false,
  });

  return result.success;
}

Planned biometric use cases (Phase 2):

  • App unlock after backgrounding > 5 minutes
  • Confirm high-value remittance transactions (amount > 5000 NOK)
  • Change security settings
  • View full transaction history

Fallback: 4-digit PIN (same as web registration flow) → hash stored in expo-secure-store (bcrypt, not reversible)

2.2 Session Management

Property Current (Phase 1) Phase 2 Target
Access token lifetime 7 days (Bearer) 15 minutes (access) + 7 days (refresh)
Refresh token Not implemented Rotate on each use
Session timeout Never (token-based) App lock after 5 min background
Max concurrent sessions Not limited 3 devices max
Force logout triggers Manual logout only Password change, suspicious activity, admin revoke

Token storage (current Phase 1):

// CURRENT — lib/api.js — in-memory + AsyncStorage
// WARNING: AsyncStorage is not encrypted — acceptable for MVP, not for production
let token = null;  // Module-level in-memory
await AsyncStorage.setItem('auth_token', token);  // Persisted (unencrypted)

Token storage (Phase 2 target):

// PHASE 2 — lib/auth.ts
import * as SecureStore from 'expo-secure-store';

// CORRECT — encrypted secure storage
await SecureStore.setItemAsync('access_token', token, {
  keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});

// WRONG — DO NOT USE FOR TOKENS
// await AsyncStorage.setItem('access_token', token);  // Unencrypted!

2.3 BankID Authentication

BankID is Drop's primary strong authentication mechanism. The BankID flow is handled by the BankID OIDC provider — Drop does not see or store BankID credentials.

Mobile BankID flow (ADR-011):

1. GET /v1/auth/bankid/initiate?platform=mobile → { redirectUrl, state }
2. Open BankID in expo-web-browser (secure, isolated browser)
3. BankID redirects to drop://auth/callback?code=&state=
4. expo-linking catches the deep link
5. POST /v1/auth/bankid/callback → Bearer token (7-day)
6. Token stored (AsyncStorage Phase 1, expo-secure-store Phase 2)

Security properties of BankID:

  • Norwegian national ID system — eID Level 3 (substantial assurance)
  • All authentication happens in BankID's secure environment
  • Drop never sees BankID PIN or biometric data
  • BankID tokens are single-use and time-limited

3. Data Protection

3.1 Secure Storage Policy

Data Type Phase 1 Storage Phase 2 Storage Forbidden
Bearer token AsyncStorage (unencrypted) expo-secure-store (Keychain/Keystore) Hardcoded
Refresh token Not implemented expo-secure-store AsyncStorage
User PII Not cached locally SQLite (encrypted with SQLCipher) AsyncStorage
Non-sensitive prefs AsyncStorage AsyncStorage
Biometric key hash Not implemented expo-secure-store Any other storage

Phase 1 risk: Bearer token stored in AsyncStorage (unencrypted) is the primary security gap. Mitigation: 7-day token expiry limits exposure window. Production apps should implement Phase 2 before launch.

3.2 Data Encryption at Rest

Phase 1: No local database. Auth token in AsyncStorage (unencrypted).

Phase 2 — Database encryption:

// Phase 2 — lib/database.ts
import * as SQLite from 'expo-sqlite';
import * as SecureStore from 'expo-secure-store';

async function openDatabase() {
  // Generate encryption key on first launch
  let dbKey = await SecureStore.getItemAsync('db_encryption_key');
  if (!dbKey) {
    dbKey = generateRandomKey(256);  // 256-bit random key
    await SecureStore.setItemAsync('db_encryption_key', dbKey);
  }

  return await SQLite.openDatabaseAsync('drop.db', { key: dbKey });
}

3.3 Sensitive Data in Memory

Rule Phase 1 Status Phase 2 Implementation
Clear passwords after use Partially (useState cleanup) Overwrite variable, trigger GC
No PII in logs Implemented — no console.log(user) in production paths Custom logger strips PII fields
No PII in crash reports TBD — no crash reporting in Phase 1 Sentry beforeSend hook scrubs payload
No PII in analytics TBD — no analytics in Phase 1 Hash user IDs

3.4 Screenshot Prevention

Drop displays financial data (balance, transaction amounts). Screenshots should be prevented on sensitive screens.

// Phase 2 — sensitive screen protection
import { preventScreenCapture, allowScreenCapture } from 'expo-screen-capture';

// Screens requiring screenshot prevention:
// - Dashboard (shows bank balance)
// - Send Money (shows account balance, transaction amounts)
// - Transaction History (financial data)

useEffect(() => {
  preventScreenCapture();
  return () => allowScreenCapture();
}, []);

Note: iOS screenshot prevention via expo-screen-capture shows a blank screen in the app switcher but cannot prevent all screenshots. Android FLAG_SECURE prevents screenshots and screen recording.

3.5 Clipboard Protection

  • Password fields: secureTextEntry={true} in React Native TextInput — disables clipboard by default on iOS
  • Transaction amounts copied to clipboard: clear after 60 seconds (Phase 2)
  • Account numbers: never copied to clipboard by app — shown as masked

4. Network Security

4.1 Certificate Pinning

Current (Phase 1): Not implemented. Standard HTTPS/TLS only.

Phase 2 implementation:

// Phase 2 — requires react-native-ssl-pinning or OkHttp pinner
// Pinned endpoints:
// - drop-app.vercel.app (production API)
// - BankID OIDC endpoints

const response = await fetch('https://drop-app.vercel.app/api/transactions', {
  method: 'GET',
  // Certificate pinning configuration here
  headers: { Authorization: `Bearer ${token}` },
});

Certificate rotation process:

  1. Pin both current cert and backup cert simultaneously
  2. Deploy app update with new cert added (OTA via EAS Update)
  3. After old cert expires, remove old cert from pins
  4. Test rotation in staging before production

4.2 SSL/TLS Configuration

Requirement Value
Minimum TLS version TLS 1.2
Preferred TLS version TLS 1.3
HTTP allowed No (Vercel enforces HTTPS)
Cipher suites TLS 1.3 default (forward-secrecy)

iOS App Transport Security (ATS):

  • No NSAllowsArbitraryLoads: true
  • All API calls go to https://drop-app.vercel.app (HTTPS enforced)

Android Network Security Config:

<!-- android/app/src/main/res/xml/network_security_config.xml — Phase 2 -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <domain-config cleartextTrafficPermitted="false">
    <domain includeSubdomains="true">getdrop.no</domain>
    <domain includeSubdomains="true">drop-app.vercel.app</domain>
  </domain-config>
</network-security-config>

4.3 Network Request Security

  • All requests over HTTPS (enforced by Vercel)
  • Request signing (HMAC) — not implemented (Phase 2)
  • Application-layer encryption for payment payloads — TBD (Phase 2)

5. Application Security

5.1 Code Obfuscation

Platform Tool Status
Android ProGuard/R8 via Expo release build Done (Expo default)
iOS Swift compiler optimizations Done (Expo default)
JavaScript Hermes bytecode compilation Done (Expo default)

Hermes: Expo SDK 54 uses Hermes by default. JavaScript is compiled to bytecode, making reverse engineering significantly harder than raw JS.

5.2 Root / Jailbreak Detection

Current (Phase 1): Not implemented.

Phase 2 implementation:

// Phase 2 — lib/integrity.ts
import * as Device from 'expo-device';

export function checkDeviceIntegrity(): { compromised: boolean; reason: string } {
  // expo-device provides basic device info
  // For more robust detection: react-native-device-info or jail-monkey
  const isEmulator = !Device.isDevice;

  return {
    compromised: isEmulator && !__DEV__,
    reason: isEmulator ? 'emulator_in_production' : 'clean',
  };
}

Response to compromised device (Phase 2):

  • Warn user: "Drop er ikke tilgjengelig på enheter med modifisert programvare"
  • Allow use with warning (not blocking in Phase 2) — higher blocking for payment flows
  • Log security event to backend

5.3 Debug Detection

Production builds must have:

  • __DEV__ mode disabled (Expo release build)
  • console.log stripped (Expo/Metro default in release builds)
  • Flipper disabled (Expo default in release builds)
  • Source maps NOT bundled with the app — upload to Sentry separately (Phase 2)

5.4 Reverse Engineering Prevention

Measure Phase 1 Phase 2
No hardcoded secrets Done — API URL only, no keys API keys fetched at runtime from secure config
No plain API keys in bundle Done Done
Sensitive logic on server Done — Drop is pass-through; payments initiated server-side Done
Binary protection Hermes bytecode (done) ProGuard rules review

6. OWASP Mobile Top 10 Checklist

# Risk Phase 1 Status Notes
M1 Improper Credential Usage Partial Bearer token in AsyncStorage (unencrypted) — Phase 2 fix
M2 Inadequate Supply Chain Security Unknown npm audit not run in CI yet
M3 Insecure Authentication/Authorization Pass BankID + JWT validated server-side
M4 Insufficient Input/Output Validation Pass React Native TextInput handles basic validation; XSS not applicable to React Native
M5 Insecure Communication Pass HTTPS enforced; cert pinning Phase 2
M6 Inadequate Privacy Controls Partial Data minimization done; GDPR consent in web app — mobile privacy notice TBD
M7 Insufficient Binary Protections Pass Hermes bytecode; Expo release build
M8 Security Misconfiguration Pass No dev configs in prod build; __DEV__ disabled
M9 Insecure Data Storage Fail Bearer token in unencrypted AsyncStorage — fix before production
M10 Insufficient Cryptography Pass TLS 1.3; no MD5/SHA-1 usage

Critical action before production: M9 (Insecure Data Storage) — migrate Bearer token from AsyncStorage to expo-secure-store. This is the single most important security fix before v1.0 production launch.


7. Security Testing Tools

Tool Type When Output
MobSF (Mobile Security Framework) Static analysis Pre-release Risk report
npm audit / pnpm audit Dependency scan Every PR CVE report
Expo Security Checks Build-time EAS Build Security warnings
Burp Suite Network proxy Penetration test API vulnerabilities
Frida Dynamic analysis Penetration test Runtime behavior
OWASP ZAP Automated API scan CI/CD Vulnerability report

Penetration test cadence: Before v1.0 production launch (pre-launch), then annually. Last pentest date: Not yet conducted. Next pentest date: Before production launch.


8. Compliance Requirements

Requirement Applicable Status Notes
GDPR (EU/EEA users) Yes Partial Privacy policy at /privacy; data deletion via support; consent management in web app
PSD2 (Payment Services Directive 2) Yes Pass PISP/AISP model; Open Banking; pre-payment disclosure implemented
Finanstilsynet (Norwegian FSA) Yes In progress Regulatory compliance ongoing
Hvitvaskingsloven (AML) Yes Pass 5-year data retention; KYC via BankID
CCPA No N/A Norwegian market only — no California users targeted
COPPA No N/A 18+ minimum age (BankID requirement)
PCI DSS No N/A Drop never stores card data — pass-through model
App Store Privacy Label Yes TODO Must complete before submission
Play Store Data Safety Yes TODO Must complete before submission
HIPAA No N/A No health data

9. Security Roadmap

Phase 1 (Current — MVP)

  • HTTPS/TLS enforced
  • BankID authentication via expo-web-browser
  • Hermes bytecode (basic obfuscation)
  • CRITICAL: Migrate Bearer token to expo-secure-store (before production)

Phase 2 (Pre-Launch)

  • expo-secure-store for all tokens
  • Certificate pinning for API endpoints
  • Biometric authentication (Face ID / Touch ID)
  • Screenshot prevention on financial screens
  • Root/jailbreak detection with user warning
  • Mobile penetration test (MobSF + Burp Suite)
  • Source maps configured (Sentry, not bundled)

Phase 3 (Post-Launch)

  • App attestation (Play Integrity API / App Attest)
  • Transaction anomaly detection
  • Session device binding
  • Refresh token rotation
  • Self-service account deletion (GDPR)

Approval

Role Name Date Signature
Author John (AI Director) 2026-02-23
Mobile Lead
Security Lead
Legal