As mobile security becomes increasingly critical for enterprise applications, I recently explored whether it's possible to restrict iOS app usage to specific network locations. This blog post documents my journey creating a proof-of-concept app called "LockedApp" that validates corporate network connections before allowing access.
The Challenge
The requirement was straightforward: ensure that our internal iOS applications can only be used when employees are connected to the corporate network or VPN. Simply checking if a VPN client like Twingate is installed isn't sufficient—the app needs to verify that the VPN is actively routing traffic through corporate infrastructure.
The solution I developed checks the device's external IP address and validates it against predefined corporate IP ranges. If the device isn't routing through approved networks, the app blocks access with a clear error message.
Visual Results
These visuals assume you are using iOS 26 as that is what I have been testing with, First, let’s look at the error that happens if you’re not using the right VPN connection, if you click retry until you are using the correct details, the box will continue to reappear:
Creating the Test Application
I started by creating a new SwiftUI project in Xcode to test this concept. Here's how I built the foundation:
Step 1: Project Setup
First, I created a new iOS project in Xcode:
- Product Name: LockedApp
- Interface: SwiftUI
- Language: Swift
Step 2: Building the Welcome Screen
I designed a professional welcome screen that would serve as the main interface. The app features A6N Networks branding with smooth animations and corporate styling.
The main app structure in LockedAppApp.swift
:
@main
struct LockedAppApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
var body: some Scene {
WindowGroup {
ContentView()
.preferredColorScheme(.dark)
}
}
}
The welcome screen includes animated elements, status indicators, and company branding, all wrapped in a ScrollView
to ensure compatibility across different device sizes.
Step 3: Implementing Network Validation
The core functionality resides in a dedicated CorporateNetworkValidator.swift
file. This class handles the IP validation logic:
class CorporateNetworkValidator {
private let corporateIPRanges = [
"203.0.113", // Example corporate range
"198.51.100", // Another corporate range
"10.0.0", // Internal corporate range
"172.16" // Additional corporate range
]
func validateCorporateConnection() async -> Bool {
guard let publicIP = await getPublicIPAddress() else {
return false
}
return isIPInCorporateRange(ip: publicIP)
}
}
The validator uses the ipify.org API to retrieve the device's external IP address:
private func getPublicIPAddress() async -> String? {
do {
let url = URL(string: "https://api.ipify.org")!
let (data, _) = try await URLSession.shared.data(from: url)
return String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
} catch {
print("Failed to get IP address: \(error)")
return nil
}
}
Step 4: Integrating Access Control
The AppDelegate
class orchestrates the network validation:
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
checkCorporateNetworkConnection()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
checkCorporateNetworkConnection()
}
private func checkCorporateNetworkConnection() {
Task {
let validator = CorporateNetworkValidator()
let isOnCorporateNetwork = await validator.validateCorporateConnection()
await MainActor.run {
if !isOnCorporateNetwork {
showCorporateConnectionError()
}
}
}
}
}
Configuration Requirements
The most critical aspect of implementation is correctly configuring your corporate IP ranges. To determine your organization's external IP addresses:
- Connect to your corporate network or VPN
- Visit
https://api.ipify.org
in a browser - Note the displayed IP address
- Update the
corporateIPRanges
array with the appropriate prefixes
For example, if your corporate IP is 203.145.67.123
, you would add "203.145.67"
to the validation array.
Testing the Implementation
During testing, I verified several scenarios:
- Corporate WiFi: App launches normally when connected to office network
- Home WiFi: App blocks access and displays error message
- Cellular Data: App blocks access unless corporate VPN is active
- VPN Connection: App allows access when Twingate or corporate VPN routes traffic
The validation occurs both at app launch and when the app becomes active, ensuring continuous compliance.
Adding to Existing Applications
Once I validated the concept with LockedApp, I developed a method to integrate this functionality into existing iOS applications without disrupting current code.
For New Integration
Create the CorporateNetworkValidator.swift
file in your existing project with the complete implementation:
import Foundation
import UIKit
class CorporateNetworkValidator {
// REPLACE THESE WITH YOUR ACTUAL CORPORATE IP RANGES
private let corporateIPRanges = [
"203.0.113", // Example: 203.0.113.x
"198.51.100", // Example: 198.51.100.x
"10.0.0", // Example: 10.0.0.x
"172.16" // Example: 172.16.x.x
]
static func validateAndBlockIfNeeded() {
let validator = CorporateNetworkValidator()
Task {
let isOnCorporateNetwork = await validator.validateCorporateConnection()
await MainActor.run {
if !isOnCorporateNetwork {
validator.showCorporateConnectionError()
}
}
}
}
func validateCorporateConnection() async -> Bool {
guard let publicIP = await getPublicIPAddress() else {
return false
}
return isIPInCorporateRange(ip: publicIP)
}
private func getPublicIPAddress() async -> String? {
do {
let url = URL(string: "https://api.ipify.org")!
let (data, _) = try await URLSession.shared.data(from: url)
return String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
} catch {
print("Failed to get IP address: \(error)")
return nil
}
}
private func isIPInCorporateRange(ip: String) -> Bool {
for range in corporateIPRanges {
if ip.hasPrefix(range) {
return true
}
}
return false
}
private func showCorporateConnectionError() {
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first,
let rootViewController = window.rootViewController else { return }
let alert = UIAlertController(
title: "Corporate Connection Required",
message: "Unfortunately, you are not using your company's corporate connection. Please connect to the corporate VPN.",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Retry", style: .default) { _ in
CorporateNetworkValidator.validateAndBlockIfNeeded()
})
alert.addAction(UIAlertAction(title: "Exit", style: .destructive) { _ in
exit(0)
})
rootViewController.present(alert, animated: true)
}
}
The validator includes a static method for easy integration and contains the complete IP validation logic.
Remember to update the corporateIPRanges
array with your actual corporate IP prefixes.
UIKit Integration
For existing UIKit applications, add these lines to your AppDelegate.swift
:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Your existing code remains unchanged
// Add this single line
CorporateNetworkValidator.validateAndBlockIfNeeded()
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Your existing code remains unchanged
// Add this single line
CorporateNetworkValidator.validateAndBlockIfNeeded()
}
SwiftUI Integration
For SwiftUI applications, modify your main App struct:
@main
struct YourExistingApp: App {
var body: some Scene {
WindowGroup {
YourExistingContentView()
.onAppear {
CorporateNetworkValidator.validateAndBlockIfNeeded()
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
CorporateNetworkValidator.validateAndBlockIfNeeded()
}
}
}
}
Security Considerations
While this approach provides effective access control for typical use cases, it's important to understand its limitations:
- VPN Spoofing: Technically sophisticated users could potentially spoof IP addresses
- Network Dependencies: The validation requires internet connectivity to function
For most enterprise scenarios, these limitations are acceptable given the significant security improvement this provides over unrestricted access.
Conclusion
The LockedApp project successfully demonstrated that iOS applications can effectively validate corporate network connections through external IP verification. This approach provides a practical security layer for enterprise applications while maintaining a smooth user experience for authorized users.
The implementation is straightforward to integrate into existing applications and requires minimal code changes. By validating network connectivity at both app launch and when returning from background, the solution ensures continuous compliance with corporate security policies.