Harjot Singh Panesar
WritingSecurity

iOS Security Best Practices 2026: Complete Developer Guide

Secure your iOS app with Keychain, certificate pinning, biometric auth, and protection against OWASP mobile vulnerabilities. Complete code examples.

09 Jan 2026Harjot Singh Panesar5 min read
iOS Security Best Practices 2026: Complete Developer Guide

Security isn't optional - it's foundational. A single data breach can destroy user trust and expose your company to legal liability. After building secure iOS apps for enterprise clients handling sensitive data, I've compiled the essential security practices every iOS developer must implement.

1. Secure Data Storage

The Keychain: Your First Line of Defense

Never store sensitive data in UserDefaults, plist files, or plain Core Data. The iOS Keychain provides hardware-backed encryption:

import Security

class KeychainManager {
    static func save(key: String, data: Data) -> Bool {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecValueData as String: data,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]

        // Delete existing item first
        SecItemDelete(query as CFDictionary)

        let status = SecItemAdd(query as CFDictionary, nil)
        return status == errSecSuccess
    }

    static func load(key: String) -> Data? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]

        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)

        return status == errSecSuccess ? result as? Data : nil
    }
}

Security Alert: Never store API keys, passwords, or tokens in your source code. Use the Keychain or secure environment variables during build time.

Keychain Access Levels

Accessibility LevelWhen to Use
kSecAttrAccessibleWhenUnlockedThisDeviceOnlyMost sensitive data - only accessible when device is unlocked, not backed up
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnlyBackground tasks needing credentials after first unlock
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlyRequires device passcode, deleted if passcode removed

Encrypting Local Databases

For apps storing significant local data, encrypt your database:

  • SQLCipher - AES-256 encryption for SQLite databases
  • Core Data with encryption - Use NSFileProtection or custom encryption
  • Realm Encryption - Built-in 64-byte encryption key support
// SQLCipher example
let db = try Connection("/path/to/db.sqlite3")
try db.key("your-256-bit-key")

// Realm encryption
var config = Realm.Configuration()
config.encryptionKey = getKeyFromKeychain() // 64-byte key
let realm = try Realm(configuration: config)

2. Secure Network Communication

App Transport Security (ATS)

Always use HTTPS. ATS is enabled by default - don't disable it:

// Info.plist - DON'T DO THIS
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/> // NEVER in production!
</dict>

Certificate Pinning

Prevent man-in-the-middle attacks by pinning your server's certificate:

class CertificatePinner: NSObject, URLSessionDelegate {
    private let pinnedCertificates: [Data]

    init(certificateNames: [String]) {
        pinnedCertificates = certificateNames.compactMap { name in
            guard let url = Bundle.main.url(forResource: name, withExtension: "cer"),
                  let data = try? Data(contentsOf: url) else { return nil }
            return data
        }
        super.init()
    }

    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
              let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        // Get server certificate
        guard let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        let serverCertData = SecCertificateCopyData(serverCertificate) as Data

        // Check if server cert matches pinned cert
        if pinnedCertificates.contains(serverCertData) {
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
}

Pro Tip: Pin the public key instead of the certificate. This survives certificate rotation without app updates.

3. Authentication Security

Biometric Authentication

Implement Face ID and Touch ID properly:

import LocalAuthentication

class BiometricAuth {
    func authenticate() async throws -> Bool {
        let context = LAContext()
        var error: NSError?

        guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            throw BiometricError.notAvailable
        }

        return try await context.evaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics,
            localizedReason: "Authenticate to access your account"
        )
    }
}

// Combine with Keychain for maximum security
func getSecureToken() async throws -> String? {
    guard try await BiometricAuth().authenticate() else {
        throw AuthError.biometricFailed
    }

    // Only accessible after biometric auth
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "authToken",
        kSecReturnData as String: true,
        kSecUseAuthenticationContext as String: LAContext()
    ]

    // ... retrieve from Keychain
}

Secure Token Management

  • Use short-lived access tokens with refresh tokens
  • Store refresh tokens in Keychain with biometric protection
  • Implement token rotation on each refresh
  • Clear all tokens on logout
  • Handle token expiration gracefully

4. Code Security

Prevent Reverse Engineering

While no protection is foolproof, make it harder:

  • Enable Bitcode - Apple recompiles your app
  • Strip debug symbols - In release builds
  • Obfuscate sensitive strings - Don't hardcode secrets
  • Detect jailbroken devices - For high-security apps
// Jailbreak detection (basic)
func isJailbroken() -> Bool {
    #if targetEnvironment(simulator)
    return false
    #else
    let paths = [
        "/Applications/Cydia.app",
        "/Library/MobileSubstrate/MobileSubstrate.dylib",
        "/bin/bash",
        "/usr/sbin/sshd",
        "/etc/apt"
    ]

    for path in paths {
        if FileManager.default.fileExists(atPath: path) {
            return true
        }
    }

    // Check if app can write outside sandbox
    let testPath = "/private/jailbreak_test.txt"
    do {
        try "test".write(toFile: testPath, atomically: true, encoding: .utf8)
        try FileManager.default.removeItem(atPath: testPath)
        return true
    } catch {
        return false
    }
    #endif
}

Secure Coding Practices

  • Validate all input data
  • Use parameterized queries for databases
  • Sanitize data before displaying in web views
  • Avoid using NSLog for sensitive data in production
  • Implement proper error handling without exposing details

5. Protecting Sensitive UI

Prevent Screenshots of Sensitive Data

// Blur content when app goes to background
class AppDelegate: UIResponder, UIApplicationDelegate {
    var blurView: UIVisualEffectView?

    func applicationWillResignActive(_ application: UIApplication) {
        let blur = UIBlurEffect(style: .light)
        blurView = UIVisualEffectView(effect: blur)
        blurView?.frame = UIScreen.main.bounds
        UIApplication.shared.windows.first?.addSubview(blurView!)
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        blurView?.removeFromSuperview()
        blurView = nil
    }
}

Secure Text Fields

// Disable autocomplete for sensitive fields
textField.textContentType = .oneTimeCode // Prevents keyboard caching
textField.isSecureTextEntry = true
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.spellCheckingType = .no

6. Runtime Protection

Detect Debugging

func isDebuggerAttached() -> Bool {
    var info = kinfo_proc()
    var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
    var size = MemoryLayout<kinfo_proc>.stride

    let result = sysctl(&mib, UInt32(mib.count), &info, &size, nil, 0)

    if result != 0 {
        return false
    }

    return (info.kp_proc.p_flag & P_TRACED) != 0
}

Secure Memory Handling

// Clear sensitive data from memory
extension Data {
    mutating func secureZero() {
        guard !isEmpty else { return }
        withUnsafeMutableBytes { bytes in
            memset_s(bytes.baseAddress!, bytes.count, 0, bytes.count)
        }
    }
}

// Usage
var sensitiveData = "password".data(using: .utf8)!
// ... use the data
sensitiveData.secureZero() // Clear from memory

7. Third-Party Library Security

  • Audit all dependencies for known vulnerabilities
  • Keep dependencies updated
  • Use Swift Package Manager or CocoaPods with locked versions
  • Review library permissions and network calls
  • Avoid libraries that are no longer maintained

8. OWASP Mobile Top 10

Protect against the most common mobile vulnerabilities:

VulnerabilityProtection
Improper Platform UsageUse iOS security features (Keychain, ATS, biometrics)
Insecure Data StorageEncrypt sensitive data, use Keychain
Insecure CommunicationHTTPS + certificate pinning
Insecure AuthenticationOAuth 2.0, biometrics, secure token storage
Insufficient CryptographyUse Apple's CryptoKit, avoid custom crypto
Insecure AuthorizationServer-side validation, principle of least privilege
Client Code QualityInput validation, secure coding practices
Code TamperingJailbreak detection, code signing validation
Reverse EngineeringObfuscation, no hardcoded secrets
Extraneous FunctionalityRemove debug code, disable logging in production

Security Checklist

Pre-Release Security Audit

  • All sensitive data stored in Keychain
  • No hardcoded API keys or secrets
  • HTTPS enforced with certificate pinning
  • Biometric authentication implemented correctly
  • Debug logging disabled in release builds
  • Input validation on all user data
  • Sensitive screens protected from screenshots
  • All third-party libraries audited
  • Jailbreak detection (if required)
  • Privacy manifest (PrivacyInfo.xcprivacy) completed

Need a Security Audit for Your iOS App?

I can review your app's security implementation and identify vulnerabilities before they become problems.

Request Security Review

Harjot Singh Panesar

Harjot Singh Panesar

iOS, Android and web developer with 10+ years and 50+ shipped projects. I build in Swift, SwiftUI, Kotlin and Flutter, ship websites and web apps in any stack, and write about what actually happens in production.

I work with startups in the United States, United Kingdom and Canada, in their working hours.

Have an app or
a site to build?

Tell me what you are building and I will come back with a plan, a timeline and the trade-offs — usually within one business day.

Start a project
Based inMohali, Punjab, India
AvailableFreelance, consulting, long-term