As we all know, from android 9.0, android introduced BiometricPrompt Api to provide standard authentication experience across growing range of biometric sensors (E.g Fingerprint,Face ID etc).
Now with this new BiometricPrompt Api user can get authenticated via fingerprint, face scanner or iris scanned (depend on their biometric preference). BiometricPrompt api will take care of this and it will notify us via various callbacks.
Below is my code to display Biometric Prompt.
biometricPrompt = new BiometricPrompt.Builder(context)
.setTitle("FingerPrint Authentication")
.setSubtitle("Login via Fingerprint")
.setDescription("Touch Fingerprint Sensor")
.setNegativeButton("Cancel", context.getMainExecutor(),
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Log.d(TAG,"Cancelled");
}
})
.build();
Now if you see my code, i am setting title as a Fingerprint Authentication. Now in device setting, if user had set a Biometric Preference as a Face ID instead of FingerPrint then this biometricPrompt will authenticate user via faceID and fingerprint sensor wont work even if user keep touching sensor. This would create confusion as Biometric title is saying that "Fingerprint authentication" and user is actually getting authenticated via faceID
Is there any way by which we can know what Biometric preference user has selected (e.g Fingerprint or FaceID)? So based upon that preference i can show appropriate message on BiometricPrompt so user wont get confused.
I already explored all api from BiometricPrompt but could find anything related to BiometricPreference.
Any help would be highly appreciated.
While not a perfect solution, you can use the PackageManager API to determine whether a device has the authenticator hardware, e.g.:
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE))
{
}
I've created a helper class as follows:
class BiometricAuthenticator
{
public enum BiometricType
{
FINGERPRINT,
FACE,
IRIS,
NONE
}
public static boolean hasBiometricAuthenticator(Context context)
{
int biometry = BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED;
if (VERSION.SDK_INT >= 30)
biometry = BiometricManager.from(context).canAuthenticate(Authenticators.BIOMETRIC_STRONG | Authenticators.BIOMETRIC_WEAK);
else
biometry = BiometricManager.from(context).canAuthenticate();
switch (biometry)
{
case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
return (false);
case BiometricManager.BIOMETRIC_SUCCESS:
return true;
}
return (false);
}
/**
* biometricType()
*
* returns type of biometry supported
*/
public static BiometricType biometricType(Context context)
{
if (VERSION.SDK_INT < 23)
return BiometricType.NONE;
PackageManager packageManager = context.getPackageManager();
// SDK 29 adds FACE and IRIS authentication
if (VERSION.SDK_INT >= 29)
{
if (packageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
return BiometricType.FACE;
if (packageManager.hasSystemFeature(PackageManager.FEATURE_IRIS))
return BiometricType.IRIS;
if (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
return BiometricType.FINGERPRINT;
return BiometricType.NONE;
}
// SDK 23-28 offer FINGERPRINT only
return (packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT) ? BiometricType.FINGERPRINT : BiometricType.NONE);
}
}
This allows you to determine if a biometric authenticator is present (hasBiometricAuthenticator), and if so, return the type of authenticator as a BiometricType enum.
A device could theoretically have multiple authenticators, and biometricType() will return FACE, IRIS, then FINGERPRINT in that order of preference on API30+ devices.
Hopefully Google will expose better API in the future, but these tricks will at least help get appropriate prompts on the dialog
There is no mean of knowing this type of information for now, an issue had been opened last year to ask for it (https://issuetracker.google.com/issues/111315641). As Android tried to simplify the path for developer to implement authentication in their apps, there is a lack of options in the BiometricPrompt implementation (see the Android document for BiometricPrompt implementation).
In your case you can simply change your title String to "Biometric Authentication", and so with the other Strings. For an example see the blog posts pointed to below.
Your code might look as follows. But I'd also recommend you use the strings.xml resource file instead of hard-coding your Strings in the code. For example, in the future, you may want translation services.
biometricPrompt = new BiometricPrompt.Builder(context)
.setTitle("Biometric Authentication")
.setSubtitle("Login via biometrics")
.setDescription("Use the Biometrics Sensor")
.setNegativeButton("Cancel", context.getMainExecutor(),
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Log.d(TAG,"Cancelled");
}
})
.build();
More broadly, the privacy implications must be evaluated before the API team makes a decision whether developers should know a user's biometric preferences. It's really not clear why a developer should need this information. Two blog posts have been published that touch on the subject of Strong vs Weak biometrics and how to implement them (blog one, blog two). Beyond that distinction (i.e. Strong vs Weak), what form-factors a user prefers or ends up using doesn't seem pertinent.
In Android R was added a method called setAllowedAuthenticators
public BiometricPrompt.Builder setAllowedAuthenticators (int authenticators)
Optional: Specifies the type(s) of authenticators that may be invoked by BiometricPrompt to authenticate the user. Available authenticator types are defined in Authenticators and can be combined via bitwise OR. Defaults to:
Authenticators#BIOMETRIC_WEAK for non-crypto authentication, or
Authenticators#BIOMETRIC_STRONG for crypto-based authentication.
If this method is used and no authenticator of any of the specified types is available at the time BiometricPrompt#authenticate(...) is called, authentication will be canceled and AuthenticationCallback#onAuthenticationError(int, CharSequence) will be invoked with an appropriate error code.
This method should be preferred over setDeviceCredentialAllowed(boolean) and overrides the latter if both are used. Using this method to enable device credential authentication (with Authenticators#DEVICE_CREDENTIAL) will replace the negative button on the prompt, making it an error to also call setNegativeButton(java.lang.CharSequence, java.util.concurrent.Executor, android.content.DialogInterface.OnClickListener).
authenticators
A bit field representing all valid authenticator types that may be
invoked by the prompt. Value is either 0 or a combination of
BiometricManager.Authenticators.BIOMETRIC_STRONG,
BiometricManager.Authenticators.BIOMETRIC_WEAK, and
BiometricManager.Authenticators.DEVICE_CREDENTIAL
Related
I'm working on an app for android phones to be used by multiple users, where they can log in with google or microsoft accounts, to connect the app info to microsoft teams and/or sharepoint if desired.
I'm coding on Android Studio, using MSAL supporting multiple accounts.
Underneath is a method I have to remove accounts from the current PublicClientApplication.MultipleAccountPublicClientApplication. It also returns the result for each removal, if they were removed or not, in a list of booleans.
When testing, all the accounts are removed successfully, but when signing in again and the microsoft sign in intent is opened, the accounts can just be clicked to sign in without password. Signing out seems kind of pointless because of this, since one can just select their user and be logged in again right away. Is it possible to require or force the Microsoft intent to log in with password?
public CompletableFuture<List<Boolean>> signOutAll() {
List<Boolean> removedList = new ArrayList<>();
CompletableFuture<List<Boolean>> future = new CompletableFuture();
for (IAccount account : accountList) {
mPCA.removeAccount(account,
new IMultipleAccountPublicClientApplication.RemoveAccountCallback() {
#Override
public void onRemoved() {
removedList.add(true);
if (accountList.size() == removedList.size()) {
future.complete(removedList);
}
}
#Override
public void onError(#NonNull MsalException exception) {
removedList.add(false);
if (accountList.size() == removedList.size()) {
future.complete(removedList);
}
}
});
}
return future;
}
--
Thank you,
Didrik
This is happening because MSAL automatically refreshes your token after expiration. When user opens your app it checks if that token is already present and valid. So you can remove the token from the Android KeyStore in onStop().
So yes you also need to remove the cache as well to remove the account from the cache, find the account that need to be removed and then call PublicClientApplication.removeAccount()
Set<IAccount> accounts = pca.getAccounts().join();
IAccount accountToBeRemoved = accounts.stream().filter(
x -> x.username().equalsIgnoreCase(
UPN_OF_USER_TO_BE_REMOVED)).findFirst().orElse(null);
pca.removeAccount(accountToBeRemoved).join();
Read more here.
On Android we basically don't have any control on the cookies because they are shared with external Chrome app and because of that it is not accessible. If you want the user to enter the password again then you should do this: AcquireTokenInteractive(scopes).WithPrompt(Prompt.ForceLogin);
I have an application which has a PIN/Lock screen. In order to open the app user needs to enter his PIN code (which he had set up before in the app).
I want to add Biometric option -> instead of entering the PIN just place your fingerprint. However you should still have an option to use the PIN as fallback. Exactly the same as Revolut, LastPass or bunch of other banking apps. Pretty straightforward, right?
I've looked at the new Biometric API and it does not support fallback to a custom pin/password (only fallback to a lock screen). I could somehow add that manually (when user cancels the dialog) but this creates poor UX (switching from Google style dialog to app style screen). Also, Google dialog has a transparent background (which could reveal sensitive information) so I would need to put it in a separate blank activity (again poor experience). I wonder how banking apps are planning to migrate to that?
Should I do this the old way (FingerprintManager)? Is fallback to device lock safe enough? If someone knows your phone PIN he could access all of your apps.
Have you looked at this blog post? or that one? The AndroidX Biometrics Library provides a method called setNegativeButtonText() that provides an option for using an account/app credential if the user doesn't want to use biometrics.
And then in the callback you would do
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Log.d(TAG, "$errorCode :: $errString")
if(errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
loginWithAppAccountCredentials() // Because negative button says use application/account password
}
}
Also when your user clicks the login button in your UI, your onClick could look like this:
override fun onClick(view: View) {
val promptInfo = createPromptInfo()
if (BiometricManager.from(context)
.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
biometricPrompt.authenticate(promptInfo, cryptoObject)
} else {
loginWithAppAccountCredentials()
}
}
I'm implementing Bio metric Prompt API for authorising user using Fingerprint. I found that Bio-metric Prompt API display different UI based on device sensor type.
Bio-metric API SDK call work independently to display respective UI based on sensor type.
Now the concern is:
In case of rear(at side of device in some devices) sensored device, it display dialog which also use to display error if any.
But in case of in/under display sensored device, it simply display a fingerprint impression and that does not display any error in case.
Now the question is:
Is there any API feature using that in-display prompt can display error.
In case not, how we can differentiate between both type of sensor device so can handle error explicitly.
In case you are still experiencing this problem, There is a recently published blog post that sheds some light on why the API behaves the way it does. The post also shows you how to use the API correctly.
You should only need to use the biometrics library in AndroidX.
Essentially you get three callbacks:
onAuthenticationSucceeded() is called when the user has been authenticated using a credential that the device recognizes.
onAuthenticationError() is called when an unrecoverable error occurs.
onAuthenticationFailed() is called when the user is rejected, for example when a non-enrolled fingerprint is placed on the sensor, but unlike with onAuthenticationError(), the user can continue trying to authenticate.
You can use any of these callbacks to post messages to your user through a Toast or such.
There is indeed an API and callback for you to utilize in this situation. The package you are looking for is either the Biometrics Package for API levels 28+ or the Fingerprint package for API levels 23-27.
The callback to which I am referring can be found here for API 28+ and here for API 23-27.
Here is some sample code with how the callback is initialized:
/**
* Helper class for authentication callback
*/
#RequiresApi(api = Build.VERSION_CODES.M)
private class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
private FingerprintHandler(){}
/**
* Called when an unrecoverable error has been encountered and the operation is complete.
* No further callbacks will be made on this object.
* #param errMsgId An integer identifying the error message
* #param errString A human-readable error string that can be shown in UI
*/
#Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
//Authentication error. The 'errString' is meant to be displayed to the user
//Handle logic here
}
/**
* Called when a fingerprint is valid but not recognized.
*/
#Override
public void onAuthenticationFailed() {
//Authentication failed (Fingerprints don't match ones on device)
//Handle logic here
}
/**
* Called when a recoverable error has been encountered during authentication. The help
* string is provided to give the user guidance for what went wrong, such as
* "Sensor dirty, please clean it."
* #param helpMsgId An integer identifying the error message
* #param helpString A human-readable string that can be shown in UI
*/
#Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
//Non-Fatal error (IE moved finger too quickly). The helpString can be displayed to the user to help them retry.
//Handle logic here
}
/**
* Called when a fingerprint is recognized.
* #param result An object containing authentication-related data
*/
#Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
//Authentication Succeeded. They are good to go and it matches the stored one
//Handle logic here
}
}
And here is some sample usage in a class of mine if the above code is not enough to get you moving.
With regards to utilizing an alert or display, I just use the callback info in conjunction with a Dialog or an on-screen textview if I don't want to block the screen.
I don't think your problem is something to do with In-display/Rear sensor. In my testing analyses with Biometricx APIs, I found both In-display/Rear sensor types showed error in the system UI when biometrics auth failed. It also depends on the device you are testing, device manufacturer may have decided not to support the Biometrics APIs. In my case, when I tested on Samsung S5 device, even though device had in-display sensor, canAuthenticate() returned false.
I'm writing a application feature to authenticate user using Biometric fingerprint authentication API. And it worked as expected with combination of BiometricPrompt API.
In general it display own UI dialog so it can be unified across Android device.(Improvement from Fingerprint Manager API)
In one of device testing scenario I come across in-display(on screen, e.g. Oneplus 6T device) fingerprint support instead rear biometric hardware option.
When I run application on it, on call of biometricPrompt.authenticate(..) instead of dialog it display in-display fingerprint authentication option. Which is ok, and manage by internal API of BiometricPrompt.
But it create some inconsistency to manage for developer.
When it provide in-build authentication dialog, all fallback error displayed in dialog itself.
But in case of in-display authentication I didn't found a way where it manage to display error message it-self. And I have to handle this fallback and display in a custom way.
Now question is
Is there a way to manage/display message by in-display authentication view component.
How can identify if device is support in-device biometric authentication.
Edit: Code reference I'm using:
import android.content.Context
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import java.lang.Exception
import java.util.concurrent.Executors
import javax.crypto.Cipher
class BiometricAuthenticationManager(private val context: Context) :
BiometricPrompt.AuthenticationCallback() {
private var biometricPrompt: BiometricPrompt? = null
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
biometricPrompt?.cancelAuthentication()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
}
fun init(cipher: Cipher, promptInfo: BiometricPrompt.PromptInfo) {
if (context is FragmentActivity) {
val executor = Executors.newSingleThreadExecutor()
biometricPrompt = BiometricPrompt(context, executor, this)
biometricPrompt?.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
} else {
throw Exception(
"Check for FragmentActivity context.")
}
}
}
For further reference about how it look like, please find following attachment.
I try to check same scenario for lock screen, where I guess it uses custom implementation using FingerPrintManager class and display message.
Faced with the same problem some time ago - OnePlus 6T has no Biometric UI, at the same time Samsung A51 - has its own (custom) UI for Fingerprint API and another for BiometricPrompt API.
I tried to watch for Activity window focus loss (For OnePlus - activity do not lose focus, at the same time BiometricPrompt UI leads to focus loss), but appear a problem with the Samsung device.
The only solution that I found, and it works for now:
Need to get the correct device name (for OnePlus for example, it should be not ONEPLUS A6003, but One Plus 6 instead)
Need to perform request to https://m.gsmarena.com/res.php3?sSearch=Device+Name, you should get the list with matches, usually, first one is needed
Parse HTML (from previews step) to get the link for device specification (like https://m.gsmarena.com/oneplus_6t-9350.php)
Now you need to perform a request to this link and again parse HTML and get the "Sensors" section. You should get a string like "Fingerprint (under display, optical), accelerometer, gyro, proximity, compass"
Split this string to get the list with sensors
Now when you need to check if an in-display scanner is present on the device, just check for "fingerprint" and "under display" for the same sensor.
Link with impl. that can be used as an example
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//Fingerprint API only available on from Android 6.0 (M)
FingerprintManager fingerprintManager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
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
}
}
Dont Forget to Add
<uses-permission android:name=" android.permission.USE_BIOMETRIC" />
In my device i already set a password of device, now i install my app which is managed by device policy manager. now when i call this method
int currentPolicy = devicePolicyManager.getPasswordQuality(demoDeviceAdmin);
if(currentPolicy==262144)passwordType="Alphabetic";
else if(currentPolicy==327680)passwordType="Alphanumeric";
else if(currentPolicy==131072)passwordType="Numeric";
//if(currentPolicy==196608)passwordType="PASSWORD_QUALITY_NUMERIC_COMPLEX";
else if(currentPolicy==393216)passwordType="Complex";
else if(currentPolicy==196608)passwordType="Pattern";
else if(currentPolicy==0)passwordType="None";
it gives me password type none. Now if i set password through device policy manager in my application like this
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
Then now if again get password quality it gives me correct value.
in First time i think device policy manager don't have or store old password type.
So my question is how to get password quality before setting password from my application through device policy manager.
Thank You
getPasswordQuality doesn't report the quality on the current password, it only reports the policy setting of lowest allowed password quality.
Basically getPasswordQuality returns the value set using the setPasswordQuality of the admin or the aggregate value if several admins are active.
The admin can check if the current password is good enough by calling DevicePolicyManager.isActivePasswordSufficient().
Keyguard reports back the current password status to DevicePolicyManager using the hidden method setActivePasswordState.
To my knowledge, this info is not available to a third party admin app.
If an incorrect password is entered, keyguard calls DevicePolicyManager.reportFailedPasswordAttempt which initiates a factory reset if too many failed attempts have been made.
Hope this helps.
/Marek Pola (Employee of Sony Mobile)
For devices before Android 6 (exclusive), you can get the unlocking method type by calling a hidden function getActivePasswordQuality() in class LockPatternUtils (you can call the function via Java reflection). But for Android 6 and above, I have not figured out how to get the unlocking method type.
/**
* Determine authentication method type (DAS, PIN, Password or Biometric)
*/
private String getAuthenticationMethodType(){
String LOCK_PATTERN_UTILS="com.android.internal.widget.LockPatternUtils";
String ACTIVE_PASSWORD_QUALITY="getActivePasswordQuality";
int lockProtectionLevel=0;
try{
Class <?> lockPatternUtilsClass=Class.forName(LOCK_PATTERN_UTILS);
Object lockPatternUtils=lockPatternUtilsClass.getConstructor(Context.class).newInstance(this);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
// Have not figured out how to get the unlocking method type for Android 6 and above devices. The same method will not work on Android 6. See detailed explanation below.
}else {
Method method = lockPatternUtilsClass.getMethod(ACTIVE_PASSWORD_QUALITY);
lockProtectionLevel=Integer.valueOf(String.valueOf(method.invoke(lockPatternUtils,null)));
}
}catch (Exception e){
e.printStackTrace();
}
switch (lockProtectionLevel){
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
return "PIN";
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
return "Password";
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
return "DAS";
case DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK:
return "Biometric";
default:return "None";
}
}
For Android 6, after all my tests so far, I found that if we still want to get the unlocking method with function getActivePasswordQuality(int userId), the app must have permission android.permission.ACCESS_KEYGUARD_SECURE_STORAGE in order to read lockscreen.password_type, and get the unlocking method. However, the permission ACCESS_KEYGUARD_SECURE_STORAGE is a signature permission, which means we need to sign the app with the same key that the manufacturers use to sign the system (or to say the permission), so as to grant the signature permission to the app successfully.
Hope this helps.