Home > Blockchain >  Biometrics prompt not appearing after first successful authentication
Biometrics prompt not appearing after first successful authentication

Time:07-20

I have a working example below, but a bit of an explanation.

I want the user to be able to toggle the option to unlock their app data with biometrics (or not if preferred). If they activate the toggle, once the app resigns to background or has been terminated the next time it is launched they should be prompted to log in.

This portion of the app functionality I have operational. However, once the user logs in once, resigns to background and then relaunches they are in instantly.

I altered the codebase so that the "permission" bool was set to false, however when the view to authenticate prompts them, there is none of the Apple biometrics, they are simply granted access.

I tried using the LAContext.invalidate but after adding that into the check when resigning of background the biometric prompts never reappear - unless fully terminated.

Am I missing something or how do other apps like banking create the prompt on every foreground instance?

// main.swift
@main
struct MyApp: App {
  @StateObject var biometricsVM = BiometricsViewModel()
  var body: some Scene {
    WindowGroup {
      // toggle for use
      if UserDefaults.shared.bool(forKey: .settingsBiometrics) {
        // app unlocked
        if biometricsVM.authorisationGranted {
          MyView() // <-- the app view itself
           .onAppear {
             NotificationCenter.default.addObserver(
               forName: UIApplication.willResignActiveNotification,
               object: nil,
               queue: .main
             ) { _ in
               biometricsVM.context.invalidate()
               biometricsVM.authorisationGranted = false
             }
           }
        } else {
          BioCheck(vm: biometricsVM)
        }
      }
    }
  }
}
// biometricsVM.swift
final class BiometricsViewModel: ObservableObject {
  @Published var authorisationGranted = false
  @Published var authorisationError: Error?

  let context = LAContext()

  func requestAuthorisation() {
    var error: NSError? = nil
    let hasBiometricsEnabled = context.canEvaluatePolicy(
      .deviceOwnerAuthentication, error: &error
    )

    let reason = "Unlock to gain access to your data"

    if hasBiometricsEnabled {
      switch context.biometryType {
        case .touchID, .faceID:
          context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { success, error in
            DispatchQueue.main.async {
              self.authorisationGranted = success
              self.authorisationError = error
            }
          }

         case .none:
          // other stuff 

        @unknown default:
          // other stuff 
      }
    }
  }
}
// biocheck.swift
struct BioCheck: View {
  @ObservedObject var vm: BiometricsViewModel
  var body: some View {
    Button {
     vm.requestAuthorisation()
    } label: {
     Text("Authenticate")
    }
    .onAppear { vm.requestAuthorisation() }
  }
}

Video of issue:

CodePudding user response:

The problem is that the code in MyApp runs once the app opens similar to didFinishLaunchingWithOptions. To fix this, create a new View & place the following code in it:

if UserDefaults.shared.bool(forKey: .settingsBiometrics) {
    if biometricsVM.authorisationGranted {
        MyView()
            .onAppear {
                NotificationCenter.default.addObserver(
                    forName: UIApplication.willResignActiveNotification,
                    object: nil,
                    queue: .main
                ) { _ in
                    biometricsVM.context.invalidate()
                    biometricsVM.authorisationGranted = false
                }
            }
    } else {
        BioCheck(vm: biometricsVM)
    }
}

Then replace the content of WindowGroup with the View you created.

Edit:

It was the function requestAuthorisation giving an error related to context. You should create a new context every time you call that function:

  func requestAuthorisation() {
    var error: NSError? = nil
    let context = LAContext()
    let hasBiometricsEnabled = context.canEvaluatePolicy(
        .deviceOwnerAuthentication, error: &error
    )
    
    let reason = "Unlock to gain access to your data"
    
    if hasBiometricsEnabled {
        switch context.biometryType {
        case .touchID, .faceID:
            context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { success, error in
                DispatchQueue.main.async {
                    self.authorisationGranted = success
                    self.authorisationError = error
                }
            }
            
        case .none:
            // other stuff
            break
        @unknown default:
            break
        }
    }
}
  • Related