It's 2015 - we're on the cusp of self-driving cars, commercial space flight, automated homes, and a world without passwords. All right, that last one might be a stretch, but with the release of Android 6.0 Marshmallow, Google has taken us one step closer to the post-password age. With the introduction of its Fingerprint Authentication API, the worlds most prevalent mobile OS has put its weight behind the move towards fingerprint authentication.

With this API, Google has created a native approach for fingerprint authentication across all Android devices. Developers can now authenticate their users on an app-by-app basis for everything from mobile purchases to app sign-in screens and more with just the tap of a finger. There are only three requirements for a user to to be eligible.

  1. The user's device must have a fingerprint reader
  2. The user's device must be running Android 6.0 Marshmallow (API 23) or greater
  3. The user must have registered fingerprints on the device (more on this later)

In the rest of this post we'll explore the new API's, how to support older devices, and also look at some recommended best practices when bringing fingerprint authentication into your app.

API Overview

The FingerprintManager class coordinates all access to the fingerprint hardware. Using FingerprintManager we can check for device support, attempt authentication, and handle any successful or failed authentication attempts appropriately. The first thing we'll need when implementing fingerprint authentication is an instance of FingerprintManager. This is a system-level service, so we need to call Context's getSystemService(String) method, passing in the Context.FINGERPRINT_SERVICE constant.


FingerprintManager fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);

Checking For Support

Now with our FingerprintManager instance, it's very simple to determine if the requirements mentioned at the beginning of this post are satisfied. First we can call isHardwareDetected() to receive a boolean indicating if the device has a fingerprint reader. If this returns false we'll need to authenticate our user some other way. If isHardwareDetected() returns true, we'll next need to call hasEnrolledFingerprints() to verify that the user has registered at least one fingerprint on the device. Even if the device has the necessary hardware, we can't authenticate a user's fingerprint if we don't have a registered one to compare against.


if (!fingerprintManager.isHardwareDetected()) { 
 // Device doesn't support fingerprint authentication 
} else if (!fingerprintManager.hasEnrolledFingerprints()) { 
 // User hasn't enrolled any fingerprints to authenticate with 
} else { 
 // Everything is ready for fingerprint authentication 
}

At this point you may have noticed we skipped the API 23+ requirement. Those lucky few of you with a minSdk of 23 or greater obviously won't have to worry about this, but for the rest of us there are two approaches - we can either check our Build.VERSION value, or we can leverage the support library. We'll explore these in more depth a little bit later.

Authenticating

Assuming all of the requirements are satisfied, we're ready to authenticate. This is done by calling FingerprintManager's authenticate(CryptoObject, CancellationSignal, int, AuthenticationCallback, Handler) method. Lets break down what each of these parameters does for us.

CryptoObject

Also introduced in Android 6.0 to support FingerprintManager, CryptoObject is a wrapper class for the crypto objects supported by FingerprintManager. These currently include Signature, Cipher, and Mac. Among the security improvements in Marshmallow, Google introduced some new Keystore features. One of these features is the ability to generate a key using KeyGenerator that requires the user to authentication before Keystore will allow the key to be used. Leveraging this secure key with our crypto object, we can create a secure method of authentication. For an example of how all of this looks in code, check out this sample implementation.

CancellationSignal

This gives us the ability to stop listening for fingerprints. In a typical implementation, this class' cancel() method will be called in the onPause() lifecycle method. This ensures we aren't listening for fingerprints while the application isn't available.

int

This is intended for flags, but currently we should only pass in 0.

AuthenticationCallback

This is the listener for fingerprint events. It provides four methods:

onAuthenticationError(int, CharSequence)
Called when a fatal error has occurred. This method provides the error code and error message as its parameters. You should implement this method to notify the user an error has occurred.

onAuthenticationFailed()
Called when a user attempts authentication but the fingerprint is not recognized. You should always notify the user that their authentication attempt failed.

onAuthenticationHelp(int, CharSequence)
Called when a non-fatal error has occurred. This method provides the error code and a help message you can display to the user.

onAuthenticationSucceeded(AuthenticationResult)
Called when a user's fingerprint is successfully recognized. The AuthenticationResult parameter includes the CryptoObject associated with the transaction.

It's important that your application correctly implements each of these methods to create a good user experience.

Handler

You can optionally provide a Handler. If provided, FingerprintManager will use the Looper from this Handler for its inner MyHandler instance.


private void useHandler(Handler handler) { 
 if (handler != null) { 
 mHandler = new MyHandler(handler.getLooper()); 
 } else if (mHandler.getLooper() != mContext.getMainLooper()) { 
 mHandler = new MyHandler(mContext.getMainLooper()); 
 } 
}

Providing the looper allows us to define what thread to run on and listen for message logging.


Looper looper = Looper.getMainLooper(); 
looper.setMessageLogging(new Printer() { 
 @Override 
 public void println(String x) { 
 Log.d(TAG, x); 
 } 
}); 
mFingerprintManager.authenticate(cryptoObject, mCancellationSignal, 0, this, new Handler(looper)); 

Test Your Code

To support the new APIs, ADB can emulate fingerprint touch events. If you only have one device running, the command for this is:


adb -e emu finger touch [finger_id]

If you don't have a device with a fingerprint reader, you can use this command with an emulator running API 23 or greater to register and use fingerprints. To enroll one or more fingerprints, go to Settings > Security and ensure you have a screen lock enabled. Once a screen lock is enabled, you can select Fingerprint from the Settings screen and choose “Add fingerprint". When the screen with the fingerprint icon appears, execute the above adb command.

Backwards Compatibility

Most of us will want our apps to support versions of Android earlier than 6.0. There are two approaches I can recommend for this: we can use Build.VERSION checks to ensure we only use these APIs on devices that support it, or we can leverage the support library. You're likely already familiar with Build.VERSION checks, so lets focus on how we can use the support library to make our implementation cleaner and safer.

Android's v4 support library includes compatibility versions of many of the fingerprint classes introduced in Marshmallow. On versions of Android below 6.0, these classes will act as though they are on a device that does not have fingerprint hardware. Happily, this eliminates the need to liter our code with Build.VERSION checks and @TargetApi annotations. As of revision 23, the v4 support library includes FingerprintManagerCompat and all of its nested classes - AuthenticationCallback, AuthenticationResult, and CryptoObject. Additionally there is a support version of CancellationSignal.

To demonstrate how we can use these classes to achieve backwards compatibility, I've modified Google's Fingerprint Authentication sample project and uploaded it here.

Best Practices

I'll wrap this post up by sharing some best practices for implementing fingerprint authentication. Following best practices helps ensure that our users feel comfortable and confident when authenticating in our application. Here are a couple ways to keep your users happy:

Have a Backup

Not everyone will be able to authenticate with a fingerprint, and some users may not want to. Always provide quick access to an alternative form of authentication.

Make It Familiar

Users like feeling smart and knowing what to do. One of the best ways to achieve this is by using design patterns they're familiar with. For this reason, Google strongly recommends using the standard fingerprint icon when you're listening for a fingerprint. I've included the icon in the sample application here.

Provide Feedback

Correctly implementing the AuthenticationCallback methods is vital to making sure your user knows what is happening. When onAuthenticationFailed() is called, notify the user that their fingerprint wasn't recognized. When onAuthenticationHelp(int, CharSequence) is called, Android has some information that might help your user authenticate - display it to them. When a fatal error has occurred and onAuthenticationError(int, CharSequence) is called, explain to your user that there was an error. Without this information, your users may get frustrated when they see nothing is happening or, possibly worse, when they see something unexpected happen.

Indoctrinate Them

This is more optional - if you see that a device has a fingerprint reader but the user hasn't enrolled any fingerprints, let them know they're missing out on a feature they may enjoy. Display an unobtrusive message or option for them to go to Android's Settings and enroll their fingerprints.

Closing Thoughts

Remembering credentials is a hassle. This is made worse on mobile devices by the need to input these values on small touch screens. Users dislike this experience so much that they resort to things like using “password" or “123456" as their credentials.

Implemented correctly, fingerprint authentication gives us an opportunity to turn this poor user experience into a secure and exciting feature. Hopefully this post and the sample project will help you create something you and your users will love.