Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

LocalAuthentication (Alternative) in Autofill Credential Provider extension

Hi, how can you authenticate a User through Biometrics with iPhone Passcode as Fallback in the Autofill Credential Provider Extension?

In the App it works without a problem. In the Extension I get "Caller is not running foreground" Yeah, it isn't, as it's just a sheet above e.g. Safari.

I'd like to avoid having the user setup a Passcode dedicated to my App, especially because FaceID is way faster.

Does anybody know how to achieve iOS native Auth in the extension? Please let me know, a code sample would be appreciated.

Regards, Mia

func authenticate(withPasscode: Bool = false) {
        let context = LAContext()
        var error: NSError?
        let reason = "You need to unlock to access your credentials"
        
        guard !withPasscode else {
            authenticateWithPasscode()
            return
        }
        // check whether biometric authentication is possible
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {

            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
                guard authenticationError == nil else {
                    tryCode = true
                    authenticateWithPasscode()
                    return
                }
                if success {
                    isUnlocked = true
                    tryCode = false
                } else {
                    tryCode = true
                    authenticateWithPasscode()
                }
            }
        } else {
            tryCode = true
            authenticateWithPasscode()
        }
        
        func authenticateWithPasscode() {
            context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { completed, authenticationError in
                if completed {
                    isUnlocked = true
                    tryCode = false
                }
            }
        }
    }

Thats the code I use on the App’s Side If I print authenticationError in the callback from the Biometric authentication in the Extension, I get “Caller is not running foreground"

I hope that addition may help

I don’t have a lot of insight into this from the Authentication Services side of this, but I can speak to the app extension and Local Authentication aspects of it.

The meaning of that error from Local Authentication is clear: That API only works in the foreground.

With the app extension architecture, a given extension type is either foreground capable or it’s not. For example, a Network Extension appex can never show UI. However, things get fuzzier for Authentication Services, where the appex can show UI in some situations but not others. I’ve seen this come up in other scenarios — an SSO extension trying to use the camera — and DTS wasn’t able to pull a workaround out of the fire. If this doesn’t work in the obvious way, my only advice is that you file a bug about it. Make sure to explain why you’re doing this and exactly what context you’re trying to use LA.

Please post your bug number, just for the record.

Share and Enjoy

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

Accepted Answer

I fixed it. I first added a delay through DispatchQueue.main.asyncAfter of .3 seconds. That fixed it while no debugger was attached. I was worried that on slower devices it still might fail, so I looked further. The problem seems to be, that .onAppear got called before it was fully in foreground. So LocalAuthentication was technically right that it wasn't at the time it was called.

I now use the following modifier

struct ViewVisibilityModifier: ViewModifier {
    let onAppeared: () -> Void
    
    func body(content: Content) -> some View {
        content
            .background(ViewVisibilityDetector(onAppeared: onAppeared))
    }
}

struct ViewVisibilityDetector: UIViewRepresentable {
    let onAppeared: () -> Void
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.onAppeared()
        }
    }
}

extension View {
    func onViewDidAppear(perform action: @escaping () -> Void) -> some View {
        self.modifier(ViewVisibilityModifier(onAppeared: action))
    }
}

on my SwiftUI View.

BlockingView()
    .onViewDidAppear {
        guard !isAuthenticated else { return }
        authenticate()
    }

Now it gets only called once it's fully open. Such it also works while Debugging. I hope that it now covers all cases from slow devices to slow debugging sessions.

LocalAuthentication (Alternative) in Autofill Credential Provider extension
 
 
Q