Adding security to React Native app using Fingerprint(Touch ID)

Adding security to React Native app using Fingerprint(Touch ID)

Keeping your phone secure from strangers is the top most priority. People use various kinds of methods to secure their phones, but what about adding Security to your mobile app?

Well lets talk about adding fingerprint(Touch ID) security in your mobile app.

In this tutorial, we'll be using the react-native-fingerprint-scanner package for securing our mobile app. You can visit here for npm package.

Step 1: Enable Fingerprint in your mobile device:

1. For iOS:

  • Tap Settings > Touch ID & Passcode, then enter your passcode.
  • Tap Add a Fingerprint and hold your device as you normally would when touching the Touch ID sensor.

2. For Android Phones:

  • Samsung: Navigate to Settings, then tap Biometrics and security, and then tap Fingerprints.
  • OnePlus: Navigating to the Security & lockscreen menu, locate the Fingerprint list subheading
  • Motorola: Touch Settings > Security & Location, and touch Fingerprint.

Step 2: Installing the package:

npm i react-native-fingerprint-scanner

OR

yarn add react-native-fingerprint-scanner

Linking the library to app:

  • For RN >= 0.60:
$ cd ios && pod install
  • For RN < 0.60, use react-native link to add the library to your project:
$ react-native link react-native-fingerprint-scanner

Step 3: Adding app permission:

  • Android:

In your AndroidManifest.xml: (API level 28+)

<uses-permission android:name="android.permission.USE_BIOMETRIC" />
  • iOS:

In your Info.plist:

<key>NSFaceIDUsageDescription</key>
<string>$(PRODUCT_NAME) requires FaceID access to allows you quick and secure access.</string>

Make sure the following versions are all correct in android/app/build.gradle

// API v29 enables FaceId
android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
...
    defaultConfig {
      targetSdkVersion 29

Step 4: Testing the code in app using fingerprint

You can test the fingerprint(Touch ID) by following code.

  • iOS:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { AlertIOS } from 'react-native';
import FingerprintScanner from 'react-native-fingerprint-scanner';

class FingerprintPopup extends Component {

  componentDidMount() {
    FingerprintScanner
      .authenticate({ description: 'Scan your fingerprint on the device scanner to continue' })
      .then(() => {
        this.props.handlePopupDismissed();
        AlertIOS.alert('Authenticated successfully');
      })
      .catch((error) => {
        this.props.handlePopupDismissed();
        AlertIOS.alert(error.message);
      });
  }

  render() {
    return false;
  }
}

FingerprintPopup.propTypes = {
  handlePopupDismissed: PropTypes.func.isRequired,
};

export default FingerprintPopup;
  • Android:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
  Alert,
  Image,
  Text,
  TouchableOpacity,
  View,
  ViewPropTypes,
  Platform,
} from 'react-native';

import FingerprintScanner from 'react-native-fingerprint-scanner';
import styles from './FingerprintPopup.component.styles';
import ShakingText from './ShakingText.component';


// - this example component supports both the
//   legacy device-specific (Android < v23) and
//   current (Android >= 23) biometric APIs
// - your lib and implementation may not need both
class BiometricPopup extends Component {
  constructor(props) {
    super(props);
    this.state = {
      errorMessageLegacy: undefined,
      biometricLegacy: undefined
    };

    this.description = null;
  }

  componentDidMount() {
    if (this.requiresLegacyAuthentication()) {
      this.authLegacy();
    } else {
      this.authCurrent();
    }
  }

  componentWillUnmount = () => {
    FingerprintScanner.release();
  }

  requiresLegacyAuthentication() {
    return Platform.Version < 23;
  }

  authCurrent() {
    FingerprintScanner
      .authenticate({ title: 'Log in with Biometrics' })
      .then(() => {
        this.props.onAuthenticate();
      });
  }

  authLegacy() {
    FingerprintScanner
      .authenticate({ onAttempt: this.handleAuthenticationAttemptedLegacy })
      .then(() => {
        this.props.handlePopupDismissedLegacy();
        Alert.alert('Fingerprint Authentication', 'Authenticated successfully');
      })
      .catch((error) => {
        this.setState({ errorMessageLegacy: error.message, biometricLegacy: error.biometric });
        this.description.shake();
      });
  }

  handleAuthenticationAttemptedLegacy = (error) => {
    this.setState({ errorMessageLegacy: error.message });
    this.description.shake();
  };

  renderLegacy() {
    const { errorMessageLegacy, biometricLegacy } = this.state;
    const { style, handlePopupDismissedLegacy } = this.props;

    return (
      <View style={styles.container}>
        <View style={[styles.contentContainer, style]}>

          <Image
            style={styles.logo}
            source={require('./assets/finger_print.png')}
          />

          <Text style={styles.heading}>
            Biometric{'\n'}Authentication
          </Text>
          <ShakingText
            ref={(instance) => { this.description = instance; }}
            style={styles.description(!!errorMessageLegacy)}>
            {errorMessageLegacy || `Scan your ${biometricLegacy} on the\ndevice scanner to continue`}
          </ShakingText>

          <TouchableOpacity
            style={styles.buttonContainer}
            onPress={handlePopupDismissedLegacy}
          >
            <Text style={styles.buttonText}>
              BACK TO MAIN
            </Text>
          </TouchableOpacity>

        </View>
      </View>
    );
  }


  render = () => {
    if (this.requiresLegacyAuthentication()) {
      return this.renderLegacy();
    }

    // current API UI provided by native BiometricPrompt
    return null;
  }
}

BiometricPopup.propTypes = {
  onAuthenticate: PropTypes.func.isRequired,
  handlePopupDismissedLegacy: PropTypes.func,
  style: ViewPropTypes.style,
};

export default BiometricPopup;
  • Screenshot: Taken from the docs

Screenshot

Step 5: Using the appropriate APIs:

  • For iOS & Android (common): isSensorAvailable(): Starts Fingerprint authentication on iOS

    • Returns a Promise<string>
    • biometryType: String - The type of biometric authentication supported by the device.
      1. iOS: biometryType = 'Touch ID', 'Face ID'
      2. Android: biometryType = 'Biometrics'
    • error: FingerprintScannerError { name, message, biometric } - The name and message of failure and the biometric type in use.
componentDidMount() {
  FingerprintScanner
    .isSensorAvailable()
    .then(biometryType => this.setState({ biometryType }))
    .catch(error => this.setState({ errorMessage: error.message }));
}
  • For iOS: authenticate({ description, fallbackEnabled })

    • Returns a Promise
    • description: String - the string to explain the request for user authentication.
    • fallbackEnabled: Boolean - default to true, whether to display fallback button (e.g. Enter Password).
componentDidMount() {
  FingerprintScanner
    .authenticate({ description: 'Scan your fingerprint on the device scanner to continue' })
    .then(() => {
      this.props.handlePopupDismissed();
      AlertIOS.alert('Authenticated successfully');
    })
    .catch((error) => {
      this.props.handlePopupDismissed();
      AlertIOS.alert(error.message);
    });
}
  • For Android:

  • authenticate({ title="Log In", subTitle, description, cancelButton="Cancel", onAttempt=() => (null) }): Starts Fingerprint authentication

    • Returns a Promise
    • title: String the title text to display in the native Android popup
    • subTitle: String the sub title text to display in the native Android popup
    • description: String the description text to display in the native Android popup
    • cancelButton: String the cancel button text to display in the native Android popup
    • onAttempt: Function - a callback function when users are trying to scan their fingerprint but failed.
componentDidMount() {
  if (requiresLegacyAuthentication()) {
    authLegacy();
  } else {
    authCurrent();
  }
}

componentWillUnmount = () => {
  FingerprintScanner.release();
}

requiresLegacyAuthentication() {
  return Platform.Version < 23;
}

authCurrent() {
  FingerprintScanner
    .authenticate({ title: 'Log in with Biometrics' })
    .then(() => {
      this.props.onAuthenticate();
    });
}

authLegacy() {
  FingerprintScanner
    .authenticate({ onAttempt: this.handleAuthenticationAttemptedLegacy })
    .then(() => {
      this.props.handlePopupDismissedLegacy();
      Alert.alert('Fingerprint Authentication', 'Authenticated successfully');
    })
    .catch((error) => {
      this.setState({ errorMessageLegacy: error.message, biometricLegacy: error.biometric });
      this.description.shake();
    });
}

handleAuthenticationAttemptedLegacy = (error) => {
  this.setState({ errorMessageLegacy: error.message });
  this.description.shake();
};
  1. release(): Stops fingerprint scanner listener, cancels native prompt if visible.

    • Returns a Void
componentWillUnmount() {
  FingerprintScanner.release();
}

By reading this blog, I'm pretty sure that you'll be able to implement Fingerprint(Touch ID) in your React Native app. If you face any error while implementing, you can always have a look at docs here .

Cover GIF Credits: Gfycat