Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[expo-local-authentication] Add method to know enrolled security level of device #11780

Merged
merged 13 commits into from
Feb 10, 2021

Conversation

mickamy
Copy link
Contributor

@mickamy mickamy commented Jan 29, 2021

Why

Some apps wants to know local authentication security level on a device.

Providing this information may be helpful for those

  • who wants to advise a device owner to use more secure authentication
  • who wants to check if any local authentication is been set (who does not care if it is biometric auth or whatever)

How

API design

I basically followed to @LinusU's API proposal at #7032 (comment).
getEnrolledLevelAsync returns enum representing security level NONE, SECRET and BIOMETRIC, which seems very reasonsable.
Big up to @LinusU 🎉

iOS

I used LAPolicyDeviceOwnerAuthentication to know if any authentication is available. And LAPolicyDeviceOwnerAuthenticationWithBiometrics for biometrics.

Android

for SDK_VERSION >= M

for SDK_VERSION < M

Sadly, KeyguardDeviceManager#isDeviceSecure() is not supported on devices prior to Android M. The most similar API I found was isKeyguardSecure(), but it counts in SIM lock state, which is not very ideal. Because SIM lock is not where BiometricPrompt authentication falls back to.

Newer version (>= 1.1.0-alpha01) of androidx.biometric library has an introduced BiometricManager#canAuthenticate(int), which may be an option, but it is not a stable release version yet.

[update: 21/02/02]
Stable version of androidx.biometric v1.1.0 came out, but calling BiometricManager#canAuthenticate with BiometricManager.Authenticators.DEVICE_CREDENTIAL alone is not supported prior to API 30.
So we still need to use KeyguardManager#isKeyguardSecure() on devices prior to M.

Test Plan

I though I could use app/sandbox for testing, but I could not make it run. So I tested it on my own bare app and applied patch to it. Here is some screenshots of the result.

No authentication Passcode/PIN/Pattern or other non biometrics TouchID/FaceID or other biometrics
iOS iOS NONE iOS SECRET iOS BIOMETRIC
Android Android NONE Android SECRET Android BIOMETRIC
Here is the code I used on testing

import {
  Text,
  AppState,
  View,
  StyleSheet,
  Modal,
  TouchableHighlight,
  Button,
  Platform,
} from 'react-native';
import Constants from 'expo-constants';
import * as LocalAuthentication from 'expo-local-authentication';

const styles = StyleSheet.create({
  container: {
    alignContent: 'center',
    flex: 1,
    justifyContent: 'center',
    padding: 8,
    paddingTop: Constants.statusBarHeight,
  },
  innerContainer: {
    alignItems: 'center',
    flex: 1,
    justifyContent: 'center',
    marginTop: '30%',
  },
  modal: {
    alignItems: 'center',
    flex: 1,
    justifyContent: 'center',
    marginTop: '90%',
  },
  text: {
    alignSelf: 'center',
    fontSize: 22,
    paddingTop: 20,
  },
});

export default class LocalAuthTest extends React.Component {
  state = {
    appState: null,
    authenticated: false,
    enrolledLevel: null,
    failedCount: 0,
    modalVisible: false,
  };

  componentDidMount() {
    AppState.addEventListener('change', this.handleAppStateChange);
    this.checkDeviceSecurity();
  }

  componentWillUnmount() {
    AppState.removeEventListener('change', this.handleAppStateChange);
  }

  handleAppStateChange = nextAppState => {
    if (
      this.state.appState &&
      this.state.appState.match(/inactive|background/) &&
      nextAppState === 'active'
    ) {
      this.checkDeviceSecurity();
    }
    this.setState({appState: nextAppState});
  };

  async checkDeviceSecurity() {
    const level = await LocalAuthentication.getEnrolledLevelAsync();
    switch (level) {
      case 0:
        this.setState({enrolledLevel: 'SecurityLevel.NONE'});
        break;
      case 1:
        this.setState({enrolledLevel: 'SecurityLevel.SECRET'});
        break;
      case 2:
        this.setState({enrolledLevel: 'SecurityLevel.BIOMETRIC'});
        break;
    }
  }

  clearState = () => {
    this.setState({authenticated: false, failedCount: 0});
  };

  scanFingerPrint = async () => {
    try {
      const results = await LocalAuthentication.authenticateAsync();
      if (results.success) {
        this.setState({
          authenticated: true,
          failedCount: 0,
        });
      } else {
        this.setState(prev => {
          return {
            failedCount: prev.failedCount + 1,
          };
        });
      }
    } catch (e) {
      console.log(e);

      if (Platform.OS === 'android') {
        await LocalAuthentication.cancelAuthenticate().catch(e => console.log(e));
      }
    }
  };

  render() {
    return (
      <View style={[styles.container, {backgroundColor: 'white'}]}>
        {this.state.enrolledLevel && <Text style={styles.text}>{this.state.enrolledLevel}</Text>}
        <Button
          title={
            this.state.authenticated
              ? 'Reset and begin Authentication again'
              : 'Begin Authentication'
          }
          onPress={() => {
            this.clearState();
            this.scanFingerPrint();
          }}
        />

        {this.state.authenticated && <Text style={styles.text}>Authentication Successful! 🎉</Text>}
      </View>
    );
  }
}

// But there is no equivalent APIs prior to M.
// Newer version (>= 1.1.0-alpha01) of `androidx.biometric` library has an introduced
// `BiometricManager#canAuthenticate(int)` which will be an alternative of `KeyguardManager#isDeviceSecure()`,
// it is not a stable release version yet though.
Copy link
Contributor Author

@mickamy mickamy Feb 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stable version of androidx.biometric is out today (version 1.1.0), so it might be a first option.
https://developer.android.com/jetpack/androidx/releases/biometric

It deprecates some methods used in expo-local-authentication though. (e.g. BiometricManager#canAuthenticate() and BiometricPrompt.PromptInfo.Builder#setDeviceCredentialAllowed(boolean)

I can update and replace some deprecated function usage if that's OK to members of expo :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be nice ;)
What do you think? cc @byCedric

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukmccall @byCedric
Updated androidx.biometric library at 37ad0c9...67ba846

At first, I thought I could call BiometricManager#canAuthenticate(int) with BiometricManager.Authenticators#DEVICE_CREDENTIAL alone.
But it turned out that DEVICE_CREDENTIAL alone was not supported prior to API 30.
So we need to use KeyguardManager#isKeyguardSecure() still.

Note that not all combinations of authenticator types are supported prior to Android 11 (API 30). Specifically, DEVICE_CREDENTIAL alone is unsupported prior to API 30

https://developer.android.com/reference/androidx/biometric/BiometricManager#canAuthenticate(int)


btw I can split PR into API addition and library update, if you want to :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't a big PR so you don't have to split it ;)

Copy link
Contributor

@lukmccall lukmccall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is awesome 🚀🚀🚀
Thanks for your contribution 🥇

@mickamy mickamy force-pushed the local-auth-add-enrolled-level branch from 343d102 to 67ba846 Compare February 2, 2021 06:23
@mickamy
Copy link
Contributor Author

mickamy commented Feb 10, 2021

@lukmccall @byCedric
Is there anything I can do to get this to be merged?

Copy link
Contributor

@lukmccall lukmccall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing and it'll good to go 😉

Could you update the documentation page?

@lukmccall
Copy link
Contributor

Ohh I almost forgot about the changelog 😅
Please, add an changelog entry too -> https://github.com/expo/expo/blob/master/packages/expo-local-authentication/CHANGELOG.md

@mickamy
Copy link
Contributor Author

mickamy commented Feb 10, 2021

Thanks @lukmccall !
Done it at 46b4287

mickamy and others added 2 commits February 10, 2021 20:11
Co-authored-by: Łukasz Kosmaty <kosmatylukasz@gmail.com>
…thentication.md

Co-authored-by: Łukasz Kosmaty <kosmatylukasz@gmail.com>
@lukmccall lukmccall merged commit f793f04 into expo:master Feb 10, 2021
@mickamy mickamy deleted the local-auth-add-enrolled-level branch February 10, 2021 12:58
@mickamy mickamy restored the local-auth-add-enrolled-level branch April 5, 2021 09:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants