52

I've integrated LocalAuthentication for my app security purpose, which has been supporting 'Touch Id' based supporting. But now, apple has recently added 'Face Id' based authentication also.

How can I check, which type of authentication is supported by a device. Touch Id or Face Id?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Krunal
  • 77,632
  • 48
  • 245
  • 261

15 Answers15

76

I've been struggling to get this to work and found that I needed to use a single instance of the LAContext and needed to call the LAContextInstance.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) before getting the biometryType. Here is my final code with support for older iOS versions:

import LocalAuthentication

static func biometricType() -> BiometricType {
    let authContext = LAContext()
    if #available(iOS 11, *) {
        let _ = authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
        switch(authContext.biometryType) {
        case .none:
            return .none
        case .touchID:
            return .touch
        case .faceID:
            return .face
        }
    } else {
        return authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? .touch : .none
    }
}

enum BiometricType {
    case none
    case touch
    case face
}
Hitesh Surani
  • 12,733
  • 6
  • 54
  • 65
leifdan01
  • 769
  • 1
  • 4
  • 4
  • 1
    Thanks for this. Exactly what I was looking for :) – Sethmr Nov 07 '17 at 15:29
  • 10
    Documentation for `biometryType` states: "This property is set only when canEvaluatePolicy succeeds for a biometric policy". So if canEvaluatePolicy fails (e.g. touchid/faceid is deactivated at all or per app) `biometricType()` returns `.none`. So `biometricType()` checking not availability of hardware but if hardware can be accessed by the app. – Valeriy Van Jan 11 '18 at 14:35
  • 1
    @ValeriyVan Have you figured out a way to check availability of hardware on device? It seems like everyone is giving biometricType as the answer, but, like you said, it is actually the wrong answer if you are just trying to present the user a button that says either "Face ID" or "Touch ID" so that the user can authorize one or the other *when device cannot yet be authenticated by biometrics*. – SAHM Feb 26 '18 at 20:36
  • 2
    @SAHM, actually I failed to find better way then checking device type. That is bad way as it is not future proof. Hope Apple will update API to address this issue. – Valeriy Van Feb 27 '18 at 11:55
  • 1
    It's funny because they actually said "Don't reference Touch ID on a device that supports Face ID. Conversely, don't reference Face ID on a device that supports Touch ID. Check the device's capabilities and use the appropriate terminology. For developer guidance, see LABiometryType." But there is no way to do just that unless the user has already authorized it. https://developer.apple.com/ios/human-interface-guidelines/user-interaction/authentication/ – SAHM Feb 27 '18 at 13:14
  • you must import LocalAuthentication – Sazzad Hissain Khan Apr 17 '18 at 15:32
  • 1
    this question is the right one and should be chosen like accepted. – swift2geek Dec 04 '18 at 09:06
  • 1
    At least on the iOS 12 simulator, inspecting the biometryType works and it is set correctly after the call to canEvaluatePolicy regardless of the canEvaluatePolicy's return value. Per iOS 12 documentation: "This property is set only after you call the canEvaluatePolicy(_:error:) method, and is set no matter what the call returns." – moliveira Feb 22 '19 at 17:40
  • Instead of switching on the authContext.biometryType, you can just do the following: Declare your enum `enum BiometricType: Int {} ` and then initialize it in the following way: `BiometricType(rawValue: authContext.biometryType.rawValue) ` and return the value parsed as an optional – Mario Bouchedid May 13 '20 at 15:35
52

With Xcode 9, Look at LocalAuthentication -> LAContext -> LABiometryType.

LABiometryType is a enum with values as in attached image

enter image description here

You can check which authentication type supported by device between Touch ID and FaceID or none.

Edit:

Apple have updated values for this enum LABiometryType. none is deprecated now.

enter image description here

Extension to check supported Biometric Type with Swift 5:

import LocalAuthentication

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

    var biometricType: BiometricType {
        var error: NSError?

        guard self.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            return .none
        }

        if #available(iOS 11.0, *) {
            switch self.biometryType {
            case .none:
                return .none
            case .touchID:
                return .touchID
            case .faceID:
                return .faceID
            @unknown default:
                #warning("Handle new Biometric type") 
            }
        }
        
        return  self.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? .touchID : .none
    }
}
Surjeet Singh
  • 11,691
  • 2
  • 37
  • 54
  • In the just released Xcode 9.2 beta, with iOS 11.2, the `LABiometryType` enum values have changed to `faceID` and `touchID`. – tfrank377 Nov 01 '17 at 20:57
  • 1
    @tfrank377 Thanks for reminding me. I have updated answer. – Surjeet Singh Nov 02 '17 at 04:34
  • can we detect whether device supports Face ID or touch ID using only simulator ? – shaqir saiyed Nov 30 '18 at 09:40
  • Yes, You can test this using simulator. Select Simulator -> Hardware -> Touch ID -> Cases This will provide support for both based on simulator. – Surjeet Singh Nov 30 '18 at 09:50
  • 1
    you have not answered the question. the question not about what it is? but how to/> – swift2geek Dec 04 '18 at 09:05
  • @swift2geek Krunal have already implemented LocalAuthentication, and only confused how he can check that device supports FaceId or TouchID. For that I guess, I answered correctly by providing him property to check auth type. – Surjeet Singh Dec 04 '18 at 12:05
  • @Surjeet, no your solution doesn't work for me. because its always returns me a type = 0. And 0 is .none. But its not true. Solution of bottom answer from mr leifdan01 worked for me. – swift2geek Dec 05 '18 at 08:02
13

As I am a big fan of extension. I phrase this answer a little differently. Essense is the same. This is a drop-in extension.

import LocalAuthentication

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

    var biometricType: BiometricType {
        var error: NSError?

        guard self.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            // Capture these recoverable error thru Crashlytics
            return .none
        }

        if #available(iOS 11.0, *) {
            switch self.biometryType {
            case .none:
                return .none
            case .touchID:
                return .touchID
            case .faceID:
                return .faceID
            }
        } else {
            return  self.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? .touchID : .none
        }
    }
}

Use like this:

var currentType = LAContext().biometricType
Alok C
  • 2,787
  • 3
  • 25
  • 44
7

I made a singleton class for local authentication as it helps to initialise an instance one time using static property for the entire application.

import Foundation
import LocalAuthentication

public class LocalAuthManager: NSObject {

    public static let shared = LocalAuthManager()
    private let context = LAContext()
    private let reason = "Your Request Message"
    private var error: NSError?

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

    private override init() {

    }

    // check type of local authentication device currently support
    var biometricType: BiometricType {
        guard self.context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            return .none
        }

        if #available(iOS 11.0, *) {
            switch context.biometryType {
            case .none:
                return .none
            case .touchID:
                return .touchID
            case .faceID:
                return .faceID
            }
        } else {
            return self.context.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil) ? .touchID : .none
        }
    }
}

Implementation:

func checkAuth() {
     let authType = LocalAuthManager.shared.biometricType
        switch authType {
        case .none:
            print("Device not registered with TouchID/FaceID")
        case .touchID:
            print("Device support TouchID")
        case .faceID:
            print("Device support FaceID")
        }
 }
Paul.V
  • 386
  • 1
  • 3
  • 13
6

Objective C :)

/** Only interesting devices are enumerated here. To change view constraints depending
 on screen height. Or the top notch for iPhone X
 */
typedef NS_ENUM(NSUInteger, BPDeviceType) {
    BPDeviceTypeUnknown,
    BPDeviceTypeiPhone4,
    BPDeviceTypeiPhone5,
    BPDeviceTypeiPhone6,
    BPDeviceTypeiPhone6Plus,
    BPDeviceTypeiPhone7,
    BPDeviceTypeiPhone7Plus,
    BPDeviceTypeiPhoneX,
    BPDeviceTypeiPad
};

+ (BPDeviceType)getDeviceType {
    double screenHeight = [[UIScreen mainScreen] bounds].size.height;
    if(UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPad)
    {
        return BPDeviceTypeiPad;

    } else if (UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiomPhone)
    {
        if (@available(iOS 11, *)) {
            UIEdgeInsets insets = [UIApplication sharedApplication].delegate.window.safeAreaInsets;
            if (insets.top > 0) {
                return BPDeviceTypeiPhoneX;
            }
        }

        if(screenHeight == 480) {
            return BPDeviceTypeiPhone4;
        } else if (screenHeight == 568) {
            return BPDeviceTypeiPhone5;
        } else if (screenHeight == 667) {
            return BPDeviceTypeiPhone6;
        } else if (screenHeight == 736) {
            return BPDeviceTypeiPhone6Plus;
        }
    }
    return BPDeviceTypeUnknown;
}

+ (BOOL) isBiometricIDAvailable {
    if (![LAContext class]) return NO;

    LAContext *myContext = [[LAContext alloc] init];
    NSError *authError = nil;
    if (![myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
        NSLog(@"%@", [authError localizedDescription]);
        return NO;
    }
    return YES;
}

+ (BOOL) isTouchIDAvailable {
    if (![LAContext class]) return NO;

    LAContext *myContext = [[LAContext alloc] init];
    NSError *authError = nil;
    if (![myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
        NSLog(@"%@", [authError localizedDescription]);
        return NO;
        // if (authError.code == LAErrorTouchIDNotAvailable) {}
    }

    if (@available(iOS 11.0, *)) {
        if (myContext.biometryType == LABiometryTypeTouchID){
            return YES;
        } else {
            return NO;
        }
    } else {
        return YES;
    }
}

+ (BOOL) supportFaceID {
    return [BPDeviceInfo getDeviceType] == BPDeviceTypeiPhoneX;
}

+ (BOOL) isFaceIDAvailable {
    if (![LAContext class]) return NO;

    LAContext *myContext = [[LAContext alloc] init];
    NSError *authError = nil;
    if (![myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
        NSLog(@"%@", [authError localizedDescription]);
        return NO;
    }

    if (@available(iOS 11.0, *)) {
        if (myContext.biometryType == LABiometryTypeFaceID){
            return YES;
        } else {
            return NO;
        }
    } else {
        return NO;
    }
}
karim
  • 15,408
  • 7
  • 58
  • 96
6

Face ID is available from iOS 11 and iPhone X comes with iOS 11 by default. In the LocalAuth framework they have added a 'biometryType' property which can give you ability to detect whether Face ID is available on device.

/// checks if face id is avaiable on device
func faceIDAvailable() -> Bool {
    if #available(iOS 11.0, *) {
        let context = LAContext()
        return (context.canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: nil) && context.biometryType == .faceID)
    }
    return false
}
Usman Awan
  • 1,208
  • 2
  • 13
  • 30
  • can we detect whether device supports Face ID or touch ID using only simulator ? – shaqir saiyed Nov 30 '18 at 09:39
  • @shaqirsaiyed Yes. We can detect whether device supports Face ID or Touch ID. When you run the app on iPhoneX or later devices it will automatically detects the Face ID else for iPhone 8 or iPhone 8Plus devices it will detect Touch ID. – Usman Awan Nov 30 '18 at 12:58
  • consider if I don't have any physical device and running in simulator. would I be able to detect then ? – shaqir saiyed Dec 05 '18 at 07:07
5

Here is one more way via the property (for example, on your access instance).

import LocalAuthentication


enum BiometricType {
    case none
    case touchID
    case faceID
}

var biometricType: BiometricType {
    get {
        let context = LAContext()
        var error: NSError?

        guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            print(error?.localizedDescription ?? "")
            return .none
        }

        if #available(iOS 11.0, *) {
            switch context.biometryType {
            case .none:
                return .none
            case .typeTouchID:
                return .touchID
            case .typeFaceID:
                return .faceID
            }
        } else {
            return  .touchID
        }
    }
}
Dren
  • 2,017
  • 21
  • 18
5

Here is my "helper class", it includes passcode also

enum BiometryType: String {
    case none = "None"
    case faceID = "Face ID"
    case touchID = "Touch ID"
    case passcode = "Passcode"
}


var biometryType: BiometryType {
    let myContext = LAContext()

    let hasAuthenticationBiometrics = myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
    let hasAuthentication = myContext.canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)

    if #available(iOS 11.0, *) {
        if hasAuthentication {
            if hasAuthenticationBiometrics {
                switch myContext.biometryType {
                case .none: return .none
                case .faceID: return .faceID
                case .touchID: return .touchID
                }
            } else {
                return .passcode
            }
        } else {
            return .none
        }
    } else {
        if hasAuthentication {
            if hasAuthenticationBiometrics {
                return .touchID
            } else {
                return .passcode
            }
        } else {
            return .none
        }
    }
}
Markicevic
  • 1,025
  • 9
  • 20
3
-(BOOL)faceIDAvailable {
        LAContext *myContext = [[LAContext alloc] init];
        NSError *authError = nil;

        if (@available(iOS 11.0, *)) {
            if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError] && myContext.biometryType == LABiometryTypeFaceID) {
                return true;
            }
        }
      return false;

}
-(BOOL)touchIDAvailable {
        LAContext *myContext = [[LAContext alloc] init];
        NSError *authError = nil;

        if (@available(iOS 11.0, *)) {
            if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError] && myContext.biometryType == LABiometryTypeTouchID) {
                return true;
            }
        }
      return false;

}
3

You NEED to add @unknown to the cases!!!

In newer Versions(Xcode 13...) you may have to specify "unknown"

#warning("Handle new Biometric type") doesn't return a value

import LocalAuthentication

class BiometricType{


static func biometricType() -> BiometricType {
    let authContext = LAContext()
    if #available(iOS 11, *) {
        let _ = authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
        switch(authContext.biometryType) {
        case .none:
            return .none
        case .touchID:
            return .touch
        case .faceID:
            return .face
        @unknown default:
            return .unknown
        }
    } else {
        return authContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? .touch : .none
    }
}

enum BiometricType {
    case none
    case touch
    case face
    case unknown
}

}
1

This code builds without warnings on Xcode 9.2-9.4 (see comments for 9.1):

@objc let biometricsAuthPolicy = LAPolicy.deviceOwnerAuthenticationWithBiometrics

@objc func supportsFaceID() -> Bool {
    if #available(iOS 11.0, *) {
        return biometryType() == .faceID // return biometryType() == .typeFaceID for Xcode 9.1
    }
    return false
}

@objc func supportsTouchID() -> Bool {
    if #available(iOS 11.0, *) {
        return biometryType() == .touchID // return biometryType() == .typeTouchID for Xcode 9.1
    }

    let context = LAContext()
    return context.canEvaluatePolicy(biometricsAuthPolicy, error: nil)
}

@objc @available(iOS 11.0, *)
func biometryType() -> LABiometryType {
    var error: NSError?
    let context = LAContext()

    guard context.canEvaluatePolicy(biometricsAuthPolicy, error: &error) else {
        if #available(iOS 11.2, *) {
            return .none
        }
        return LABiometryType.LABiometryNone // return LABiometryType.none for Xcode 9.1
    }

    return context.biometryType
}
Victor Bogdan
  • 2,022
  • 24
  • 33
1

From @Markicevic extension, but ignoring cases where user is not enrolled, etc...

extension LAContext {

enum BiometricType: String {
    case none = ""
    case touchID = "Touch ID"
    case faceID = "Face ID"
}

static var biometricType: BiometricType {
    var error: NSError?

    let context = LAContext()

    _ = context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)

    if error?.code == LAError.Code.touchIDNotAvailable.rawValue {
        return .none
    }

    if #available(iOS 11.0, *) {
        switch context.biometryType {
        case .none:
            return .none
        case .touchID:
            return .touchID
        case .faceID:
            return .faceID
        }
    } else {
        return  context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? .touchID : .none
    }
}

}

Everton Cunha
  • 1,017
  • 8
  • 10
1

update for swift 5, switch flow need a default condition.

import Foundation
import LocalAuthentication

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

    var biometricType: BiometricType {
        var error: NSError?

        guard self.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            // Capture these recoverable error through fabric
            return .none
        }

        if #available(iOS 11.0, *) {
            switch self.biometryType {
            case .touchID:
                return .touchID
            case .faceID:
                return .faceID
            default:
                return .none
            }
        }

        return self.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? .touchID : .none
    }

}

test case is in the below

// need to import LocalAuthentication in the calling file
// import LocalAuthentication
let currentType = LAContext().biometricType
print("biometry type > \(currentType)")
// biometry type > touchID

If you want to test in simulator, you need to enrolled touchId/faceId.
Simulator > Hardware > Touch ID/Face ID > Enrolled. enter image description here

Zgpeace
  • 3,927
  • 33
  • 31
1

Please refer to the apple sample code provided for "Logging a User into Your App with Face ID or Touch ID", which will be helpful to understand the authentication easily.

Apple Sample Code link - https://docs-assets.developer.apple.com/published/fbfd13f4d9/LoggingAUserIntoYourAppWithFaceIDOrTouchID.zip

Read the detailed explanation of the sample code on the below link. https://developer.apple.com/documentation/localauthentication/logging_a_user_into_your_app_with_face_id_or_touch_id

yo2bh
  • 1,356
  • 1
  • 14
  • 26
1

Started testing some new apps on the 12 Pro and realised my published apps only have Touch ID and not Face ID.

I came here and saw all of this so I started trying to change my Touch ID code but all I needed to do was add the privacy key to the info.plist.

Information Property List ➕

Then scroll down to: Privacy- Face ID Usage Description, (Type: String), (Value: YES)

Too easy

daj mi spokój
  • 248
  • 1
  • 2
  • 8