Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[local_auth] Allow device authentication (pin/pattern/passcode) #2489

Merged
merged 75 commits into from
Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
d4bf470
Add generic local authentication method
kennethj Jan 29, 2020
624e178
Android failover to device authentication
kennethj Jan 30, 2020
30c9e06
Add authenticate method that fails over to device authentication
kennethj Jan 30, 2020
074c9ff
Update example
kennethj Jan 30, 2020
706b5d3
Create prompt with fail over if fail over is true
kennethj Jan 30, 2020
1673242
Update example
kennethj Jan 30, 2020
589c61f
Update tests
kennethj Jan 30, 2020
9bb138f
Add documentation comments
kennethj Jan 30, 2020
73240b0
Increment version to 0.6.2 indicating a new feature
kennethj Jan 30, 2020
2bd7267
Add 0.6.2 changes to changelog
kennethj Jan 30, 2020
f54da09
Format code
kennethj Jan 30, 2020
34d730a
Format code
kennethj Jan 30, 2020
8505137
Format code
kennethj Jan 30, 2020
968018b
Format code
kennethj Jan 30, 2020
e7337e1
Update readme example
kennethj Jan 30, 2020
972d5da
Update gradle version
kennethj Jan 30, 2020
ee8b92c
Update gradle version
kennethj Jan 30, 2020
fb785a5
Merge branch 'master' into feature/local_auth
kennethj Feb 26, 2020
03ab18c
Handle older versions of the SDK 23+
kennethj Mar 12, 2020
8c8f35f
Add isDeviceSupported method
kennethj Mar 12, 2020
adecffb
Merge branch 'feature/local_auth' of https://github.com/kennethj/flut…
kennethj Mar 12, 2020
ea48204
Format code
kennethj Mar 13, 2020
4f740b5
Merge branch 'master' into feature/local_auth
kennethj Mar 13, 2020
b76ac35
Use ternary instead of Visibility widget
kennethj Mar 13, 2020
a8b033d
Merge branch 'feature/local_auth' of https://github.com/kennethj/flut…
kennethj Mar 13, 2020
3ae02ab
Merge branch 'master' into feature/local_auth
kennethj Aug 19, 2020
144a66d
Merge branch 'master' into feature/local_auth
kennethj Sep 15, 2020
60b4c43
Merge branch 'master' into feature/local_auth
kennethj Jan 2, 2021
51945ba
Clean up
kennethj Jan 2, 2021
64c12d6
Remove spaces
kennethj Jan 2, 2021
7c24fb4
format code
kennethj Jan 2, 2021
3aea84d
Format code
kennethj Jan 2, 2021
39bb801
Format
kennethj Jan 2, 2021
25dadc0
Refactor variable names
kennethj Jan 2, 2021
51ae491
Update version
kennethj Jan 2, 2021
58c0724
Gradle updates
kennethj Jan 2, 2021
208d9f4
Refactor: remove redundant setters
kennethj Jan 3, 2021
773a9d7
Allow dialog text to be set in dart
kennethj Jan 3, 2021
bebd37a
Use an enum to avoid null state
kennethj Jan 3, 2021
f376def
Add breaking change note to changelog
kennethj Jan 3, 2021
65508fa
Return `Future<bool>` instead of `Future<bool?>`
kennethj Jan 3, 2021
c43771f
Move `resultListener` property to top of class
kennethj Jan 3, 2021
be61dc1
Remove unneeded constructor
kennethj Jan 4, 2021
69455da
Use `authenticate` as the primary method and remove redundant code
kennethj Jan 5, 2021
df8348f
Update gradle
kennethj Jan 5, 2021
8f5c1b5
Format code
kennethj Jan 5, 2021
a76349d
Fix tests to include 'biometricOnly' parameter
kennethj Jan 5, 2021
3b8bbe6
Use `call.arguments`instead of `arguments`
kennethj Jan 5, 2021
7d2f4c1
Cast boolValue
kennethj Jan 5, 2021
5a48b9b
Format code
kennethj Jan 5, 2021
26d21ab
Add null checks
kennethj Jan 5, 2021
50d60d1
Update gradle version
kennethj Jan 5, 2021
d4439cd
Update gradle
kennethj Jan 5, 2021
3f40e6c
Set service properties onAttachedToActivity
kennethj Jan 6, 2021
2d1b4ba
Show errors
kennethj Jan 6, 2021
54995ea
Format
kennethj Jan 6, 2021
e65260f
Remove mystery hack of forgotten days gone by
kennethj Jan 6, 2021
454edbd
Add gradle file
kennethj Jan 6, 2021
ee9af37
Remove unreleased feature note from 0.6.3+1
kennethj Jan 12, 2021
a59f5d8
Update changelog
kennethj Jan 12, 2021
81d5bd3
Merge branch 'feature/local_auth' of https://github.com/kennethj/flut…
kennethj Jan 13, 2021
d2a0c0c
Remove local result properties and result listener
kennethj Jan 13, 2021
2d184cd
Remove unneeded this.
kennethj Jan 13, 2021
0c199f6
Add deprecated annotation to authenticateWithBiometrics
kennethj Jan 13, 2021
34dac80
Format code
kennethj Jan 13, 2021
2e42403
Merge branch 'master' into feature/local_auth
kennethj Jan 13, 2021
028d966
Add doc comment
kennethj Jan 13, 2021
3f8d163
Update readme
kennethj Jan 13, 2021
e0facb5
Format code
kennethj Jan 13, 2021
d0eda35
Update deprecate code
kennethj Jan 14, 2021
3f3d3fa
Enable useAndroidX
kennethj Jan 14, 2021
975e0c9
Revert changes to integration_test
kennethj Jan 14, 2021
7e27dec
Update gradle.properties
kennethj Jan 14, 2021
bc32335
Add lockRequestResult
kennethj Jan 15, 2021
db5bef2
Merge branch 'feature/local_auth' of https://github.com/kennethj/flut…
kennethj Jan 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/local_auth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## 0.6.3+1

* Add `authenticate` method that uses biometric authentication, but allows users to also use device authentication - pin, pattern, passcode.
kennethj marked this conversation as resolved.
Show resolved Hide resolved
* Update package:e2e -> package:integration_test


## 0.6.3

* Increase upper range of `package:platform` constraint to allow 3.X versions.
Expand Down Expand Up @@ -33,10 +35,12 @@

* Replace deprecated `getFlutterEngine` call on Android.


kennethj marked this conversation as resolved.
Show resolved Hide resolved
## 0.6.1+3

* Make the pedantic dev_dependency explicit.


## 0.6.1+2

* Support v2 embedding.
Expand Down
32 changes: 23 additions & 9 deletions packages/local_auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ bool canCheckBiometrics =

Currently the following biometric types are implemented:

* BiometricType.face
* BiometricType.fingerprint
- BiometricType.face
- BiometricType.fingerprint

To get a list of enrolled biometrics, call getAvailableBiometrics:

Expand All @@ -44,24 +44,39 @@ if (Platform.isIOS) {
We have default dialogs with an 'OK' button to show authentication error
messages for the following 2 cases:

1. Passcode/PIN/Pattern Not Set. The user has not yet configured a passcode on
iOS or PIN/pattern on Android.
2. Touch ID/Fingerprint Not Enrolled. The user has not enrolled any
fingerprints on the device.
1. Passcode/PIN/Pattern Not Set. The user has not yet configured a passcode on
iOS or PIN/pattern on Android.
2. Touch ID/Fingerprint Not Enrolled. The user has not enrolled any
fingerprints on the device.

Which means, if there's no fingerprint on the user's device, a dialog with
instructions will pop up to let the user set up fingerprint. If the user clicks
'OK' button, it will return 'false'.

Use the exported APIs to trigger local authentication with default dialogs:

The `authenticate()` method uses biometric authentication, but also allows
users to use pin, pattern, or passcode.

```dart
var localAuth = LocalAuthentication();
bool didAuthenticate =
await localAuth.authenticate(
localizedReason: 'Please authenticate to show account balance');
```

The `authenticateWithBiometrics()` method uses biometric authentication only.

```dart
var localAuth = LocalAuthentication();
bool didAuthenticate =
await localAuth.authenticateWithBiometrics(
localizedReason: 'Please authenticate to show account balance');
localizedReason: 'Please authenticate to show account balance');
```

Note that `authenticate()` and `authenticateWithBiometrics()` methods have
the same signature and parameters.

If you don't want to use the default dialogs, call this API with
'useErrorDialogs = false'. In this case, it will throw the error message back
and you need to handle them in your dart code:
Expand Down Expand Up @@ -134,7 +149,6 @@ you need to also add:
to your Info.plist file. Failure to do so results in a dialog that tells the user your
app has not been updated to use TouchID.


## Android Integration

Note that local_auth plugin requires the use of a FragmentActivity as
Expand All @@ -155,7 +169,7 @@ Update your project's `AndroidManifest.xml` file to include the
On Android, you can check only for existence of fingerprint hardware prior
to API 29 (Android Q). Therefore, if you would like to support other biometrics
types (such as face scanning) and you want to support SDKs lower than Q,
*do not* call `getAvailableBiometrics`. Simply call `authenticateWithBiometrics`.
_do not_ call `getAvailableBiometrics`. Simply call `authenticateWithBiometrics`.
This will return an error if there was no hardware available.

## Sticky Auth
Expand Down
2 changes: 2 additions & 0 deletions packages/local_auth/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.plugins.localauth">
<uses-sdk android:targetSdkVersion="29"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
Expand All @@ -20,6 +22,7 @@
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.DefaultLifecycleObserver;
Expand All @@ -28,6 +31,8 @@
import io.flutter.plugin.common.MethodCall;
import java.util.concurrent.Executor;

import static android.content.Context.KEYGUARD_SERVICE;

/**
* Authenticates the user with fingerprint and sends corresponding response back to Flutter.
*
Expand Down Expand Up @@ -75,21 +80,35 @@ interface AuthCompletionHandler {
Lifecycle lifecycle,
FragmentActivity activity,
MethodCall call,
AuthCompletionHandler completionHandler) {
AuthCompletionHandler completionHandler,
boolean allowCredentials) {
this.lifecycle = lifecycle;
this.activity = activity;
this.completionHandler = completionHandler;
this.call = call;
this.isAuthSticky = call.argument("stickyAuth");
this.uiThreadExecutor = new UiThreadExecutor();
this.promptInfo =
new BiometricPrompt.PromptInfo.Builder()
.setDescription((String) call.argument("localizedReason"))
.setTitle((String) call.argument("signInTitle"))
.setSubtitle((String) call.argument("fingerprintHint"))
.setNegativeButtonText((String) call.argument("cancelButton"))
.setConfirmationRequired((Boolean) call.argument("sensitiveTransaction"))
.build();

if (allowCredentials) {
this.promptInfo =
new BiometricPrompt.PromptInfo.Builder()
.setDescription((String) call.argument("localizedReason"))
.setTitle((String) call.argument("signInTitle"))
.setSubtitle((String) call.argument("fingerprintHint"))
.setConfirmationRequired((Boolean) call.argument("sensitiveTransaction"))
.setDeviceCredentialAllowed(true)
kennethj marked this conversation as resolved.
Show resolved Hide resolved
.build();

} else {
this.promptInfo =
new BiometricPrompt.PromptInfo.Builder()
.setDescription((String) call.argument("localizedReason"))
.setTitle((String) call.argument("signInTitle"))
.setSubtitle((String) call.argument("fingerprintHint"))
.setNegativeButtonText((String) call.argument("cancelButton"))
.setConfirmationRequired((Boolean) call.argument("sensitiveTransaction"))
.build();
}
}

/** Start the fingerprint listener. */
Expand Down Expand Up @@ -125,21 +144,25 @@ private void stop() {
public void onAuthenticationError(int errorCode, CharSequence errString) {
switch (errorCode) {
case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL:
completionHandler.onError(
"PasscodeNotSet",
"Phone not secured by PIN, pattern or password, or SIM is currently locked.");
break;
if (call.argument("useErrorDialogs")) {
showGoToSettingsDialog(
"Device credentials required",
kennethj marked this conversation as resolved.
Show resolved Hide resolved
"Device credentials security does not appear to be set up. Go to \'Settings > Security\' to add credentials.");
return;
}
completionHandler.onError("NotAvailable", "Security credentials not available.");
case BiometricPrompt.ERROR_NO_SPACE:
case BiometricPrompt.ERROR_NO_BIOMETRICS:
if(promptInfo.isDeviceCredentialAllowed())return;
if (call.argument("useErrorDialogs")) {
showGoToSettingsDialog();
showGoToSettingsDialog((String) call.argument("fingerprintRequired"), (String) call.argument("goToSettingDescription"));
return;
}
completionHandler.onError("NotEnrolled", "No Biometrics enrolled on this device.");
break;
case BiometricPrompt.ERROR_HW_UNAVAILABLE:
case BiometricPrompt.ERROR_HW_NOT_PRESENT:
completionHandler.onError("NotAvailable", "Biometrics is not available on this device.");
completionHandler.onError("NotAvailable", "Security credentials not available.");
break;
case BiometricPrompt.ERROR_LOCKOUT:
completionHandler.onError(
Expand Down Expand Up @@ -213,19 +236,23 @@ public void onResume(@NonNull LifecycleOwner owner) {
onActivityResumed(null);
}

//hack: dialog opens twice on Android 8
private boolean isSettingsDialogOpen = false;
// Suppress inflateParams lint because dialogs do not need to attach to a parent view.
@SuppressLint("InflateParams")
private void showGoToSettingsDialog() {
private void showGoToSettingsDialog(String title, String descriptionText) {
if(isSettingsDialogOpen) return;
View view = LayoutInflater.from(activity).inflate(R.layout.go_to_setting, null, false);
TextView message = (TextView) view.findViewById(R.id.fingerprint_required);
TextView description = (TextView) view.findViewById(R.id.go_to_setting_description);
message.setText((String) call.argument("fingerprintRequired"));
description.setText((String) call.argument("goToSettingDescription"));
message.setText(title);
description.setText(descriptionText);
Context context = new ContextThemeWrapper(activity, R.style.AlertDialogCustom);
OnClickListener goToSettingHandler =
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
isSettingsDialogOpen = false;
completionHandler.onFailure();
stop();
activity.startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
Expand All @@ -235,10 +262,12 @@ public void onClick(DialogInterface dialog, int which) {
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
isSettingsDialogOpen = false;
completionHandler.onFailure();
stop();
}
};
isSettingsDialogOpen = true;
new AlertDialog.Builder(context)
.setView(view)
.setPositiveButton((String) call.argument("goToSetting"), goToSettingHandler)
Expand Down
Loading