Tech & Engineering Blog

Automatic Interception on iOS Solution review

Introduction

HUMAN Bot Defender is a behavior-based bot management solution that protects your websites, mobile applications and APIs from automated attacks, safeguarding your online revenue, reducing the risk of data breaches and improving operational efficiency.

In the context of mobile applications, we have HUMAN Mobile SDK for iOS and Android platforms that is responsible for collecting user-behavior data for native mobile applications.

The main functionalities of the Mobile SDK are to:

  1. Provide headers for URL requests.
  2. Handle the response from the web service.
  3. Present a challenge when needed.

Until now, the functionalities mentioned above had to be handled manually when integrating with the Mobile SDK, thus creating many points of integration and burdening the deployment process of the Mobile SDK.

When I started to think about the new version, I had a vision on how to ease the usage of the Mobile SDK - “Just call the start function and we will do the rest.” The most important values that we get from this direction are:

  1. The main flow will be under the Mobile SDK instead of being in the host app.
  2. Integration will be much easier and less error prone.

In order to do this, we had to figure out how to inspect and manipulate URL requests that are not under the Mobile SDK scope.

Technical overview

Swizzling. For those of you that are not familiar with this concept, Method Swizzling is the process of changing the implementation of an existing selector. It’s a technique made possible by the fact that method invocations in Objective-C can be changed at runtime, by changing how selectors are mapped to underlying functions in a class’s dispatch table.

OK, but how does Method Swizzling help us to intercept all URL requests? URLSessionConfiguration’s default object has a variable called protocolClasses. This array contains all URL Protocol classes that are available in the app. The system iterates through them to find the first protocol that can handle the upcoming URL request. We need to make the system ask for our URL Protocol first, before all other URL protocols.

So, what do we do? It’s simple, we just need to “swizzle” the implementation of protocolClasses. The new implementation for the protocolClasses getter should get the original array and then insert our URL Protocol class to the array at index 0. This way we ensure that every time the OS is asking for URLProtocol we will be the first to raise our hand 👋.

This solution has the following advantages:

  1. No code is needed to enable the interception behavior.
  2. It’s easy to say “no” to the OS when we don’t want to intercept the URL request, and by that preserve the original behavior.
  3. This solution works great with all network libraries that are based on URLSession, like Alamofire.

 

Solution description

Let’s talk business. Here is our code.

The swizzleProtocolClasses function swizzles the original implementation of the protocolClassess’s getter with our getter.

func swizzleProtocolClasses() {
    let instance = URLSessionConfiguration.default
    let urlSessionConfigurationClass: AnyClass = object_getClass(instance)!

    let method1: Method = class_getInstanceMethod(urlSessionConfigurationClass, #selector(getter: urlSessionConfigurationClass.protocolClasses))!
    let method2: Method = class_getInstanceMethod(URLSessionConfiguration.self, #selector(URLSessionConfiguration.swizzle_protocolClasses))!

    method_exchangeImplementations(method1, method2)
}

In our custom getter, we first get the original array. But wait, there is a bug there, right? We call the same function again.. 🤔. But no, we don’t. Remember that we “swizzled” the implementation between the original getter and our own getter? That means that calling our own function directly will actually run the original function. It may come off as confusing, but it’s actually quite simple. It’s just switching the implementation between two functions.

Next, we check if the original array contains our own URL Protocol class. If not, we add it.

@objc func swizzle_protocolClasses() -> [AnyClass]? {
    var originalProtocolClasses = swizzle_protocolClasses()
    if let doesContain = originalProtocolClasses?.contains(where: { protocolClass in
        return protocolClass == PXURLRequestsInterceptor.self
    }), !doesContain {
      originalProtocolClasses?.insert(PXURLRequestsInterceptor.self, at: 0)
    }
    return originalProtocolClasses
}

And that’s it!

Takeaway

The Method Swizzling is a very powerful technique for manipulating behaviors in the system. In this case, it enabled us to validate that the system will prioritize our URL Protocol when handling the request. However, with great power comes great responsibility: changing the system behavior in runtime might work now, but could break in future OS releases.

Using Method Swizzling made the process of integrating our Mobile SDK more efficient. By automating the request handling and significantly reducing the points of integration with the customer application, it allows our customers to integrate with the HUMAN Mobile SDK with just a single line of code 💥.

And what about Android? Well, Method Swizzling is not available in Java/Kotlin, but we have the same solution by providing an interceptor object that can be added to the request’s chain, which is available in the OkHttp library.