WWDC Quick Look 💓 By SwiftGGTeam
Ready, set, relay: Protect app traffic with network relays

Ready, set, relay: Protect app traffic with network relays

Watch original video

Highlight

Apple provides a new set of Network Relay APIs in iOS 17 and macOS Sonoma. Developers can use MASQUE and Oblivious HTTP to add a privacy layer to app network traffic, and enterprise administrators can use relays as a lighter alternative to traditional VPNs for secure employee access to internal resources.


Core Content

The problem: why VPN is not the best answer for every scenario

For developers who need to protect network traffic, traditional options are limited. You either do nothing and expose the user’s IP address to servers, or you route through a VPN—but VPNs have several inherent issues:

  1. Scope is too broad: VPNs typically take over all traffic on the device; apps cannot enable protection only for the requests they care about.
  2. Poor user experience: VPN connections are slow, require reconnection when switching networks, and users must toggle them manually.
  3. Suboptimal trust model: VPN providers can see the user’s real IP and destination addresses—effectively handing privacy to a middleman.

For enterprise scenarios, VPN configuration and distribution is also a burden for IT teams. Employees must install VPN clients, enter configuration, and handle disconnect/reconnect.

Apple’s approach: network relays

Apple introduced Network Relay support in iOS 17 / macOS Sonoma. A relay is a special type of proxy server built on two IETF-standardized protocols:

  • MASQUE: Proxies arbitrary TCP and UDP connections without modifying backend servers.
  • Oblivious HTTP: Designed specifically for HTTP traffic, suited to REST API scenarios.

The core advantage of relays is that they can be chained. You can configure two relay servers: the first knows the user’s IP but not the content (which is encrypted), and the second knows the content but not the user’s IP. No single party gets both. This is the technology behind iCloud Private Relay.

For developers, this means you can configure relays for your app alone without affecting other traffic on the device. For enterprises, relays are lighter than VPNs—faster to connect, proxy only specified domains, and integrate with existing identity providers.


Detailed Content

Configuring a single relay in your app

Configuring a relay takes only a few lines of code. Create a ProxyConfiguration object in the Network framework (04:52):

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

Key points:

  • NWEndpoint.url accepts the relay server URL; the protocol must be HTTPS
  • RelayHop represents one hop in a relay chain; http3RelayEndpoint specifies HTTP/3 for communication with the relay
  • ProxyConfiguration’s relayHops is an array supporting single-hop or multi-hop (chain) setups
  • The code above only creates the configuration object; it has not yet been applied to any connection

Three integration paths: Network framework, URLSession, WebKit

Relay configuration can be injected into Apple’s three main networking APIs.

Option 1: Network framework (05:40)

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

var context = NWParameters.PrivacyContext(description: "my relay")
context.proxyConfigurations = [relayConfig]

let parameters = NWParameters.tls
parameters.setPrivacyContext(context)

let connection = NWConnection(host: "www.example.com", port: 443, using: parameters)
connection.start(queue: .main)

Key points:

  • NWParameters.PrivacyContext is a new type in iOS 17 / macOS Sonoma for encapsulating privacy configuration
  • proxyConfigurations accepts an array of ProxyConfiguration
  • setPrivacyContext binds the privacy context to connection parameters
  • Suitable for scenarios needing low-level network control, such as custom protocols or non-HTTP traffic

Option 2: URLSession (06:07)

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

let config = URLSessionConfiguration.default
config.proxyConfigurations = [relayConfig]

let mySession = URLSession(configuration: config)
let url = URL(string: "https://www.example.com/api/v1/employees")!
let (data, response) = try await mySession.data(from: url)

Key points:

  • URLSessionConfiguration adds a new proxyConfigurations property
  • Once configured, all requests on that URLSession route through the relay
  • Uses Swift concurrency’s async/await API to fetch data
  • The server only sees the relay’s IP address, not the user’s real IP

Option 3: WebKit (06:30)

import Network

let relayEndpoint = NWEndpoint.url(URL(string: "https://relay.example.com")!)
let relayServer = ProxyConfiguration.RelayHop(http3RelayEndpoint: relayEndpoint)

let relayConfig = ProxyConfiguration(relayHops: [relayServer])

let webkitConfig = WKWebViewConfiguration()
webkitConfig.websiteDataStore = WKWebsiteDataStore.nonPersistent()
webkitConfig.websiteDataStore.proxyConfigurations = [relayConfig]
let webView = WKWebView(frame: .zero, configuration: webkitConfig)

let url = URL(string: "https://www.example.com/api/v1/employees")!
webView.load(URLRequest(url: url))

Key points:

  • WKWebsiteDataStore adds a new proxyConfigurations property
  • Uses nonPersistent() data store, meaning no cookies or cache are retained, enhancing privacy
  • All network requests in the WebView go through the relay
  • Suitable for embedded browser scenarios, such as OAuth login flows

Enterprise: deploying relays via configuration profiles

Enterprises do not need employees to configure relays manually. Administrators can deploy configuration profiles through MDM (Mobile Device Management) (09:15):

<dict>
    <key>PayloadType</key>
    <string>com.apple.relay.managed</string>
    <key>Relays</key>
    <array>
        <dict>
            <key>HTTP3RelayURL</key>
            <string>https://relay.example.com</string>
            <key>PayloadCertificateUUID</key>
            <string>5AB702EC-32F3-48A9-94FE-8EA1C67ACF46</string>
        </dict>
    </array>
    <key>MatchDomains</key>
    <array>
        <string>internal.example.com</string>
    </array>
</dict>

Key points:

  • PayloadType is com.apple.relay.managed, a new configuration profile type
  • HTTP3RelayURL specifies the relay server address
  • PayloadCertificateUUID references a certificate defined in the same profile for TLS verification
  • MatchDomains limits the relay to proxying traffic for specified domains only; other traffic is unaffected
  • This is a major improvement over VPN: only proxy the traffic you need, not everything

Enterprise: programmatic configuration via NetworkExtension

If an enterprise has its own management app, relays can also be configured programmatically through the NetworkExtension framework (09:42):

import NetworkExtension

let newRelay = NERelay()
let relayURL = URL(string: "https://relay.example.com:443/")
newRelay.http3RelayURL = relayURL
newRelay.http2RelayURL = relayURL

newRelay.additionalHTTPHeaderFields = ["Authorization" : "PrivateToken=123"]

let manager = NERelayManager.shared()
manager.relays = [newRelay]
manager.matchDomains = ["internal.example.com"]

manager.isEnabled = true
do {
    try await manager.saveToPreferences()
} catch let saveError {
    // Handle error
}

Key points:

  • NERelay is a new class in NetworkExtension representing a relay configuration
  • Supports both HTTP/3 and HTTP/2 as fallback
  • additionalHTTPHeaderFields can add custom HTTP headers for authentication (e.g., an Authorization token)
  • NERelayManager.shared() is a singleton managing system-wide relay configuration
  • matchDomains limits relay scope to specified domains only
  • saveToPreferences() persists configuration to the system and requires user authorization

Core Takeaways

  1. Add network privacy protection for health/finance apps: If your app handles sensitive data (health records, financial information, location data), configure a relay via URLSessionConfiguration.proxyConfigurations so servers cannot obtain the user’s real IP. Why it’s worth doing: privacy regulations (GDPR, CCPA) impose strict requirements on data collection and processing; hiding IP is an effective way to reduce compliance risk. How to start: choose a relay provider supporting MASQUE, set proxyConfigurations on URLSessionConfiguration, then verify server logs only show the relay IP.

  2. Replace enterprise VPN for internal API access: When enterprise apps need intranet resources, employees no longer need to install VPN clients. Deploy a com.apple.relay.managed configuration profile via MDM; the relay proxies only domains listed in MatchDomains. Why it’s worth doing: VPNs are slow, require manual reconnect on drop, and interfere with non-enterprise traffic—relays avoid these pain points. How to start: set up a MASQUE-capable relay server, create a configuration profile, push it via MDM, then use via NERelayManager or URLSession.

  3. Use a two-hop relay architecture for stronger privacy: Configure two RelayHop entries in the relayHops array—the first knows the user IP but not content, the second knows content but not user IP. Why it’s worth doing: with a single relay, the provider knows both who you are and what you access. Two-hop architecture ensures no single entity can link both. How to start: choose two different relay providers, create two RelayHop objects, and pass them to ProxyConfiguration(relayHops: [hop1, hop2]).

  4. Add network isolation for WebView content: If your app embeds a WebView showing third-party content, use WKWebsiteDataStore.nonPersistent() with proxyConfigurations so WebView requests route through a relay without retaining local data. Why it’s worth doing: embedded browsers are a major privacy leak—third-party scripts and trackers can obtain user information through the WebView. How to start: create a WKWebViewConfiguration, set websiteDataStore to nonPersistent() and configure proxyConfigurations.

  5. Implement passwordless authentication with custom HTTP headers: Add an Authorization header via NERelay.additionalHTTPHeaderFields; the relay server can verify request origin without requiring a password. Why it’s worth doing: traditional VPN access to internal resources often requires extra username/password steps. Custom HTTP headers can integrate with SSO for seamless authentication. How to start: generate an API token in the enterprise IdP, pass it through additionalHTTPHeaderFields, and verify the token on the relay server.


Comments

GitHub Issues · utterances