Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 16 min read

Mobile App Security Checklist 2026 (Banking & Fintech)

Short answer: a banking app security checklist should follow the OWASP MASVS v2 control groups: STORAGE, CRYPTO, AUTH, NETWORK, PLATFORM, CODE, RESILIENCE and PRIVACY. Keep secrets in the Keychain or Android Keystore, le

Short answer: a banking app security checklist should follow the OWASP MASVS v2 control groups: STORAGE, CRYPTO, AUTH, NETWORK, PLATFORM, CODE, RESILIENCE and PRIVACY. Keep secrets in the Keychain or Android Keystore, let the server make every authorization decision, enforce TLS, treat jailbreak and root detection as defense in depth, and verify it all with the OWASP MASTG.

In a banking app a leak is both a regulatory event and a direct financial loss. This checklist covers the client-side controls I check when building or auditing fintech and banking apps, from authentication to incident response, and maps each one to the OWASP Mobile Application Security Verification Standard (MASVS) so it lines up with the standard security reviewers already use.

The server half (token lifecycle, policy enforcement, rate limiting and abuse control) is in the Laravel API security checklist. The two are meant to be read together: a hardened app in front of an API that trusts the client is still an open attack path. For how these controls fit a regulated release process, see mobile architecture audits, and for having them built into a new banking app from the start, see fintech and banking app development.

How does OWASP MASVS map to a banking app?

MASVS v2 dropped the old L1/L2/R verification levels from the standard itself and organizes requirements into eight control groups. The levels live on as MAS testing profiles, which decide how strictly each control is tested; a banking app is what the stricter profiles exist for. The table maps each group to what it means for a banking app, the concrete platform check, and the section that covers it.

MASVS group What it means for a banking app Concrete iOS / Android check Section
MASVS-STORAGE Tokens, account data and statements never land in plaintext files, backups, caches or the clipboard. iOS: Keychain with a ThisDeviceOnly accessibility class; files under NSFileProtectionComplete. Android: Keystore-wrapped keys; allowBackup off or dataExtractionRules excluding sensitive files. 3
MASVS-CRYPTO Platform crypto with modern algorithms, and keys that are never hardcoded in the binary. iOS: CryptoKit AES-GCM, keys in the Secure Enclave or Keychain. Android: Keystore keys, StrongBox where the device has it. 3
MASVS-AUTH The server decides who the user is; biometrics unlock a credential instead of returning a yes/no. iOS: Keychain item with SecAccessControl (.biometryCurrentSet). Android: Keystore key with setUserAuthenticationRequired, used through BiometricPrompt with a CryptoObject. 1
MASVS-NETWORK TLS everywhere, no cleartext exceptions, and a deliberate decision on certificate pinning. iOS: App Transport Security with no NSAllowsArbitraryLoads. Android: Network Security Config with cleartextTrafficPermitted="false", optional pin-set with an expiration. 2
MASVS-PLATFORM Balances and account numbers do not leak through the app switcher, screenshots, WebViews or IPC. iOS: cover the window when the scene resigns active; restrict WKWebView JavaScript. Android: FLAG_SECURE on sensitive activities; exported="false" on components that do not need it. 1, 5
MASVS-CODE Supported OS versions are enforced, dependencies are current, and all input from outside the app is validated. Both: minimum OS policy, forced-update path, SDK inventory, deep link and push payload validation. 5, 7
MASVS-RESILIENCE Tampering, hooking and debugging are made expensive, as defense in depth rather than as a primary control. iOS: App Attest, jailbreak and debugger checks. Android: Play Integrity API, root checks, R8 shrinking and obfuscation. 4, 5
MASVS-PRIVACY Collect the minimum personal data, keep it out of logs and analytics, and disclose what third-party SDKs do. iOS: privacy manifest and os_log privacy redaction. Android: Play Data safety form, R8 rules stripping debug logs. 6

1. Authentication & Session Management

MASVS-AUTH, MASVS-STORAGE, MASVS-PLATFORM

Most account takeovers start with a stolen credential or a lifted token, so these controls limit how long any single secret is useful and where it can be used.

Token Lifecycle

  • Short-lived access tokens (around 15 minutes) with refresh tokens in the 7 to 30 day range (with Sanctum that means a short token expiry plus a rotate endpoint, or Passport; see the Laravel checklist): short enough that a token captured from a proxy log is dead before anyone reads it, long enough that refresh happens between sessions rather than mid-transfer
  • Rotate the refresh token on every use, and revoke the whole family if a rotated-out token is presented again; that is the signal a token was copied
  • Invalidate tokens server-side on logout. Expiry alone leaves a stolen token valid until the clock runs out
  • Keep access tokens in memory only, never in UserDefaults or SharedPreferences
  • Store refresh tokens in the iOS Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly, or encrypted with an Android Keystore key
  • For high-value apps, bind the session to the device: a non-exportable Secure Enclave or Keystore key, registered with the server at enrolment, signs each request, so a copied token cannot be replayed elsewhere

Biometric & MFA

  • Treat Face ID, Touch ID and Android biometrics as a local convenience that unlocks a stored credential. The server authenticates the credential; a biometric result alone never grants access
  • Make biometrics unlock a secret instead of returning a boolean: a Keychain item protected by SecAccessControl with .biometryCurrentSet on iOS, or a Keystore key with setUserAuthenticationRequired(true) used through BiometricPrompt with a CryptoObject on Android. A hooked evaluatePolicy callback then unlocks nothing, and .biometryCurrentSet invalidates the item if a new fingerprint or face is enrolled
  • Require a second factor for transfers above a threshold, password changes, and new payees
  • Step-up within a session: re-authenticate before sensitive operations even if the session is fresh
  • Prefer TOTP or push approval over SMS codes. SIM-swap fraud is the standard way SMS OTP is defeated, and it is cheap

Passkeys Instead of Passwords

  • Passkeys (FIDO2/WebAuthn) remove the password from sign-in, so there is nothing to phish, reuse or stuff. The private key never leaves the device or the user’s credential manager, and each passkey is bound to your domain, so a look-alike site cannot use it
  • On iOS 16 and later use ASAuthorizationPlatformPublicKeyCredentialProvider with an associated domain (webcredentials:). On Android 9 and later use the Credential Manager API. Both talk to the same WebAuthn relying-party endpoints on your server
  • Synced passkeys (iCloud Keychain, Google Password Manager) survive a lost phone, which matters for account recovery. NIST SP 800-63B-4 accepts synced passkeys at AAL2 when the sync service meets its requirements; for the highest assurance, keep a device-bound key such as the Secure Enclave or Keystore key used for session binding above
  • Roll out alongside the existing login, not as a replacement on day one. Offer passkey creation after a successful sign-in, keep step-up checks for transfers and new payees, and make recovery at least as strong as sign-in, since a weak reset flow undoes the passkey
  • The passkey, biometric, device-binding and recovery code for all of this is in secure authentication best practices for fintech apps

Session Controls

  • Auto-lock after a short inactivity window; 2 to 5 minutes is typical for banking apps. Make it configurable only within that range
  • Limit concurrent sessions per user, show the user their active devices, and let them sign one out. On a Laravel backend, laravel-device-tokens gives you one Sanctum token per device with sign-out per device or everywhere

Screenshot and App-Switcher Masking

  • iOS takes a snapshot of the screen for the app switcher when the app goes to the background. Cover the window with a privacy view in sceneWillResignActive and remove it in sceneDidBecomeActive, so balances and account numbers never reach that snapshot
  • iOS has no public API to block screenshots. Detect screen recording and mirroring with UIScreen.isCaptured, react to userDidTakeScreenshotNotification, and hide sensitive values while capture is active
  • On Android, set WindowManager.LayoutParams.FLAG_SECURE on sensitive activities. It blocks screenshots, screen recording and the recents-screen preview in one step

2. API & Network Security

MASVS-NETWORK

Transport Security

  • TLS 1.2 or later only, enforced at the server. ATS already refuses anything older on iOS, so the client side is mostly about not adding exceptions
  • App Transport Security with no exceptions. NSAllowsArbitraryLoads in a production Info.plist is a finding on its own
  • On Android, ship a Network Security Config with cleartextTrafficPermitted="false" and no user-added CAs trusted in release builds (scope debug-overrides to debug only)
  • Validate the full certificate chain. A URLSessionDelegate or TrustManager that accepts any certificate to make a staging environment work has a way of surviving into release

Certificate Pinning: Trade-offs

Pinning stops interception by a mis-issued or user-installed certificate. It does not stop an attacker who controls the device, and a badly managed pin locks every user out on the day a certificate renews.

  • Pin the public key (SPKI hash) rather than the whole certificate, so renewing a certificate with the same key does not break the app
  • Include at least one backup pin for a key that is not yet in use, so rotation does not force an app update
  • iOS: use NSPinnedDomains in the ATS configuration or validate in a URLSessionDelegate trust challenge. Android: use a <pin-set> with an expiration date in Network Security Config, so an outdated build falls back to normal TLS validation rather than failing closed
  • Have a remote way to update or relax pins, and a runbook for certificate rotation, before you ship pinning

Request Integrity

  • TLS already protects requests in transit. Request signing earns its place as device binding: sign with the non-exportable Secure Enclave or Keystore key from section 1, so a bearer token is useless off the device it was issued to. An HMAC with a per-session secret delivered over the same TLS channel adds little
  • With signing in place, include a timestamp and nonce in the signed payload and reject anything outside a few-minute window, so a captured request cannot be replayed
  • Send tokens only in the Authorization: Bearer header. Query strings end up in access logs, proxies, and crash reports
  • Expect HTTP 429 with Retry-After and honour it with backoff. A client that retries instantly turns a brief server incident into a longer one
  • Error responses must not leak stack traces, SQL, or whether an account exists. Field-level validation messages are fine; β€œno account with that email” is not

3. Data Storage & Encryption

MASVS-STORAGE, MASVS-CRYPTO

What Goes Where on iOS

  • Keychain: tokens, passwords, private keys, PINs. Use kSecAttrAccessibleWhenUnlockedThisDeviceOnly so items are never migrated to another device through a backup
  • Core Data / SQLite: financial records, encrypted with SQLCipher or protected with the Data Protection class NSFileProtectionComplete. Data Protection only makes the file unreadable while the device is locked; data that must stay unreadable on a jailbroken, unlocked device needs application-level encryption too
  • UserDefaults: non-sensitive UI preferences only. It is a plist on disk and lands in backups
  • Cached images and PDFs: disable or clear the URLCache for sensitive responses and mark files with NSFileProtectionComplete. Downloaded statements are the classic leak here
  • Backups: set isExcludedFromBackup on files that should not leave the device
  • Clipboard: copy sensitive values with UIPasteboard options .localOnly and an .expirationDate (60 seconds is enough for an account number), so they do not sync through Universal Clipboard or linger

What Goes Where on Android

  • Android Keystore: generate keys inside the Keystore so the key material is non-exportable; request StrongBox with setIsStrongBoxBacked(true) where the device supports it and fall back when it does not
  • App-private storage: encrypt tokens and records with a Keystore-held key before writing them. Never plain SharedPreferences or external storage
  • Backups: set android:allowBackup="false", or use dataExtractionRules (Android 12+) to exclude sensitive files from cloud backup and device-to-device transfer
  • Clipboard: mark copied sensitive values with ClipDescription.EXTRA_IS_SENSITIVE (Android 13+) so they are not shown in the clipboard preview

Encryption Standards

  • AES-256-GCM for symmetric encryption, which gives confidentiality and integrity in one primitive (CryptoKit AES.GCM on iOS, a Keystore-backed Cipher on Android). Never reuse a nonce with the same key
  • Use the Secure Enclave for private keys when available. It holds P-256 keys only, so wrap symmetric keys with an enclave key rather than storing them inside it
  • Never hardcode keys. Generate them in the Keystore or Secure Enclave; derive from a user secret only when the data must survive without the server
  • Zero sensitive buffers after use. Swift and Kotlin strings cannot be reliably zeroed and may be copied around memory, so keep secrets in mutable byte arrays you can wipe

4. Device & Runtime Integrity

MASVS-RESILIENCE

Jailbreak and Root Detection: Defense in Depth, Not a Control

On a jailbroken or rooted device the sandbox is gone: Keychain items can be dumped, pinning bypassed with one Frida script, and any method hooked. Every detection check runs on that same device, so each one can be patched out. I treat detection as friction plus a risk signal for the server, never as the thing that keeps an account safe. Layered checks still raise the cost:

  • Look for jailbreak artifacts: package manager apps, the /var/jb prefix used by rootless jailbreaks, and injected libraries listed by _dyld_get_image_name. Hardcoded lists of 2015-era paths are the first thing attackers patch
  • Attempt to write outside the app sandbox; it should fail on a stock device
  • Check whether fork() succeeds; it is denied to sandboxed apps on stock iOS
  • On Android, check for su binaries, test-keys builds, Magisk and other root-management packages, and whether SELinux is enforcing
  • Verify the app’s own code signature and bundle identifier at runtime; repackaged apps are a common distribution path for banking malware
  • Combine several independent signals and send them to the server. A single boolean in the app is one patch away from always-false
  • On detection, degrade rather than refuse: warn the user, disable transfers and payee changes, and log the event server-side with the device signals

Runtime Attack Detection

  • Debugger detection on iOS via sysctl (the P_TRACED flag) or ptrace(PT_DENY_ATTACH). ptrace is not public API, so it is called through dlsym, and both checks are trivially patched. A speed bump, not a gate
  • For Objective-C code paths that matter, confirm at runtime that method_getImplementation still points at your own code. Swift final classes and structs are harder to swizzle in the first place
  • Detect the simulator and emulators, and never ship production credentials or endpoints in builds that can run there
  • Use App Attest (DeviceCheck framework, iOS 14+). It creates a Secure Enclave key attested by Apple; your server verifies the attestation once and a per-request assertion after that. It is the strongest available signal that a request came from your unmodified app
  • On Android, use the Play Integrity API and verify its verdict on your server, not in the app. Decide in advance what each verdict level (device, app, account) is allowed to do

5. Code Hardening & Obfuscation

MASVS-CODE, MASVS-RESILIENCE, MASVS-PLATFORM

  • Xcode release builds on arm64 already produce position-independent executables with stack protection. Verify nothing in the build settings turned them off (otool -hv shows the PIE flag), and never ship Swift built with -Ounchecked, which removes overflow and precondition checks
  • Strip symbols from release builds (Deployment Postprocessing and Strip Linked Product set to YES) and upload the dSYM to your crash reporter, so stack traces are readable to you and not to whoever downloads the IPA
  • Obfuscate sensitive strings and class names where the threat model justifies it: iXGuard on iOS, DexGuard on Android. R8 alone shrinks and renames; it is not meaningful obfuscation against a determined reverser
  • Remove NSLog, print, and debug output from production builds. A #if DEBUG wrapper is enough on iOS; section 6 has the Android R8 rule
  • Restrict JavaScript in WKWebView (WKWebpagePreferences.allowsContentJavaScript = false) unless the page needs it, and never expose a native bridge (WKScriptMessageHandler, addJavascriptInterface) to content you do not control
  • Treat deep links, universal and app links, and push payloads as untrusted input: validate every parameter and never let a deep link complete an action by itself. Set android:exported="false" on components that do not need to be reachable
  • Enforce a minimum supported OS version and keep a forced-update path, so a build with a known vulnerability can be retired without waiting for users
  • Inventory third-party SDKs. Each one ships with your signing identity and permissions; review what it collects, how often it updates, and whether it has a privacy manifest

6. Logging: What NOT to Log

MASVS-STORAGE, MASVS-PRIVACY

Logs are the storage location nobody threat-models. On iOS, NSLog and unredacted os_log output goes to the unified log, readable from any Mac the phone is plugged into and included in sysdiagnose bundles. Server logs are often less protected than the database they describe.

Never Log

  • Full account numbers, card numbers, CVV
  • Auth tokens, refresh tokens, session IDs
  • Passwords (even hashed)
  • Full API request/response bodies
  • PII: SSN, DOB, full address
  • Biometric data or persistent device identifiers

Safe to Log

  • Event types (login_attempt, transfer_initiated)
  • Masked identifiers (last 4 of account)
  • Error codes and HTTP status codes
  • Request duration and latency
  • Feature flags and A/B experiment IDs
  • Random per-launch correlation IDs that are not the authenticated session identifier

Secure Logging in the App

  • On iOS, use Logger / os_log and keep dynamic values at the default .private level so they are redacted outside a debugger. Marking a value .public is a code review item
  • On Android, strip Log.d and Log.v from release builds with an R8 -assumenosideeffects rule on android.util.Log
  • Check what crash reporters and analytics SDKs capture. Breadcrumbs, network logging, and session replay can collect request bodies and screen contents by default

Server-Side Audit Logging

  • Log every authentication event with timestamp, IP, device identifier, and outcome
  • Log high-value transactions with user ID, amount, and a masked counterparty
  • Log admin actions and configuration changes with the actor
  • Ship logs to a SIEM (Splunk, Datadog, CloudWatch) with retention set by your compliance regime. PCI DSS requires twelve months with three months immediately available; treat that as the floor
  • Alert on brute-force patterns, impossible travel, and unusual transaction volume per account, not just per system

7. Incident Response Readiness

MASVS does not cover incident response, but a banking app cannot ship without it. The three kill switches below separate a two-hour incident from a two-week one, because each removes the dependency on users updating the app.

Kill Switches

  • Remote feature flags that can disable any feature (transfers, new payees, a vulnerable SDK) without an app update
  • A forced-update mechanism driven by a server-controlled minimum build, so a compromised version can be blocked the same day
  • Server-side session revocation for one user or every user, tested in staging before you need it

Detection & Response Playbook

  • Define P0/P1/P2 severity levels with response SLAs before launch
  • Name a single incident owner with authority to pull the kill switches
  • Prepare user notification templates for credential-exposure scenarios in advance; writing them during the incident is how they go out late
  • Know the regulatory clocks before launch. GDPR gives 72 hours to notify the supervisory authority; the US banking agencies’ Computer-Security Incident Notification rule requires a banking organization to notify its primary federal regulator within 36 hours of determining that a notification incident has occurred (a bank service provider must notify the bank as soon as possible); PCI DSS card-brand rules and US state breach laws each set their own deadlines
  • Run a tabletop exercise each quarter against a realistic scenario: a leaked signing key, a malicious SDK update, a credential-stuffing run

How do you test a banking app against MASVS?

MASVS says what to achieve; the OWASP Mobile Application Security Testing Guide (MASTG) says how to verify it, with iOS and Android test cases organized by the same control groups. I use it to structure internal reviews and to scope external penetration tests, so both produce evidence in the same shape.

  • Scope the review by MASVS group, and agree which MAS testing profile applies before testing starts
  • Run static checks on every release build: entitlements, ATS and Network Security Config, exported components, backup flags, hardcoded secrets
  • Run dynamic checks on a test device: inspect what ends up in the Keychain, Keystore, files, logs and the app-switcher snapshot
  • Record results per control so the evidence is ready for compliance and audit reviews

Mobile App Penetration Testing Checklist for Fintech Apps

When I scope a pen test on a banking or fintech app, iOS and Android, these are the checks I make sure are in it. Each one maps back to a section above.

  • Traffic: route the app through an intercepting proxy, confirm TLS is enforced, and try to bypass certificate pinning
  • Storage: pull the app container and check the Keychain or Keystore, databases, caches, logs and screenshots for tokens or personal data
  • API authorization: replay and edit requests to read another user’s accounts, change amounts or skip steps; the server must refuse every one
  • Session: confirm tokens expire, logout revokes them on the server, and a stolen token cannot be used from a second device
  • Runtime: run on a jailbroken or rooted device with Frida, and check that tampering is detected and reported to the server
  • Binary: decompile the release build for hardcoded keys, test endpoints, debug menus and verbose logging
  • Platform: check deep links, exported Android components, pasteboard use and backups for data that leaks out of the app

Security Audit Checklist Summary

  • Short-lived tokens + refresh rotation
  • Biometrics unlock a Keychain/Keystore secret
  • TLS 1.2+, ATS and Network Security Config
  • Certificate pinning with backup pins
  • Device-bound request signing
  • Keychain with device-only access
  • AES-256-GCM encryption at rest
  • App-switcher masking and FLAG_SECURE
  • Jailbreak/root detection (layered)
  • App Attest / Play Integrity
  • Release build hardening verified
  • Symbols stripped from release
  • No PII in any log output
  • SIEM with anomaly alerts
  • Remote kill switches in place
  • Incident response playbook ready

Need a Security Audit for Your Mobile Banking App?

If you want this checklist run against your app, see mobile architecture audits. If you are building the app, or a new module in it, see fintech and banking app development.

Request a Security Audit
View Audit Services

Originally published at puneetpal.dev.

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.