1

Ultimately, what I want to have is one function (or probably a function within a separate class) that prompts the user to authenticate via TouchID, then passcode and if either of these are successful then returns a true boolean.

I've figured out the authentication mostly however I can't get the function to return a boolean, here's roughly what I have so far:

The authenticate user function:

func authenticateUser() -> Bool {

        let context = LAContext()
        var error: NSError?
        let reasonString = "Authentication is needed to access your places."

        if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {

            context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, policyError) -> Void in

                if success {

                    print("touchID authentication succesful")

                } else {

                    switch policyError!.code {

                    case LAError.UserFallback.rawValue:

                        print("User selected to enter password.")

                        NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
                            self.showPasswordAlert()
                        })

                    default:

                        print("Authentication failed! :(")

                    }
                }

            })

        } else {

            print(error?.localizedDescription)

            NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
                self.showPasswordAlert()
            })
        }

        return true

    }

It's just set to return true for now for testing purposes. However I'd like to have it return true whenever there's a successful authentication. I can't place the return within the context.evaluatePolicy because it's inside the block method. Is there another way to do what I want? Or am I going about this in totally the wrong manner?

Also, for reference here is my showPasswordAlert function:

func showPasswordAlert() {

        let alertController = UIAlertController(title: "Passcode", message: "Please enter your passcode.", preferredStyle: .Alert)

        let defaultAction = UIAlertAction(title: "OK", style: .Default) { (action) -> Void in

            if let textField = alertController.textFields?.first as UITextField? {

                if let passcode = self.keychainWrapper.myObjectForKey("v_Data") as? String {

                    if textField.text == passcode {

                        print("Authentication successful! :) ")

                    } else {


                    }

                }

            }

        }

        let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)

        alertController.addAction(defaultAction)

        alertController.addAction(cancelAction)

        alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in

            textField.placeholder = "Enter passcode..."
            textField.textAlignment = NSTextAlignment.Center
            textField.secureTextEntry = true
            textField.keyboardType = UIKeyboardType.NumberPad

        }

        self.presentViewController(alertController, animated: true, completion: nil)

    }

So in my head what I'm thinking is: showPasswordAlert could also return a true boolean to authenticateUser and then this would in turn return a true boolean to where the authenticateUser function is being called. I know there's a simpler way to do that but I'd just like to get it working for now.

mforrest
  • 104
  • 2
  • 3
  • 12
  • Sort of related: [Throwing Exceptions in a Block Method](http://stackoverflow.com/questions/33213715/swift-throw-from-closure-nested-in-a-function) There are, as of this comment, no accepted nor positively scored answers (where 0 is not "positive") – Arc676 Oct 19 '15 at 14:13
  • So is the short answer that this can't be done the way I expect because it's inside a closure? – mforrest Oct 19 '15 at 16:32
  • Not sure. Maybe someone will come up with something. – Arc676 Oct 20 '15 at 13:00

2 Answers2

0

So after much trial and error I've come up with possibly what is the best solution for me at the moment.

It seems that since evaluatePolicy and co. are run asynchronously you can't return variables from them or access variables. You can however, call selectors from inside these blocks (why this is I have no idea).

So my current solution as of writing this post is to call the authenticate function as such:

func authenticateUserWithAction(actionSelector: Selector, object: AnyObject){}

I pass it an action (declared elsewhere in the class, but basically what you want to do if authentication is successful) and an object. The object is just incase the action requires something to be passed to the function. So in my app for example, after authentication a viewController is presented and an object on that viewController is set to an object in the original viewController. This object is passed in the authenticate function.

From within the authenticate user function I can call to an authenticateUserWithPasscode(actionSelector: Selector, object: AnyObject) that takes in the same action and object as the original authenticate function.

The action and object are passed down the chain until the user is authenticated and they are performed.

Pretty hacky code overall but it seems to be working fine for me.

mforrest
  • 104
  • 2
  • 3
  • 12
0

Also had this problem, I ended up making a struct called Authentication which has a static function authenticate which gets passed the view controller from where you're calling it and a callback function:

import LocalAuthentication
import UIKit

struct Authentication {
static func authenticate(viewController: UIViewController, callback: 
@escaping (Bool) -> ()) {
    let context = LAContext()
    var error: NSError?

    if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
        let reason = "Please Authenticate"

        context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) {
            [weak viewController] success, authenticationError in

            guard let viewController = viewController else {
                return
            }

            DispatchQueue.main.async {
                if success {
                    callback(true)
                } else {
                    let ac = UIAlertController(title: "Authentication failed", message: "Please try again", preferredStyle: .alert)
                    ac.addAction(UIAlertAction(title: "OK", style: .default))
                    viewController.present(ac, animated: true)
                    callback(false)
                }
            }
        }
    } else {
        let ac = UIAlertController(title: "Touch ID not available", message: "Your device is not configured for Touch ID.", preferredStyle: .alert)
        ac.addAction(UIAlertAction(title: "OK", style: .default))
        viewController.present(ac, animated: true)
        callback(false)
    }
}
}

Then calling it:

Authentication.authenticate(viewController: parentViewController, callback: {
        [weak self] (authenticated: Bool) in
        if authenticated {
            self?.yourFunctionHere()
        }
    })
Tom Pearson
  • 384
  • 1
  • 8