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:
2
Answers
The problem is that the code in
MyApp
runs once the app opens similar todidFinishLaunchingWithOptions
. To fix this, create a newView
& place the following code in it:Then replace the content of
WindowGroup
with theView
you created.Edit:
It was the function
requestAuthorisation
giving an error related tocontext
. You should create a new context every time you call that function:create a new LAContext variable each time you want to authenticate, don’t use the same global variable. Since the LAContext needs one successful authentication alone.