Swift By Rahul

App Transport Security Best Practices on iOS

In the world of mobile app development, security is paramount. Users trust our apps with their data, and it's our responsibility to protect it. A critical component of this security on Apple platforms is App Transport Security (ATS). Introduced in iOS 9, ATS significantly enhances user privacy and data integrity by enforcing secure network connections by default.

But what exactly is ATS, and how does it impact your app? More importantly, what are the best practices for working with it, especially when dealing with legacy systems that might not yet meet modern security standards? This article will dive deep into ATS, providing you with the knowledge and practical steps to ensure your app's network communications are secure and compliant.

App Transport Security (ATS) Flow Your App ATS (Security Gate) Secure Server Insecure Server Network Request Allowed Blocked

Understanding App Transport Security (ATS)

At its core, ATS is a set of policies that dictate how your app can connect to web services. Since iOS 9, watchOS 2, tvOS 9, and macOS 10.11, ATS has been enabled by default for all new apps. This means that all HTTP connections made by your app must conform to specific security requirements.

Specifically, ATS requires:

  1. HTTPS (TLS 1.2 or higher): All connections must use HTTPS. This encrypts data in transit, preventing eavesdropping and tampering.
  2. Forward Secrecy: The server must support Forward Secrecy, which ensures that even if a server's long-term private key is compromised, past communications remain secure.
  3. Strong Cipher Suites: Only modern, strong cryptographic cipher suites are allowed.
  4. Trusted Certificates: Server certificates must be signed by a trusted Certificate Authority (CA) and meet specific size requirements (e.g., RSA 2048-bit or greater, ECDSA 256-bit or greater).

If your app attempts to connect to a server that doesn't meet these criteria, the connection will fail, resulting in an error similar to App Transport Security has blocked a cleartext HTTP (http://) resource load because it is insecure.. This aggressive stance by Apple is a clear signal: secure your network communications.

The Default ATS Behavior

By default, any network request originating from your app using URLSession (or other higher-level APIs like WebKit) will be subject to ATS scrutiny. If the destination server does not adhere to the strict ATS requirements, the connection will be blocked. This is a good thing for security, as it protects users from potential Man-in-the-Middle (MitM) attacks, data interception, and other vulnerabilities associated with insecure HTTP connections.

For most modern applications that communicate with well-maintained APIs, ATS should not be an issue. However, challenges arise when integrating with older, third-party services, legacy internal APIs, or content delivery networks (CDNs) that might not yet have fully adopted modern security standards.

When and How to Bypass ATS (Carefully!)

While Apple strongly discourages disabling ATS, they provide mechanisms to create exceptions when absolutely necessary. These exceptions are configured in your app's Info.plist file, under the NSAppTransportSecurity dictionary. It's crucial to understand that bypassing ATS should be a last resort and done with extreme caution, always aiming to secure the backend service itself rather than weakening your app's security posture.

Here are the primary ways to configure ATS exceptions, from least to most recommended:

1. NSAllowsArbitraryLoads (The "Nuclear Option")

This boolean key, when set to YES, completely disables ATS for all domains. This is highly discouraged by Apple and should never be used in production apps. It opens up your app and users to significant security risks, allowing connections over insecure HTTP and to servers with weak TLS configurations.

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/> <!-- AVOID THIS IN PRODUCTION! -->
</dict>

If you ever find this key set to true in your Info.plist, you should make it a top priority to remove it and adopt more granular exceptions or, ideally, migrate your backend to secure HTTPS.

2. NSAllowsArbitraryLoadsForMedia

This key is a slightly less dangerous version of NSAllowsArbitraryLoads, allowing arbitrary loads specifically for media content (e.g., video and audio streams). It's intended for scenarios where you need to play media from various sources, some of which might be insecure, but your core data communications remain protected by ATS.

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoadsForMedia</key>
    <true/>
</dict>

While better than a full arbitrary load, still use this with caution and only for media content.

3. NSExceptionDomains (The Recommended Approach)

This is the preferred and most secure way to bypass ATS for specific domains. NSExceptionDomains is a dictionary where each key is a domain name (e.g., api.legacy.com), and its value is another dictionary containing specific exception rules for that domain. This allows you to apply exceptions only where they are strictly necessary, keeping the rest of your app's connections secure.

Here are the common keys you can use within an NSExceptionDomains entry:

  • NSExceptionAllowsInsecureHTTPLoads (Boolean): Set to true if the domain serves content over plain HTTP. This is often the primary reason for needing an exception.
  • NSExceptionMinimumTLSVersion (String): Specifies the minimum TLS version required for this domain (e.g., TLSv1.0, TLSv1.1, TLSv1.2). Use this if a server cannot meet TLS 1.2.
  • NSExceptionRequiresForwardSecrecy (Boolean): Set to false if the domain does not support Forward Secrecy. By default, even exceptions require Forward Secrecy.
  • NSIncludesSubdomains (Boolean): Set to true if the exception should also apply to all subdomains of the specified domain (e.g., sub.api.legacy.com).
ATS Exception Strategies Comparison NSAllowsArbitraryLoads "The Nuclear Option" Disables ATS for ALL domains. High security risk. Avoid in production. NSExceptionDomains "Granular Control" Disables ATS for SPECIFIC domains. Lower, controlled risk. Recommended for exceptions.

Practical Example: Configuring NSExceptionDomains

Let's say your app needs to connect to http://legacy.api.example.com which only supports HTTP and an older TLS version (e.g., TLS 1.0) without Forward Secrecy, and also https://another.api.example.com which is HTTPS but also lacks Forward Secrecy.

Your Info.plist would look something like this:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>legacy.api.example.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSExceptionMinimumTLSVersion</key>
            <string>TLSv1.0</string>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
        </dict>
        <key>another.api.example.com</key>
        <dict>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSIncludesSubdomains</key>
            <true/> <!-- Applies to any.another.api.example.com too -->
        </dict>
    </dict>
</dict>

With this configuration, only legacy.api.example.com and another.api.example.com (and its subdomains) will have relaxed ATS rules, while all other network connections in your app will still enforce the strict default ATS requirements.

Swift Code Example for Network Request

Here's a simple URLSession example that demonstrates how ATS might affect your requests. If you try to access an http:// URL without proper NSExceptionDomains setup, it will fail.

import Foundation

func makeNetworkRequest(to urlString: String) async {
    guard let url = URL(string: urlString) else {
        print("Invalid URL: \(urlString)")
        return
    }

    print("Attempting to connect to: \(urlString)")

    do {
        let (data, response) = try await URLSession.shared.data(from: url)
        guard let httpResponse = response as? HTTPURLResponse else {
            print("Not an HTTP response for \(urlString).")
            return
        }

        if (200...299).contains(httpResponse.statusCode) {
            print("✅ Successfully fetched data from \(urlString). Status Code: \(httpResponse.statusCode)")
            // Optionally print data:
            // print("Data: \(String(data: data, encoding: .utf8) ?? "N/A")")
        } else {
            print("❌ Request failed for \(urlString). Status Code: \(httpResponse.statusCode)")
            print("Response headers: \(httpResponse.allHeaderFields)")
        }
    } catch {
        print("❌ Network request failed for \(urlString) with error: \(error.localizedDescription)")
        if let urlError = error as? URLError, urlError.code == .appTransportSecurityRequiresSecureConnection {
            print("💡 This error is likely due to App Transport Security (ATS) blocking an insecure connection.")
            print("   Consider updating your server to HTTPS or configuring ATS exceptions carefully in Info.plist.")
        }
    }
}

// To run this, you'd typically call it within a Task in an async context,
// for example, from a button action or in an `onAppear` modifier in SwiftUI.
// For demonstration, let's simulate calls:

/*
// Example Usage:
Task {
    // This will likely succeed if the domain is secure and ATS compliant
    await makeNetworkRequest(to: "https://api.github.com/zen")

    // This will likely fail due to ATS if 'insecure.example.com' is HTTP
    // and not configured in NSExceptionDomains
    await makeNetworkRequest(to: "http://insecure.example.com/data")

    // If 'legacy.api.example.com' is configured in NSExceptionDomains
    // as shown above, this might succeed even if it's HTTP.
    await makeNetworkRequest(to: "http://legacy.api.example.com/status")
}
*/

Checking ATS Compliance with nscurl

Before even writing a single line of Swift code, you can test a URL's ATS compliance using the nscurl command-line tool. This tool simulates a URLSession request and reports any ATS violations.

Open your Terminal and run:

nscurl --ats-diagnostics https://api.example.com

Replace https://api.example.com with the actual URL you want to test. The output will detail which ATS requirements are met or failed for the given URL. Look for lines like ATS Dictionary: (null) (meaning no exceptions needed for that URL) or specific failures (Result : FAIL). This is an invaluable tool for debugging ATS issues.

Best Practices and Recommendations

  1. Prioritize Securing Your Backend: The absolute best practice is to ensure all your backend services and third-party APIs use HTTPS with TLS 1.2+, strong cipher suites, and Forward Secrecy. This eliminates the need for any ATS exceptions in your app.
  2. Avoid NSAllowsArbitraryLoads: Seriously, do not use this in production. It defeats the entire purpose of ATS and exposes your users to unnecessary risks.
  3. Use NSExceptionDomains Sparingly: If you must make an exception, use NSExceptionDomains to target only the specific, non-compliant domains. Be as granular as possible.
  4. Regularly Review Info.plist: Periodically audit your Info.plist for any ATS exceptions. As backend services are updated, you might be able to remove old exceptions, further strengthening your app's security.
  5. Educate Your Team: Ensure all developers working on your project understand the importance of ATS and the implications of bypassing it.
  6. Test Thoroughly: Always test network connections on real devices and simulators with various configurations (including and excluding your ATS exceptions) to catch any unexpected behavior.
Info.plist
└─ NSAppTransportSecurity (Dictionary)
   ├─ NSAllowsArbitraryLoads (Boolean, NO by default, AVOID!)
   └─ NSExceptionDomains (Dictionary)
      └─ my.legacy.api.com (Dictionary)
         ├─ NSExceptionAllowsInsecureHTTPLoads (Boolean, YES if HTTP needed)
         ├─ NSExceptionMinimumTLSVersion (String, e.g., TLSv1.0)
         ├─ NSExceptionRequiresForwardSecrecy (Boolean, NO if not supported)
         └─ NSIncludesSubdomains (Boolean, YES for subdomains)

Future of ATS

Apple's commitment to security is unwavering. While they haven't completely locked down ATS, their consistent encouragement to adopt secure connections suggests that future iOS versions might make it even harder to bypass ATS, possibly deprecating some of the exception keys. Staying ahead by securing your backend services is the most future-proof strategy.

Summary

App Transport Security is a fundamental security feature on Apple platforms that protects user data by enforcing secure network connections. While it can sometimes pose challenges when integrating with older services, understanding its mechanisms and adhering to best practices—primarily using NSExceptionDomains sparingly and striving for secure backends—will ensure your app remains robust and trustworthy. Prioritize security, and your users will thank you.

Happy Swifting!