Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disable passcode entry completely #477

Closed
kipropkorir opened this issue Apr 28, 2020 · 12 comments
Closed

Disable passcode entry completely #477

kipropkorir opened this issue Apr 28, 2020 · 12 comments

Comments

@kipropkorir
Copy link

I have tried disabling by using policy authenticationPolicy: .biometryCurrentSet and authenticationPolicy: .biometryAny but after three failed attempts the app asks for a passcode. How do I remove passcode completely?

@kishikawakatsumi
Copy link
Owner

kishikawakatsumi commented Apr 28, 2020

Did you mean that you want to enable biometrics, but you do not want the passcode fallback, right? If so it's impossible. The passcode fallback option is always provided by the system. Otherwise you can completely disable biometrics and passcode.

@kipropkorir
Copy link
Author

I see other apps have done it though, it possible to do it without using this library maybe?

@kishikawakatsumi
Copy link
Owner

This configuration can also be done via the library. Give it a try.

@kipropkorir
Copy link
Author

kipropkorir commented Apr 28, 2020

Please guide me sir, using this library

@kishikawakatsumi
Copy link
Owner

let keychain = Keychain()
keychain
    .accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .touchIDAny)
keychain["..."] = ...

I haven't tested but it should work.

@kipropkorir
Copy link
Author

It does not work, please add the feature to your library thanks

@kishikawakatsumi
Copy link
Owner

@kipropkorir

I have confirmed that the above code works. Perhaps you are using the library in the wrong way.
I won't change the library because it is working properly. If my perception of your problem is wrong, please provide me more information tells me that.

@kipropkorir
Copy link
Author

Hey @kishikawakatsumi , thanks for the update, my case scenario is as below.

keychain
.accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .userPresence)
Using the above code shows both biometric and passocode option.

But when I use:

keychain
.accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .touchIDAny)
or
keychain
.accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .biometricAny)

It doesn't show the passcode option at first but after three failed biometric auth attempts it falls back to "enter passcode", kindly confirm this 👍

@kishikawakatsumi
Copy link
Owner

Show full code. I believe you do not use return value or running it on the main thread.

@kipropkorir
Copy link
Author

kipropkorir commented Apr 30, 2020

Here is the full code:

import Foundation
import KeychainAccess
import LocalAuthentication

public struct KeyChainKeys {
public static let MASTER_MSISDN = "master-msisdn"
public static let BIOMETRIC_ID_KEY = "touch-id-store"
}

protocol BiometricIDService {
var biometricSupported: BiometricType { get }

func checkForBiometricId(completion: @escaping ((_ pincode: String?, _ error: Error?) -> Void))
func storePinForBiometricId(_ pin: String, completion: @escaping ((_ error: Error?) -> Void))
func removeBiometricIdPin(completion: @escaping ((_ error: Error?) -> Void))

}

class BiometricIDServiceDefault: BiometricIDService {

private let bundleId = Bundle.main.bundleIdentifier!

let biometricSupported = LAContext().biometricType

func checkForBiometricId(completion: @escaping ((_ pincode: String?, _ error: Error?) -> Void)) 

{

    let keychain = Keychain(service: bundleId)
    DispatchQueue.global().async {
        do {
            let storedPin = try keychain
                .authenticationPrompt(NSLocalizedString("login", comment: "login" ))
                .get(KeyChainKeys.BIOMETRIC_ID_KEY)

            Logger.debug("pincode: \(String(describing: storedPin))")
            DispatchQueue.main.async {
                completion(storedPin, nil)
            }

        } catch let error {
            DispatchQueue.main.async {
                completion(nil, error)
            }
        }
    }

}

func storePinForBiometricId(_ pin: String, completion: @escaping ((_ error: Error?) -> Void)) {

    let keychain = Keychain(service: bundleId)
    DispatchQueue.global().async {
        do {
            try keychain
                .accessibility(.whenPasscodeSetThisDeviceOnly, authenticationPolicy: .touchIDAny) 
                .set(pin, key: KeyChainKeys.BIOMETRIC_ID_KEY)
            DispatchQueue.main.async {
                completion(nil)
            }
        } catch let error {
            DispatchQueue.main.async {
                completion(error)
            }
        }
    }

}

func removeBiometricIdPin(completion: @escaping ((Error?) -> Void)) {

    let keychain = Keychain(service: bundleId)
    do {
        try keychain.remove(KeyChainKeys.BIOMETRIC_ID_KEY)
        DispatchQueue.main.async {
            completion(nil)
        }
    } catch let error {
        DispatchQueue.main.async {
            completion(error)
        }
    }

}

}

enum BiometricType: String {
case none
case touchID
case faceID
}

private extension LAContext {
var biometricType: BiometricType {
var error: NSError?

    guard canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
        Logger.debug("Cannot evaluate biometrics policy \(error?.localizedDescription ?? "Error unavailable")")
        return .none
    }
    
    guard #available(iOS 11.0, *) else {
        return .touchID
    }
    switch biometryType {
    case .none:
        return .none
    case .touchID:
        return .touchID
    case .faceID:
        return .faceID
    @unknown default:
        assertionFailure("Unknown biometry type detected")
        return .none
    }
}

}

I use the storePinForBiometricId method to store the pin and use the checkForBiometricId to fetch the key in a different view controller

@kishikawakatsumi
Copy link
Owner

kishikawakatsumi commented Apr 30, 2020

Thanks. I have run your code. It seems working as expected.

When the first failure, it shows retry and cancel button. Then the second failure, shows only the cancel button. There is no passcode fallback. Isn't this the behavior you want?

The code I tried is here:

BiometricIDServiceDefault().storePinForBiometricId("abcd") { (e) in
    print(e) // => nil

    DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
        BiometricIDServiceDefault().checkForBiometricId { (p, e) in
            print(p)
            print(e)
        }
    }
}
First Failure Second Failure
Screen Shot 2020-04-30 at 23 57 17 Screen Shot 2020-04-30 at 23 57 24

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants