Biometric login is supported for the following operating systems:
iOS 11.3 or higher
Android 6.0 (API 23) or higher
Authgear supports enabling biometric login in the native mobile application. You will need to
Enable biometric login in your application via the portal.
In the mobile app, use the mobile SDK to enable biometric login for your users.
A pair of cryptographic keys will be generated upon registering biometric login. The private key will be stored securely in the device (using Keystore in Android and Keychain in iOS), while the public key is stored in the Authgear server. To authenticate the user, fingerprint or face is presented to unlock the private key, and a digital signed message is sent to the server to proof the authenticity of the user.
Fig 1.0. The following figure shows the sequence for enabling Biometric Login on a supported device:
The Client App that is already logged in to a user's account will check if biometrics is supported by the user's device. If the device supports biometric login, it is then enabled. The public key is sent to Authgear server and associated with the logged-in user's account.
The flow is then completed and biometric login is enabled for the user on the Client App.
Fig 2.0. The following figure shows the sequence for a user logging in with Biometric:
With biometric login already enabled for the user, the next time they need to log in they can initiate a biometric authentication flow which will follow the sequence shown in Fig 2.0 above. Once the biometric login is successful, Authgear server will return an access token and a refresh token. The client application can then use the access token to make authenticated requests.
Sounds overwhelming? Authgear's magic handles all these for you. Follow this guide to enable biometric login with a few lines of code in your app.
Enable biometric authentication for your project
In the portal, go to Authentication > Biometric.
Turn on Enable biometric authentication.
Save the settings.
authentication:identities:...# Add biometric along with the other enabled identities - biometricidentity:biometric:# Enable listing biometric login in user setting page, default falselist_enabled:true
Set reasonably short token lifetimes for client applications
Biometric login is usually used when you want the user to re-login after a relatively short period of time. For sensitive applications such as financial apps, it's recommended to use a short refresh token lifetime and a short idle timeout.
In the Authgear Portal, go to Applications
Select the client application that represent the integration with the mobile app
Set a short Refresh Token Lifetime to say 3,600 seconds (1 hour)
Enable Expire after idling
Set a short Idle Timeout, to say 1,800 seconds (30 minutes)
By doing so, the end-user's session will be expired 1 hour after their login, or after 30 minutes of inactivity. The end-user will need to authenticate themself again with biometric, even if the app process has not yet been killed.
Configure SDK so users must re-login after app closed
Apart from the short token lifetimes, it's also common for sensitive apps to ask the user to re-login by biometric after the app process is killed and relaunched.
The SDK should be configured to use TransientTokenStorage so the tokens are stored in memory, and will be cleared when the app is closed. So the end-users must authenticate with biometrics again.
let authgear =Authgear( clientId:"{your_clien_id}", endpoint:"{your_app_endpoint}", tokenStorage: TransientTokenStorage())authgear.configure() { result inswitch result {case .success():// configured successfullycaselet .failure(error):// failed to configured }}
publicclassMyAwesomeApplication extends Application {// The client ID of the oauth client.private static final String CLIENT_ID ="a_random_generated_string"// Deployed authgear's endpointprivate static final String AUTHGEAR_ENDPOINT ="http://<myapp>.authgear.cloud/"private Authgear mAuthgear;public void onCreate() {super.onCreate(); mAuthgear = new Authgear(this, CLIENT_ID, AUTHGEAR_ENDPOINT, new TransientTokenStorage()); mAuthgear.configure(new OnConfigureListener() {@Overridepublic void onConfigured() {// Authgear can be used. }@Overridepublic void onConfigurationFailed(@NonNull Throwable throwable) { Log.d(TAG, throwable.toString());// Something went wrong, check the client ID or endpoint. } }); }public Authgear getAuthgear() {return mAuthgear; }}
import React, { useCallback } from"react";import { View, Button } from"react-native";import authgear, { TransientTokenStorage } from"@authgear/react-native";functionLoginScreen() {constonPress=useCallback(() => {// Normally you should only configure once when the app launches. authgear.configure({ clientID:"client_id", endpoint:"http://<myapp>.authgear.cloud", tokenStorage:newTransientTokenStorage() }).then(() => { authgear.authenticate({ redirectURI:"com.myapp.example://host/path", }).then(({ userInfo }) => {console.log(userInfo); }); }); }, []);return ( <View> <ButtononPress={onPress} title="Authenticate" /> </View> );}
usingSystem;usingAndroid.App;usingAndroid.Content.PM;usingAndroid.Runtime;usingAndroid.OS;usingXamarin.Forms;usingAuthgear.Xamarin;namespaceMyApp.Droid{publicclassMainActivity:global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity {protectedoverridevoidOnCreate(Bundle savedInstanceState) { // ...var authgear =newAuthgearSdk(this,newAuthgearOptions { ClientId = CLIENT_ID, AuthgearEndpoint = ENDPOINT, TokenStorage =newTransientTokenStorage() });DependencyService.RegisterSingleton<AuthgearSdk>(authgear);LoadApplication(newApp()); // ... } // other methods are omitted for brevity. }}
Enable biometric login in mobile SDK
In the following section, we will show you how to use biometric login in the SDK. In the SDK code snippet, authgear is referring to the configured Authgear container.
Biometric options
In the SDKs, a set of biometric options is required to check the support or enable biometric on the device.
iOS
There are two options on iOS:
localizedReason is the message string the user will see when FaceID or TouchID is presented
constraint is an enum that constraint the access of key stored under different conditions:
biometryAny: The key is still accessible by Touch ID if fingers are added or removed, or by Face ID if the user is re-enrolled
BiometricCurrentSet: The key is invalidated if fingers are added or removed for Touch ID, or if the user re-enrolls for Face ID
title is the Title of the biometric dialog presented to the users
subtitle is the subtitle of the biometric dialog presented to the users
description is the description of the biometric dialog presented to the users
negativeButtonText is what the dismiss button says in the biometric dialog
constraint is an array defines the requirement of security level, which can be BIOMETRIC_STRONG, BIOMETRIC_WEAK, DEVICE_CREDENTIAL. See reference in Android developers documentation on BiometricManager.Authenticators``
invalidatedByBiometricEnrollment is a boolean that controls if the key pair will be invalidated if a new biometric is enrolled, or when all existing biometrics are deleted. See reference in Android developers documentation on KeyGenParameterSpec.Builder.
Code examples
Check support
Always check if the current device supports biometric login before calling any biometric API, including before enabling biometric login and before using biometric to login.
// check if current device supports biometric loginvar supported =falsedo {try authgear.checkBiometricSupported() supported =true} catch {}if supported {// biometric login is supported}
boolean supported =false;try {// biometric login is supported SDK_INT >= 23 (Marshmallow)if (Build.VERSION.SDK_INT>=23) {// check if current device supports biometric loginauthgear.checkBiometricSupported(this.getApplication(),ALLOWED ); supported =true; }} catch (Exception e) {}if (supported) {// biometric login is supported}
// We will need the options for the other biometric apiconstbiometricOptions= { ios: { localizedReason:'Use biometric to authenticate', constraint:'biometryCurrentSet'asconst, }, android: { title:'Biometric Authentication', subtitle:'Biometric authentication', description:'Use biometric to authenticate', negativeButtonText:'Cancel', constraint: ['BIOMETRIC_STRONG'asconst], invalidatedByBiometricEnrollment:true, },};// check if current device supports biometric loginauthgear.checkBiometricSupported(biometricOptions).then(() => {// biometric login is supported }).catch(() => {// biometric login is not supported });
// We will need the options for the other biometric apifinal ios =BiometricOptionsIOS( localizedReason:"Use biometric to authenticate", constraint:BiometricAccessConstraintIOS.biometryAny,);final android =BiometricOptionsAndroid( title:"Biometric Authentication", subtitle:"Biometric authentication", description:"Use biometric to authenticate", negativeButtonText:"Cancel", constraint: [BiometricAccessConstraintAndroid.biometricStrong], invalidatedByBiometricEnrollment:false,);try {// check if current device supports biometric loginawait authgear.checkBiometricSupported(ios: ios, android: android);// biometric login is supported} catch (e) {// biometric login is not supported}
constbiometricOptions:BiometricOptions= { ios: { localizedReason:"Use biometric to authenticate", constraint:BiometricAccessConstraintIOS.BiometryCurrentSet, policy:BiometricLAPolicy.deviceOwnerAuthenticationWithBiometrics, }, android: { title:"Biometric Authentication", subtitle:"Biometric authentication", description:"Use biometric to authenticate", negativeButtonText:"Cancel", constraint: [BiometricAccessConstraintAndroid.BiometricStrong], invalidatedByBiometricEnrollment:true, },};constupdateBiometricState=useCallback(async () => {if (isPlatformWeb()) {return; }try {awaitauthgearCapacitor.checkBiometricSupported(biometricOptions);constenabled=awaitauthgearCapacitor.isBiometricEnabled();setBiometricEnabled(enabled); //to be implemented in later step } catch (e) {console.error(e); } }, []);
// We will need the options for the other biometric apivar ios =newBiometricOptionsIos{ LocalizedReason ="Use biometric to authenticate", AccessConstraint =BiometricAccessConstraintIos.BiometricAny,};var android =newBiometricOptionsAndroid{ Title ="Biometric Authentication", Subtitle ="Biometric authentication", Description ="Use biometric to authenticate", NegativeButtonText ="Cancel", AccessConstraint =BiometricAccessConstraintAndroid.BiometricOnly, InvalidatedByBiometricEnrollment =false,};var biometricOptions =newBiometricOptions{ Ios = ios, Android = android};try{ // check if current device supports biometric loginauthgear.EnsureBiometricIsSupported(biometricOptions); // biometric login is supported}catch{ // biometric login is not supported}
Enable biometric login
Enable biometric login for logged in user
// provide localizedReason for requesting authentication// which displays in the authentication dialog presented to the userauthgear.enableBiometric( localizedReason:"REPLACE_WITH_LOCALIZED_REASON", constraint: .biometryCurrentSet) { result inifcaselet .failure(error)= result {// failed to enable biometric with error } else {// enabled biometric successfully }}
// We will need the options for the other biometric apiBiometricOptions biometricOptions =newBiometricOptions( activity,// FragmentActivity"Biometric authentication",// title"Biometric authentication",// subtitle"Use biometric to authenticate",// description"Cancel",// negativeButtonText ALLOWED,// allowedAuthenticatorstrue// invalidatedByBiometricEnrollment);authgear.enableBiometric( biometricOptions,newOnEnableBiometricListener() { @OverridepublicvoidonEnabled() {// enabled biometric login successfully } @OverridepublicvoidonFailed(Throwable throwable) {// failed to enable biometric with error } });
authgear.enableBiometric(biometricOptions).then(() => {// enabled biometric login successfully }).catch((err) => {// failed to enable biometric with error });
try {await authgear.enableBiometric(ios: ios, android: android);// enabled biometric login successfully} catch (e) {// failed to enable biometric with error}
try{awaitauthgear.EnableBiometricAsync(biometricOptions); // enabled biometric login successfully}catch{ // failed to enable biometric with error}
Check if biometric has been enabled before
Before asking the user to log in with biometric, Check if biometric login has been enabled on the current device. I.e. Is the key pair exist on the device (Keystore in Android and Keychain in iOS).
This method will still return true even if all the fingerprint and facial data has been removed from the device. Before this method, you should use the "checkBiometricSupported" to check if biometry is supported in the device level.
var enabled = (try? authgear.isBiometricEnabled()) ??false
try{var enabled =awaitauthgear.GetIsBiometricEnabledAsync(); // show if biometric login is enabled}catch{ // failed to check the enabled status}
Login with biometric credentials
If biometric is supported and enabled, you can use the Authenticate Biometric method to log the user in. If the key pair is invalidated due to changes in the biometry settings, e.g added fingerprint or re-enrolled face data, the biometricPrivateKeyNotFound will be thrown. You should handle the error by the Disable Biometric method, and ask the user to register biometric login again.
authgear.authenticateBiometric { result inswitch result {caselet .success(userInfo):let userInfo = userInfo// logged in successfullycaselet .failure(error):// failed to login }}
authgear.authenticateBiometric( biometricOptions,newOnAuthenticateBiometricListener() { @OverridepublicvoidonAuthenticated(UserInfo userInfo) {// logged in successfully } @OverridepublicvoidonAuthenticationFailed(Throwable throwable) {// failed to login } });
authgear.authenticateBiometric(biometricOptions).then(({userInfo}) => {// logged in successfully }).catch((e) => {// failed to login });
try {final userInfo =await authgear.authenticateBiometric(ios: ios, android: android);// logged in successfully} catch (e) {// failed to login}
In all methods related to biometric, the SDK may throw the following errors that describe the status of the biometry enrollment or the key pair stored on the device.
iflet authgearError = error as? AuthgearError {switch authgearError {case .cancel:// user cancelcase .biometricPrivateKeyNotFound:// biometric info has changed. e.g. Touch ID or Face ID has changed.// user have to set up biometric authentication againcase .biometricNotSupportedOrPermissionDenied:// user has denied the permission of using Face IDcase .biometricNoPasscode:// device does not have passcode set upcase .biometricNoEnrollment:// device does not have Face ID or Touch ID set upcase .biometricLockout:// the biometric is locked out due to too many failed attemptsdefault:// other error// you may consider showing a generic error message to the user }}
importcom.oursky.authgear.BiometricLockoutException;importcom.oursky.authgear.BiometricNoEnrollmentException;importcom.oursky.authgear.BiometricNoPasscodeException;importcom.oursky.authgear.BiometricNotSupportedOrPermissionDeniedException;importcom.oursky.authgear.BiometricPrivateKeyNotFoundException;importcom.oursky.authgear.CancelException;if (e instanceof CancelException) {// user cancel} elseif (e instanceof BiometricPrivateKeyNotFoundException) {// biometric info has changed// user have to set up biometric authentication again} elseif (e instanceof BiometricNoEnrollmentException) {// device does not have biometric set up} elseif (e instanceof BiometricNotSupportedOrPermissionDeniedException) {// biometric is not supported in the current device// or user has denied the permission of using biometric} elseif (e instanceof BiometricNoPasscodeException) {// device does not have unlock credential set up} elseif (e instanceof BiometricLockoutException) {// the biometric is locked out due to too many failed attempts} else {// other error// you may consider showing a generic error message to the user}
import { CancelError, BiometricPrivateKeyNotFoundError, BiometricNotSupportedOrPermissionDeniedError, BiometricNoEnrollmentError, BiometricNoPasscodeError, BiometricLockoutError,} from'@authgear/react-native'if (e instanceofCancelError) {// user cancel} elseif (e instanceofBiometricPrivateKeyNotFoundError) {// biometric info has changed. e.g. Touch ID or Face ID has changed.// user have to set up biometric authentication again} elseif (e instanceofBiometricNoEnrollmentError) {// device does not have biometric set up// e.g. have not set up Face ID or Touch ID in the device} elseif (e instanceofBiometricNotSupportedOrPermissionDeniedError) {// biometric is not supported in the current device// or user has denied the permission of using Face ID} elseif (e instanceofBiometricNoPasscodeError) {// device does not have unlock credential or passcode set up} elseif (e instanceofBiometricLockoutError) {// the biometric is locked out due to too many failed attempts} else {// other error// you may consider showing a generic error message to the user}
try {// ...} onCancelExceptioncatch (e) {// user cancel} onBiometricPrivateKeyNotFoundExceptioncatch (e) {// biometric info has changed. e.g. Touch ID or Face ID has changed.// user have to set up biometric authentication again} onBiometricNoEnrollmentExceptioncatch (e) {// device does not have biometric set up// e.g. have not set up Face ID or Touch ID in the device} onBiometricNotSupportedOrPermissionDeniedExceptioncatch (e) {// biometric is not supported in the current device// or user has denied the permission of using Face ID} onBiometricNoPasscodeExceptioncatch (e) {// device does not have unlock credential or passcode set up} onBiometricLockoutExceptioncatch (e) {// the biometric is locked out due to too many failed attempts} catch (e) {// other error// you may consider showing a generic error message to the user}
import { CancelError, BiometricPrivateKeyNotFoundError, BiometricNotSupportedOrPermissionDeniedError, BiometricNoEnrollmentError, BiometricNoPasscodeError, BiometricLockoutError,} from'@authgear/capacitor'if (e instanceofCancelError) {// user cancel} elseif (e instanceofBiometricPrivateKeyNotFoundError) {// biometric info has changed. e.g. Touch ID or Face ID has changed.// user have to set up biometric authentication again} elseif (e instanceofBiometricNoEnrollmentError) {// device does not have biometric set up// e.g. have not set up Face ID or Touch ID in the device} elseif (e instanceofBiometricNotSupportedOrPermissionDeniedError) {// biometric is not supported in the current device// or user has denied the permission of using Face ID} elseif (e instanceofBiometricNoPasscodeError) {// device does not have unlock credential or passcode set up} elseif (e instanceofBiometricLockoutError) {// the biometric is locked out due to too many failed attempts} else {// other error// you may consider showing a generic error message to the user}
try{ // ...}catch (OperationCanceledException ex){ // user cancel}catch (BiometricPrivateKeyNotFoundException ex){ // biometric info has changed. e.g. Touch ID or Face ID has changed. // user have to set up biometric authentication again}catch (BiometricNoEnrollmentException ex){ // device does not have biometric set up // e.g. have not set up Face ID or Touch ID in the device}catch (BiometricNotSupportedOrPermissionDeniedException ex){ // biometric is not supported in the current device // or user has denied the permission of using Face ID}catch (BiometricNoPasscodeException ex){ // device does not have unlock credential or passcode set up}catch (BiometricLockoutException ex){ // the biometric is locked out due to too many failed attempts}catch{ // other error // you may consider showing a generic error message to the user}