How to Securely Log Users Out of iOS Apps with Inactivity Timeout

Written by dekij | Published 2023/05/14
Tech Story Tags: ios | swift | security | foundation | ios-app-development | ios-development | iosprogramming | hackernoon-top-story | hackernoon-es | hackernoon-hi | hackernoon-zh | hackernoon-vi | hackernoon-fr | hackernoon-pt | hackernoon-ja | hackernoon-tr | hackernoon-ko | hackernoon-de | hackernoon-bn

TLDRMany apps have a feature that automatically logs you out after a certain period of inactivity. Some apps log you out based on API inactivity, while others implement a logout timer by overriding the UIView controls. In this article, we will explore a better way to achieve this behavior using the hitTest method.via the TL;DR App

Many apps have a feature that automatically logs you out after a certain period of inactivity. However, some of these apps log you out based on API inactivity, while others implement a logout timer by overriding the UIView controls. In this article, we will explore a better way to achieve this behavior using the hitTest method. And some apps logging you out based on the time difference between last login / API call with the current time. The issue with the last approach is that you can keep your session constantly alive by manually setting your time on a device to the login time point.

The problem with the first approach is that some users may be logged out even if they are only scrolling through content and not making any requests to the backend. Imagine a user who reads terms and conditions of a certain banking product before opening and he is being logged out. We definitely want to avoid this.

The issue with the second approach is that it can be difficult to implement the inactivity logout feature in an existing codebase, especially if the app has a large codebase with custom views.

This is where the hitTest method can be very helpful.

Short answer

We can use a hack with the hitTest method in order to prevail of the listed issues.

Let’s assume that we already have an implemented version of a class that is responsible for tracking a user activity time.

We are going to express it with the following API:

protocol IUserActivity {
    func resetInactiveTime()
}

Now, let’s declare an extension of a UIWindow in our app:

extension UIWindow {
    open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        userActivity.resetInactiveTime()        
        return super.hitTest(point, with: event)
    }
}

And that’s it! Now this method will be triggered on every interactive event user makes on a screen such as taping, scrolling and other gesture activities.

You could also declare a subclass of a UIWindow and override a hitTest method in there.

Wait, how does that work?🤔 (Long answer)

Touch events in iOS typically originate from user interactions with the device's touch screen.

When the user touches the screen, the hardware detects the touch and generates a stream of raw touch data that includes information such as the location, pressure, and duration of the touch.

This raw touch data is then processed by the operating system and transformed into high-level touch events that can be handled by UIKit. The operating system determines which UIWindow object the touch event corresponds to based on the touch location and the view hierarchy, and then sends the touch event to that object's UIResponder object for processing.

When a touch event occurs, the iOS operating system uses a hit-testing algorithm to determine which UIView or UIWindow object the touch event corresponds to.

The hit-testing algorithm starts at the top-level UIWindow object and recursively checks each subview in the view hierarchy until it finds the deepest view that contains the touch point. It does this by checking whether the touch point is inside the frame of each subview.

The hit-testing uses a depth-first reverse pre-order traversal algorithm. In other words, the algorithm visits the root node first and then traverses its subtrees from higher to lower indexes.

This kind of traversal allows reducing the number of traversal iterations and stopping the search process once the first deepest descendant view that contains the touch-point is found.

This is possible since a subview is always rendered in front of its superview, and a sibling view is always rendered in front of its sibling views with a lower index into the subviews array. When multiple overlapping views contain a specific point, the deepest view in the rightmost subtree will be the frontmost view(1).

As we can see, the traverse algorithm always starts from the UIWindow object, no matter which view or control user interacting with.

And we definitely can use this for our purpose just by intercepting these events in hitTest method of UIWindow class!

That’s it! Let me know what you think in the comments below.


Written by dekij | Software Engineer Tech Lead / iOS Tech Lead
Published by HackerNoon on 2023/05/14