iOS 18.3.1 - App shows multiple Face ID checks issue when launched

Our app uses Face ID to optionally secure access to the app for device owner. This not the new 'Require Face ID' feature of iOS 18 - this is our own custom implementation that has some other related logic for authentication handling.

Starting in iOS 18.3.1, starting the app results in multiple Face Id checks being fired - sometimes just a couple but sometimes many more.

Curiously, this is happening even when I completely disable any code we have that prompts for Face ID. It appears to come from nowhere.

This does not happen on prior iOS 18 releases so, while I might be doing something improper in the code, something specific has changed in iOS 18.3.1 to cause this issue to manifest.

I'm looking for advice as to what could be occurring here, how to debug a Face Id check that appears to come from nowhere, and what, if any, workarounds exist.

Written by aidan-uc in 774934021
It appears to come from nowhere.

Well that’s weird. My advice is that, when you see the Face ID prompt, stop the app in the debug and look at all the thread backtraces. One of those might offer a hint as to what’s going on.

You can view the backtraces in Xcode but I often find it easier to dump a text representation using the LLDB console:

(lldb) thread backtrace all 
Written by aidan-uc in 774934021
Starting in iOS 18.3.1

Do you literally mean 18.3.1? That is, you observe this behaviour in 18.3.1 but not 18.3? Or something else?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for the response - that's helpful.

I've been able to drill down a little more and I now believe it's down to a Keychain change that I made which appears to have manifested itself after a device restart. The upgrade to 18.3.1 is likely a red herring - it's just that these days that's the only time many people do a restart.

FWIW, what I did was add access control attributes to further constrain some code that stores and retrieves token values from the keychain. The expectation was that this would a) only provide the token while the device was unlocked and b) avoid using an existing token when the biometry set was changed.

        var error: Unmanaged<CFError>?

        defer {
            error?.release()
        }
        
        return SecAccessControlCreateWithFlags(
            nil,
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly as CFString,
            [.biometryCurrentSet],
            &error
        )
    }

Previously, I was using:

kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,

in the attributes.

One some devices this seems to be causing a Face Id check to appear for each query for the current value. It's curious to me that it's not happening on all devices on which I test, though.

If it's inconsistently manifesting on fresh installs on different devices, while I wouldn't expect it given the 'this device only' directive, I still suspect it's possible there's some remnant data clouding the waters here due to further misunderstandings on my part.

I’d be very surprised if the keychain wasn’t enforcing this access control constraint. My best guess here is that your app is dealing with old keychain entries. Remember that, on current systems, deleting an app does not delete its keychain items [1]. The next time you install and run the app, it’ll find its items waiting for it in the keychain.

When I’m testing things like this I usually add a Reset button to my UI. Tapping that deletes all my keychain items so that I can start afresh.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Although that behaviour is not guaranteed. See this post for some old, but still relevant, backstory.

iOS 18.3.1 - App shows multiple Face ID checks issue when launched
 
 
Q