AI News Hub Logo

AI News Hub

🔐 SSL Pinning in Mobile Apps: Android & iOS (Practical Guide + Trade-offs) - Part 2

DEV Community
Armando Picón

Unlike Android, where libraries like OkHttp abstract much of the complexity, iOS takes a more low-level approach to networking and security. This means one thing: You have more control — but also more responsibility. In this second part, we’ll explore how SSL pinning is implemented in iOS using two different strategies: Certificate Pinning (.cer) Public Key Pinning (recommended for production) Both approaches achieve the same goal — trusting only your backend — but they differ significantly in terms of stability, maintainability, and real-world viability. We’ll also take a step back and look at the bigger picture: When pinning makes sense When it becomes a liability And how it fits into a broader mobile security strategy Let’s dive in. iOS is more low-level. You’ll work with: URLSession URLSessionDelegate Security.framework There are two approaches: This is what your teammate probably mentioned. Export your backend certificate as .cer Add it to your Xcode project Compare it at runtime class PinningDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, let serverTrust = challenge.protectionSpace.serverTrust, let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { completionHandler(.cancelAuthenticationChallenge, nil) return } let serverCertData = SecCertificateCopyData(serverCertificate) as Data guard let localCertPath = Bundle.main.path(forResource: "server", ofType: "cer"), let localCertData = try? Data(contentsOf: URL(fileURLWithPath: localCertPath)) else { completionHandler(.cancelAuthenticationChallenge, nil) return } if serverCertData == localCertData { completionHandler(.useCredential, URLCredential(trust: serverTrust)) } else { completionHandler(.cancelAuthenticationChallenge, nil) } } } This approach is: 💣 Fragile Certificates expire Renewal changes .cer Your app breaks 👉 You must release a new version of the app Instead of comparing full certificates: 👉 Compare public keys ✔️ Advantages Survives certificate renewal More stable in production Equivalent to Android approach 🧪 Conceptual Example let serverPublicKey = SecCertificateCopyKey(serverCertificate) let localPublicKey = SecCertificateCopyKey(localCertificate) // Compare keys or their hashes // ⚠️ Apple APIs here are verbose and require careful handling. Instead of manual implementation, use: Alamofire (widely used networking library) Example let evaluators: [String: ServerTrustEvaluating] = [ "api.yourservice.com": PinnedCertificatesTrustEvaluator() ] let manager = ServerTrustManager(evaluators: evaluators) let session = Session(serverTrustManager: manager) Let’s be clear: Feature Covered by Pinning Encrypt traffic ✔ (via TLS) Prevent MITM ✔ Authenticate user ❌ Protect API access ❌ Replace VPN ❌ 👉 You still need: JWT / OAuth API Gateway Rate limiting Backend security Before adding pinning, ask yourself: ❗ Operational cost Certificate rotation becomes risky You need fallback pins You need monitoring ❗ Release dependency A backend change can break clients instantly ❗ Debugging complexity Harder to inspect traffic (Charles Proxy, etc.) Use pinning if: You build fintech / healthcare apps You operate in hostile network environments You have strong DevOps practices Avoid (or delay) if: You’re building a typical consumer app You don’t control backend infrastructure Your team lacks experience with cert rotation Mobile App ↓ HTTPS (TLS) ↓ API Gateway ↓ Authentication (JWT / OAuth) ↓ Microservices Optional hardening: Certificate Pinning 🔐 WAF 🛡️ Rate limiting 🚦 “SSL pinning” is often mentioned casually, but: 👉 It’s not a silver bullet Used correctly, it adds a strong extra layer of defense. Used blindly, it becomes a production risk. If you’re working with Kotlin Multiplatform or shared logic, keep in mind: Pinning is platform-specific You’ll need separate implementations for Android and iOS