Authenticators
Currently the Android SDK offers three methods for user authentication:
-
PIN
is a default type of user authentication where user has to provide set of digits in order to authenticate. This method of authenticator has to be registered during user registration and can't be deregistered even when other authenticators are registered. When the user can't authenticate with another method (such as fingerprint), the Android SDK allows them to use their PIN instead. -
FINGERPRINT
is a native Android fingerprint authentication (using the Android API). In order to use it on a device, all requirements and availability have to be met. -
CUSTOM
is all third-party custom authenticators that are added to the app by the app developer. In order to use such authenticators, the app developer needs to enable the custom authenticator functionality in the IDAAS-core, provide proper scripts in the extension engine, and interface implementations in the Android SDK.
Authenticator interface
The OneginiAuthenticator
interface allows you to take a control over all authentication possibilities offered by the device. Currently the Android SDK supports three different types of user authentication: PIN (default), fingerprint, and custom authentication.
The OneginiAuthenticator
interface presents the following methods:
getType()
: This method should be used to distinguish all types of authentication. Currently all possible authentication types are: PIN,FINGERPRINT*
, CUSTOM.getId()
: This method should be used to distinguish different CUSTOM authenticators. When multiple CUSTOM authenticators are registered within the device or app, the type is not enough to distinguish between them (since all of them have CUSTOM type). In such case, the ID should be used. This is the ID that's provided by the authenticator developers. For PIN and FINGERPRINT authenticators, this value (while not empty) doesn't have much value.getName()
: Returns the name of the authenticator. For PIN and FINGERPRINT authenticators, names are predefined (PIN and Fingerprint). ForCUSTOM*
authenticators, the name is set in the client configuration provided by the IDAAS-core.isRegistered()
: This indicates if the authenticator was already registered for the user in the app. In particular, it will be always TRUE for PIN authenticator for any registered user.isPreferred()
: This indicates if the authenticator was set as preferred by the user. Only one (registered) authenticator can be set as preferred.
public interface OneginiAuthenticator {
int PIN = 0;
int FINGERPRINT = 1;
@Deprecated int FIDO = 2;
int CUSTOM = 3;
@IntDef({
PIN,
FINGERPRINT,
CUSTOM
})
@Retention(RetentionPolicy.RUNTIME)
@interface OneginiAuthenticatorType {}
/**
* Returns the ID of the authenticator
* @return String ID
*/
String getId();
/**
* Returns the authenticator type (PIN, FINGERPRINT, CUSTOM)
* @return int authenticator type
*/
@OneginiAuthenticatorType
int getType();
/**
* Returns a name of the authenticator
* @return String name
*/
String getName();
/**
* Returns if the authenticator is registered for usage
* @return TRUE if the authenticator is registered
*/
boolean isRegistered();
/**
* Returns if the authenticator was set as preferred one for authentication.
* @return TRUE if the authenticator is preferred
*/
boolean isPreferred();
/**
* Returns the UserProfile associated with the authenticator
* @return UserProfile userProfile
*/
UserProfile getUserProfile();
}
Registered and deregistered authenticators
To enable or disable each authenticator, the user has to register or deregister the authenticator.
If multiple user profiles are registered in the application, each user profile has its own set of registered authenticators. This means that if first user profile (Alice) registers fingerprint authenticator, the other registered user profile (Bob) does not register fingerprint authenticator.
The PIN authenticator is registered (created) during user registration. This type of authenticator can't be deregistered (unless the user gets deregistered). Other types of authenticators (FINGERPRINT and CUSTOM) can be registered and deregistered only by the currently authenticated user.
To fetch a set containing all possible authenticators for the user on current device, you can use the UserClient#getAllAuthenticators(final UserProfile userProfile)
method. The set will contain all registered and deregistered authenticators that are supported by the device and are enabled in the IDAAS-core configuration. You can also use the UserClientc#getRegisteredAuthenticators(final UserProfile userProfile)
and UserClient#getNotRegisteredAuthenticators(final UserProfile userProfile)
methods to fetch only registered or not registered authenticators.
Preferred authenticators
If a user has registered multiple authenticators, they can select a preferred authenticator. If a preferred authenticator is set, the preferred authenticator will be provided by default (the user retains the option to select an alternative authenticator.) If a preferred authenticator is deregistered, the Android SDK will provide the default PIN authenticator.
To set the preferred authenticator for the currently authenticated user, use the UserClient.setPreferredAuthenticator(final OneginiAuthenticator authenticator)
method.
- Only registered authenticators should be set as preferred.
- Only one authenticator can be set as preferred.
- Mobile authentication requests specify the authenticator that must be used. In these instances, the preferred authenticator setting is ignored.
Authenticate a user with a PIN
To authenticate a user with a PIN, determine if the user has registered and then handle the PIN authentication request.
Determine if a user exists
The UserClient
contains the getUserProfiles
method, which returns a set of all registered UserProfile
value objects. If the method isRegisteredAtLeastOneUser
returns false, it means no user is authenticated on the device. If the user is not registered, the user must register before they will be able to log in.
Example code - verify device has a registered user
private boolean isRegisteredAtLeastOneUser() {
final Set<UserProfile> userProfiles = OneginiSDK.getOneginiClient(this).getUserClient().getUserProfiles();
return userProfiles.size() > 0;
}
Log in a registered user
If a user is registered on the device, you may use the UserClient
authenticateUser
method to attempt to authenticate that user. The authenticateUser
method requires the following arguments:
UserProfile
is theUserProfile
ValueObject
that you want to authenticate.OneginiAuthenticationHandler
is the authentication handler to return the authentication result to.
Authentication results in an access token, optionally with a refresh token. The authentication handler contains:
onSuccess
is the method that lets you know that authentication was finished successfully. At this point, you can request data on behalf of the user.onError
is the method that is called in every other case.
PIN request handlers
You need to provide your own authentication request handlers using the OneginiClientBuilder
methods. The OneginiCreatePinRequestHandler
and OneginiPinAuthenticationRequestHandler
are required in order to perform authentication flows with a PIN.
Example code
public static OneginiClient getOneginiClient(final Context context) {
OneginiClient oneginiClient = OneginiClient.getInstance();
if (oneginiClient == null) {
final Context applicationContext = context.getApplicationContext();
final RegistrationRequestHandler registrationRequestHandler = new RegistrationRequestHandler(applicationContext);
final CreatePinRequestHandler createPinRequestHandler = new CreatePinRequestHandler(applicationContext);
final PinAuthenticationRequestHandler pinAuthenticationRequestHandler = new PinAuthenticationRequestHandler(applicationContext);
// will throw OneginiConfigNotFoundException if OneginiConfigModel class can't be found
oneginiClient = new OneginiClientBuilder(applicationContext, createPinRequestHandler, pinAuthenticationRequestHandler)
// add a browser registration handler
.setBrowserRegistrationRequestHandler(registrationRequestHandler)
.build();
}
return oneginiClient;
}
Create PIN request handler
The OneginiCreatePinRequestHandler
interface is responsible for handling the PIN creation process. Create a class that implements this interface and overrides the following methods:
-
startPinCreation(final UserProfile userProfile, final OneginiPinCallback oneginiPinCallback, final int pinLength)
: This method will be invoked by the Android SDK whenever there will be a need to create a new PIN (during registration, or during the change PIN action). You have to callOneginiPinCallback#acceptAuthenticationRequest(pinEnteredByUser)
in order to successfully finish the PIN creation process. ThepinLength
parameter determines the required pin length. In order to cancel the PIN creation process, you can call theOneginiPinCallback#denyAuthenticationRequest()
method. -
onNextPinCreationAttempt(final OneginiPinValidationError oneginiPinValidationError)
: This method will be called when the PIN provided by user hasn't met the PIN policy. You can check the exact error by comparing theOneginiPinValidationError#getErrorType()
value with theOneginiPinValidationError's
static fields. There are also theOneginiPinValidationError#getMessage()
andOneginiPinValidationError#getCause()
methods, which can return more information about the error. -
finishPinCreation()
is called whenever the correct PIN was created or the action was canceled by the user. Below is sample code for creating a custom PIN request handler. This example creates an additionalPinWithConfirmationHandler
to support the PIN verification step. You can feel free to skip this step if it's not relevant for your use case. You can check the full solution in our example app that is available on GitHub.
Example code
public class CreatePinRequestHandler implements OneginiCreatePinRequestHandler {
public static PinWithConfirmationHandler oneginiPinCallback;
private final Context context;
public CreatePinRequestHandler(final Context context) {
this.context = context;
}
@Override
public void startPinCreation(final UserProfile userProfile, final OneginiPinCallback oneginiPinCallback, final int pinLength) {
PinActivity.setIsCreatePinFlow(true);
notifyActivity(context.getString(R.string.pin_title_choose_pin), "");
CreatePinRequestHandler.oneginiPinCallback = new PinWithConfirmationHandler(oneginiPinCallback);
}
@Override
public void onNextPinCreationAttempt(final OneginiPinValidationError oneginiPinValidationError) {
handlePinValidationError(oneginiPinValidationError);
}
@Override
public void finishPinCreation() {
Toast.makeText(context, "CreatePinRequestHandler#finishPinCreation", Toast.LENGTH_LONG).show();
}
/**
* Extended pin handler, used to create PIN verification step
*/
public class PinWithConfirmationHandler {
private final OneginiPinCallback originalHandler;
private char[] pin;
public PinWithConfirmationHandler(final OneginiPinCallback originalHandler) {
this.originalHandler = originalHandler;
}
public void onPinProvided(final char[] pin) {
if (isPinSet()) {
secondPinProvided(pin);
} else {
firstPinProvided(pin);
}
}
private void firstPinProvided(final char[] pin) {
OneginiSDK.getOneginiClient(context).getUserClient().validatePinWithPolicy(pin, new OneginiPinValidationHandler() {
@Override
public void onSuccess() {
PinWithConfirmationHandler.this.pin = pin;
notifyActivity(context.getString(R.string.pin_title_verify_pin), "");
}
@Override
public void onError(final OneginiPinValidationError oneginiPinValidationError) {
handlePinValidationError(oneginiPinValidationError);
}
});
}
public void secondPinProvided(final char[] pin) {
final boolean pinsEqual = Arrays.equals(this.pin, pin);
nullifyPinArray();
if (pinsEqual) {
originalHandler.acceptAuthenticationRequest(pin);
} else {
notifyActivity(context.getString(R.string.pin_title_choose_pin), context.getString(R.string.pin_error_not_equal));
}
}
public void pinCancelled(){
nullifyPinArray();
originalHandler.denyAuthenticationRequest();
}
private boolean isPinSet() {
return pin != null;
}
private void nullifyPinArray() {
if (isPinSet()) {
final int arraySize = pin.length;
for (int i = 0; i < arraySize; i++) {
pin[i] = '\0';
}
pin = null;
}
}
}
private void handlePinValidationError(final OneginiPinValidationError oneginiPinValidationError) {
@OneginiPinValidationError.PinValidationErrorType int errorType = oneginiPinValidationError.getErrorType();
switch (errorType) {
case OneginiPinValidationError.WRONG_PIN_LENGTH:
notifyActivity(context.getString(R.string.pin_title_choose_pin), context.getString(R.string.pin_error_invalid_length));
break;
case OneginiPinValidationError.PIN_BLACKLISTED:
notifyActivity(context.getString(R.string.pin_title_choose_pin), context.getString(R.string.pin_error_blacklisted));
break;
case OneginiPinValidationError.PIN_IS_A_SEQUENCE:
notifyActivity(context.getString(R.string.pin_title_choose_pin), context.getString(R.string.pin_error_sequence));
break;
case OneginiPinValidationError.PIN_USES_SIMILAR_DIGITS:
notifyActivity(context.getString(R.string.pin_title_choose_pin), context.getString(R.string.pin_error_similar));
break;
case OneginiPinValidationError.DEVICE_DEREGISTERED:
new DeregistrationUtil(context).onDeviceDeregistered();
case OneginiPinValidationError.GENERAL_ERROR:
default:
notifyActivity(context.getString(R.string.pin_title_choose_pin), oneginiPinValidationError.getMessage());
break;
}
}
private void notifyActivity(final String title, final String message) {
final Intent intent = new Intent(context, PinActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(PinActivity.EXTRA_TITLE, title);
intent.putExtra(PinActivity.EXTRA_MESSAGE, message);
context.startActivity(intent);
}
}
PIN authentication request handler
Create a class that implements the OneginiPinAuthenticationRequestHandler
interface, which is responsible for handling PIN authentication requests, and override the following methods:
-
startAuthentication(final UserProfile userProfile, final OneginiPinCallback oneginiPinCallback, final AuthenticationAttemptCounter attemptCounter)
: This method will be invoked by the Android SDK whenever there will be a to need authenticate the user. You have to callOneginiPinCallback#acceptAuthenticationRequest(pinEnteredByUser)
in order to successfully finish the PIN creation process orOneginiPinCallback#denyAuthenticationRequest()
to cancel it. -
onNextAuthenticationAttempt(final AuthenticationAttemptCounter attemptCounter)
: This method is called when the PIN that is provided by the user is incorrect, but the failed attempts limit hasn't been reached yet. The method's parameter is anAuthenticationAttemptCounter
object providing information about number of failed attempts, remaining attempts, and maximum attempts counters. -
finishAuthentication()
: This method is called whenever the authentication process is finished regardless of whether it was successful or failed.
Example code
public class PinAuthenticationRequestHandler implements OneginiPinAuthenticationRequestHandler {
public static OneginiPinCallback oneginiPinCallback;
private static UserProfile userProfile;
private final Context context;
private final UserStorage userStorage;
public PinAuthenticationRequestHandler(final Context context) {
this.context = context;
userStorage = new UserStorage(context);
}
@Override
public void startAuthentication(final UserProfile userProfile, final OneginiPinCallback oneginiPinCallback, final AuthenticationAttemptCounter attemptCounter) {
PinAuthenticationRequestHandler.userProfile = userProfile;
PinAuthenticationRequestHandler.oneginiPinCallback = oneginiPinCallback;
PinActivity.setIsCreatePinFlow(false);
startPinActivity(userProfile);
}
@Override
public void onNextAuthenticationAttempt(final AuthenticationAttemptCounter attemptCounter) {
PinActivity.setRemainingFailedAttempts(attemptCounter.getRemainingAttempts());
startPinActivity(userProfile);
}
@Override
public void finishAuthentication() {
PinActivity.setRemainingFailedAttempts(0);
}
private void startPinActivity(final UserProfile userProfile) {
final Intent intent = new Intent(context, PinActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(PinActivity.EXTRA_TITLE, context.getString(R.string.pin_title_enter_pin));
final User user = userStorage.loadUser(userProfile);
intent.putExtra(PinActivity.EXTRA_USER_NAME, user.getName());
context.startActivity(intent);
}
}
Authenticate user with custom authenticator
The Android SDK allows you to authenticate users with third-party (custom) authenticators. In such case, you (or the custom authenticator library) are responsible for authenticating the user when requested and returning the authentication result (success, failure, or exception) back to the Android SDK. In order to use the custom authentication feature, you need to first enable and configure the feature on the IDAAS-core side.
To use a custom authenticator, you should provide an implementation of the OneginiCustomAuthenticator
interface to the Android SDK. The implementation provides the unique ID of an authenticator along with methods that should be called to register, deregister, or authenticate using the custom authenticator. These methods are named by the Android SDK as Actions
and are defined by interfaces: OneginiCustomAuthRegistrationAction
, OneginiCustomAuthDeregistrationAction
, and OneginiCustomAuthAuthenticationAction
.
Example code - for custom authentication implementation
public class BasicCustomAuthenticator implements OneginiCustomAuthenticator {
private final OneginiCustomAuthRegistrationAction registrationAction;
private final OneginiCustomAuthDeregistrationAction deregistrationAction;
private final OneginiCustomAuthAuthenticationAction authAuthenticationAction;
public BasicCustomAuthenticator(final Context context) {
registrationAction = new BasicCustomAuthRegistrationAction(context);
deregistrationAction = new BasicCustomAuthDeregistrationAction(context);
authAuthenticationAction = new BasicCustomAuthAuthenticationAction(context);
}
@Override
public OneginiCustomAuthRegistrationAction getRegistrationAction() {
return registrationAction;
}
@Override
public OneginiCustomAuthDeregistrationAction getDeregistrationAction() {
return deregistrationAction;
}
@Override
public OneginiCustomAuthAuthenticationAction getAuthenticationAction() {
return authAuthenticationAction;
}
@Override
public String getId() {
return "EXPERIMENTAL_CUSTOM_AUTHENTICATOR_ID";
}
}
OneginiCustomAuthRegistrationAction
The OneginiCustomAuthRegistrationAction
interface defines a method that should be called when the Android SDK requires the custom authenticator registration. When the OneginiCustomAuthRegistrationAction#finishRegistration
method is called, you should start the registration process and return the final result on the provided OneginiCustomAuthRegistrationCallback
. It consists of three methods:
-
void acceptRegistrationRequest(String optionalRegistrationData)
should be called when the custom authenticator registration has succeeded. -
void denyRegistrationRequest()
should be triggered when the custom authenticator registration has been canceled. -
void returnError(Exception e)
should be invoked when the custom authenticator registration has returned an error.
Example code - for custom authenticator registration action implementation
public class BasicCustomAuthRegistrationAction implements OneginiCustomAuthRegistrationAction {
public static OneginiCustomAuthRegistrationCallback CALLBACK;
private final Context context;
public BasicCustomAuthRegistrationAction(final Context context) {
this.context = context;
}
@Override
public void finishRegistration(final OneginiCustomAuthRegistrationCallback callback) {
CALLBACK = callback;
final Intent intent = new Intent(context, BasicAuthenticatorRegistrationActivity.class);
context.startActivity(intent);
}
}
OneginiCustomAuthDeregistrationAction
The OneginiCustomAuthDeregistrationAction
interface defines a method that should be called when the Android SDK requires the custom authenticator deregistration. When the OneginiCustomAuthDeregistrationAction#finishDeregistration
method is called, you should start the deregistration process and return the final result on the provided OneginiCustomAuthDeregistrationCallback
. It consists of three methods:
-
void acceptDeregistrationRequest(String optionalDeregistrationData)
should be called when the custom authenticator deregistration has succeeded. -
void denyDeregistrationRequest()
should be triggered when the custom authenticator deregistration has been canceled. -
void returnError(Exception e)
should be invoked when the custom authenticator deregistration has returned an error.
Example code - for custom authenticator deregistration action implementation
public class BasicCustomAuthDeregistrationAction implements OneginiCustomAuthDeregistrationAction {
public static OneginiCustomAuthDeregistrationCallback CALLBACK;
private final Context context;
public BasicCustomAuthDeregistrationAction(final Context context) {
this.context = context;
}
@Override
public void finishDeregistration(final OneginiCustomAuthDeregistrationCallback callback, final String s) {
CALLBACK = callback;
final Intent intent = new Intent(context, BasicAuthenticatorDeregistrationActivity.class);
context.startActivity(intent);
}
}
OneginiCustomAuthAuthenticationAction
The OneginiCustomAuthAuthenticationAction
interface defines a method that should be called when the Android SDK requires the custom authenticator authentication. When the OneginiCustomAuthAuthenticationAction#finishAuthentication
method is called, you should start the authentication process and return the final result on the provided OneginiCustomAuthAuthenticationCallback
. It consists of two methods:
-
void returnSuccess(String optionalAuthenticationData)
should be called when the custom authenticator authentication has succeeded. -
void returnError(Exception e)
should be invoked when the custom authenticator authentication has failed.
Example code - for custom authenticator authentication action implementation
public class BasicCustomAuthAuthenticationAction implements OneginiCustomAuthAuthenticationAction {
public static OneginiCustomAuthAuthenticationCallback CALLBACK;
private final Context context;
public BasicCustomAuthAuthenticationAction(final Context context) {
this.context = context;
}
@Override
public void finishAuthentication(final OneginiCustomAuthAuthenticationCallback callback, final String s) {
CALLBACK = callback;
final Intent intent = new Intent(context, BasicAuthenticatorAuthenticationActivity.class);
context.startActivity(intent);
}
}
Authenticator handlers
The Android SDK provides two interfaces: OneginiCustomAuthenticationRequestHandler
for regular authentication, and OneginiMobileAuthWithPushCustomRequestHandler
for push authentication.
You will need to provide OneginiCustomAuthenticationRequestHandler
and OneginiMobileAuthWithPushCustomRequestHandler
to the OneginiClientBuilder
instance, together with the OneginiCustomAuthenticator
implementation, as shown below:
Example code - supply custom authenticator to the Android SDK
new OneginiClientBuilder(applicationContext, registrationRequestHandler, createPinRequestHandler, pinAuthenticationRequestHandler)
.setCustomAuthenticationRequestHandler(new BasicCustomAuthenticationRequestHandler(context))
.setMobileAuthWithPushCustomRequestHandler(new MobileAuthenticationBasicCustomRequestHandler(applicationContext))
.addCustomAuthenticator(new BasicCustomAuthenticator(applicationContext))
.build();
The OneginiCustomAuthenticationRequestHandler
interface exposes two methods you should use to control the process of authenticating the user:
-
void startAuthentication(UserProfile userProfile, OneginiCustomCallback callback)
is triggered when new custom authentication request is made, providing theUserProfile
object and the result callback. -
void finishAuthentication()
is triggered when custom authentication finished either with a success or an error.
OneginiMobileAuthWithPushCustomRequestHandler
works in exactly the same manner with a single change in the parameters of the startAuthentication()
method, where instead of the UserProfile
you get the OneginiMobileAuthenticationRequest
object containing information about the push request as well as the UserProfile
.
To control the flow of custom authentication, you should use the provided OneginiCustomCallback
. It consists of three methods:
-
acceptAuthenticationRequest()
should be called when the user accepts the custom authentication request. -
denyAuthenticationRequest()
should be triggered when the user denies the custom authentication request. -
fallbackToPin()
should be invoked when the user decides to resign from the custom authentication and wants to enter their PIN to finish the authentication.
Example code - for OneginiCustomAuthenticationRequestHandler implementation
public class BasicCustomAuthenticationRequestHandler implements OneginiCustomAuthenticationRequestHandler {
public static OneginiCustomCallback CALLBACK;
private final Context context;
private String userProfileId;
public BasicCustomAuthenticationRequestHandler(final Context context) {
this.context = context;
}
@Override
public void startAuthentication(final UserProfile userProfile, final OneginiCustomCallback oneginiCustomCallback) {
CALLBACK = oneginiCustomCallback;
userProfileId = userProfile.getProfileId();
notifyActivity(COMMAND_START);
}
@Override
public void finishAuthentication() {
notifyActivity(COMMAND_FINISH);
}
private void notifyActivity(final String command) {
final Intent intent = new Intent(context, CustomAuthActivity.class);
intent.putExtra(EXTRA_COMMAND, command);
intent.putExtra(EXTRA_USER_PROFILE_ID, userProfileId);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
Custom authenticator registration and authentication
To register the custom authenticator or authenticate with it, you should use the same methods like for other OneginiAuthenticators
: UserClient#registerAuthenticator(final OneginiAuthenticator authenticator, final OneginiAuthenticatorRegistrationHandler handler)
, UserClient#authenticateUser(final UserProfile userProfile, final OneginiAuthenticationHandler authenticationHandler)
, or UserClient#authenticateUser(final UserProfile userProfile, final OneginiAuthenticator oneginiAuthenticator, final OneginiAuthenticationHandler authenticationHandler)
.
Example code - custom authenticator registration
//to register the custom authenticator
OneginiSDK.getOneginiClient(context).getUserClient().registerAuthenticator(customAuthenticator, new OneginiAuthenticatorRegistrationHandler() {
@Override
public void onSuccess(final CustomInfo customInfo) {
// successfully registered authenticator
}
@Override
public void onError(final OneginiAuthenticatorRegistrationError oneginiAuthenticatorRegistrationError) {
// handle different errors
}
});
The only difference between the custom authenticator and other authenticator types (PIN, Fingerprint) is that in case of a custom authenticator, you can receive a non-null info object CustomInfo
in both successful and failed registration or authentication attempts. In case of a success, the object is passed as a param in the onSuccess()
method of the registration/authentication
attempt. In case of an error, the info object can be received by calling getErrorDetails().getCustomInfo()
methods on the error object.
The CustomInfo
contains the status code and the data that were returned by the registration or authentication script implemented in the extension engine.
new OneginiAuthenticationHandler() {
@Override
public void onSuccess(final UserProfile userProfile, final CustomInfo customInfo) {
// customInfo passed as a param
}
@Override
public void onError(final OneginiAuthenticationError oneginiAuthenticationError) {
// customInfo passed through error object
oneginiAuthenticationError.getErrorDetails().getCustomInfo();
}
};
Authenticate user with biometrics
Biometric authentication allows users to perform authentication by using a fingerprint or face recognition. Users are now enabled to access their sensitive data or authenticate transactions using Android's common biometric validation.
The Android SDK allows you to authenticate users with the biometric features of a user's device. You can use it for both regular authentication as well as mobile authentication. Users will be able to attempt biometric scanning as many times as the Android system will allow them to. If the Android Biometric API returns an error for any reason (for example when too many failed attempts are detected), the Android SDK will revoke biometric authentication and perform a fallback to PIN authentication.
Availability
A user can enable biometric authentication only if all of the following requirements are met:
- the device is not rooted
- the device supports Class 3 Biometrics (as defined by Android)
- the user has already enrolled biometrics on the device (that is, registered at least one fingerprint)
- the client configuration on the IDAAS-core allows using fingerprint authentication
Root detection
The root detection check is applied during the biometric authentication, even if root detection is disabled for the application itself. Rooted devices are more vulnerable as both the application sandbox and AndroidKeystore
can be violated.
Biometrics changes
Keys stored in the AndroidKeyStore
will become permanently invalidated once a new biometric enrollment is added or all enrollments are removed. In such case, the Android SDK will deregister biometric authenticator and the user will have to enroll for biometric authentication again.
Authenticate using Biometric Authentication
When biometric authentication is enabled and the device is not rooted, the user will be prompted to scan their finger instead of providing their PIN to authenticate. The Android SDK will use the OneginiBiometricAuthenticationRequestHandler
interface to ask for a fingerprint. The handler should show a Biometric Prompt and include BiometricPrompt.CryptoObject
. CryptoObject
is used for cryptography purposes and allows the Android SDK to use a biometric refresh token to authenticate the user.
If the biometric scan fails for any reason, the user can always choose to fall back on PIN authentication. After failing to scan a fingerprint for the allowed number of times, the Android SDK will automatically fall back on the PIN authentication method. This can be done by calling the OneginiBiometricCallback#fallbackToPin()
method.
Using biometric authentication with multiple user profiles
The Android SDK v5.03.00 introduced support for multiple user profiles. When this feature is implemented, a user will be able to create and use different accounts (profiles) on the same device. Each profile has its own separate PIN, push notification support, and other features.
If the application supports both the multiple profiles feature and biometric authentication, you should keep in mind that it has some (potential) drawbacks.
Let's assume that Bob is the owner of a device, but he shares it with his wife Alice. They have both registered their fingerprints and they both created profile in the ExampleApp. When the ExampleApp asks Bob for a fingerprint for login or to confirm a transaction, any valid (registered) fingerprint will be accepted. Because of that, Alice can log in to Bob's account using her fingerprint. When Bob tries to authenticate with a fingerprint, but the exceeds number of failed attempts, the Android Biometric API can be blocked automatically for some amount of time (around 15-30 seconds). If Alice tries to log in to her account shortly after that, she might not be able to do so before the API is unblocked. When the server detects improper usage of a biometric refresh token that indicates a corrupted or modified biometric keystore, all biometric refresh tokens on this device will be revoked on the server side.
Enabling biometric authentication
To enable biometric authentication for a user, you need to request a list of not yet registered authenticators with UserClient.getNotRegisteredAuthenticators(final UserProfile userProfile)
. This method can be used after the user is authenticated and will return a set of OneginiAuthenticator
that are possible to register. Then you can register the chosen authenticator providing the authenticator's instance to UserClient.registerAuthenticator(final OneginiAuthenticator authenticator, final OneginiAuthenticatorRegistrationHandler handler)
method, along with the handler that will inform you about the success or failure of the registration process. If your device hasn't met one of the requirements, the biometric authenticator won't be present in the list of authenticators.
Example code for registering a biometric authenticator
UserProfile authenticatedUserProfile = OneginiSDK.getOneginiClient(context).getUserClient().getAuthenticatedUserProfile();
if (authenticatedUserProfile == null){
return;
}
OneginiAuthenticator biometricAuthenticator;
// to get the biometric authenticator
final Set<OneginiAuthenticator> notRegisteredAuthenticators = OneginiSDK.getOneginiClient(context).getUserClient()
.getNotRegisteredAuthenticators(authenticatedUserProfile);
for (final OneginiAuthenticator auth : notRegisteredAuthenticators) {
if (auth.getType() == OneginiAuthenticator.BIOMETRIC){
// the fingerprint authenticator is available for registration
biometricAuthenticator = auth;
}
}
// biometric authentication is not available for all devices
if (biometricAuthenticator == null) {
return;
}
//register the authenticator
OneginiSDK.getOneginiClient(context).getUserClient()
.registerAuthenticator(biometricAuthenticator, new OneginiAuthenticatorRegistrationHandler() {
@Override
public void onSuccess(final CustomInfo customInfo) {
// successfully registered authenticator
}
@Override
public void onError(
final OneginiAuthenticatorRegistrationError oneginiAuthenticatorRegistrationError){
// handle error that might occur
}
});
val authenticatedUserProfile = OneginiSDK.getOneginiClient(context).userClient
.authenticatedUserProfile ?: return
var biometricAuthenticator: OneginiAuthenticator = OneginiSDK.getOneginiClient(context).userClient
.getNotRegisteredAuthenticators(authenticatedUserProfile)
.find { it.type == OneginiAuthenticator.BIOMETRIC } ?: return // biometric authentication is not available for all devices
//register biometric authenticator
OneginiSDK.getOneginiClient(context).userClient.registerAuthenticator(biometricAuthenticator, object : OneginiAuthenticatorRegistrationHandler {
override fun onSuccess(customInfo: CustomInfo?) {
// successfully registered authenticator
}
override fun onError(oneginiAuthenticatorRegistrationError: OneginiAuthenticatorRegistrationError) {
// handle error that might occur
}
})
Disabling biometric authentication
To disable biometric authentication for the currently authenticated user profile, call deregisterAuthenticator(final OneginiAuthenticator authenticator, final OneginiAuthenticatorDeregistrationHandler handler)
on the UserClient
instance. The method will revoke user's biometric refresh token on both the client and server side.
The user will still be able to log in using their PIN.
Authenticator handlers
The Android SDK provides two OneginiCustomAuthenticationRequestHandler
interfaces for regular authentication and OneginiMobileAuthWithPushCustomRequestHandler
for push authentication.
You will need to provide OneginiCustomAuthenticationRequestHandler
and OneginiMobileAuthWithPushCustomRequestHandler
to the OneginiClientBuilder
instance, together with the OneginiCustomAuthenticator
implementation, as shown below:
Example code: supply a biometric authenticator
OneginiClientBuilder(applicationContext, createPinRequestHandler, pinAuthenticationRequestHandler)
.setBrowserRegistrationRequestHandler(registrationRequestHandler)
.setMobileAuthWithPushRequestHandler(new MobileAuthWithPushRequestHandler(applicationContext))
.setMobileAuthWithPushPinRequestHandler(new MobileAuthWithPushPinRequestHandler(applicationContext))
.setMobileAuthWithPushBiometricRequestHandler(
new MobileAuthWithPushBiometricRequestHandler(applicationContext))
.setBiometricAuthenticationRequestHandler(
new BiometricAuthenticationRequestHandler(applicationContext))
.build();
OneginiClientBuilder(applicationContext, createPinRequestHandler, pinAuthenticationRequestHandler)
.setBrowserRegistrationRequestHandler(registrationRequestHandler)
.setMobileAuthWithPushRequestHandler(MobileAuthWithPushRequestHandler(applicationContext))
.setMobileAuthWithPushPinRequestHandler(MobileAuthWithPushPinRequestHandler(applicationContext))
.setMobileAuthWithPushBiometricRequestHandler(
MobileAuthWithPushBiometricRequestHandler(applicationContext))
.setBiometricAuthenticationRequestHandler(BiometricAuthenticationRequestHandler(applicationContext))
.build()
The OneginiBiometricAuthenticationRequestHandler
interface exposes two methods that are used to control the process of biometric authentication:
-
startAuthentication(final UserProfile userProfile, BiometricPrompt.CryptoObject cryptoObject, final OneginiBiometricCallback callback)
is triggered when a new fingerprint authentication request is made, providing anUserProfile
object, a biometric crypto object that has to be authenticated, and a biometric callback. -
finishAuthentication()
is triggered when biometric authentication finished either with success or an error.
The OneginiMobileAuthWithPushFingerprintRequestHandler
works in exactly the same manner with a single change in the parameters of the startAuthentication()
method, where instead of the UserProfile
you get the OneginiMobileAuthenticationRequest
object containing information about the push request as well as the UserProfile
.
Example code for OneginiBiometricAuthenticationRequestHandler implementation
public class BiometricAuthenticationRequestHandler implements OneginiBiometricAuthenticationRequestHandler {
public static OneginiBiometricCallback CALLBACK;
public static BiometricPrompt.CryptoObject CRYPTO_OBJECT;
private final Context context;
private String userProfileId;
public BiometricAuthenticationRequestHandler(final Context context) {
this.context = context;
}
@Override
public void startAuthentication(
@NonNull UserProfile userProfile,
@NonNull BiometricPrompt.CryptoObject cryptoObject,
@NonNull OneginiBiometricCallback oneginiBiometricCallback) {
CALLBACK = oneginiBiometricCallback;
CRYPTO_OBJECT = cryptoObject;
userProfileId = userProfile.getProfileId();
notifyActivity(COMMAND_START);
}
@Override
public void finishAuthentication() {
CRYPTO_OBJECT = null;
CALLBACK = null;
notifyActivity(COMMAND_FINISH);
}
private void notifyActivity(final String command) {
final Intent intent = new Intent(context, BiometricAuthenticationActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(EXTRA_COMMAND, command);
intent.putExtra(EXTRA_USER_PROFILE_ID, userProfileId);
context.startActivity(intent);
}
}
class BiometricAuthenticationRequestHandler(private val context: Context) : OneginiBiometricAuthenticationRequestHandler {
private var userProfileId: String? = null
override fun startAuthentication(
userProfile: UserProfile,
cryptoObject: BiometricPrompt.CryptoObject,
oneginiBiometricCallback: OneginiBiometricCallback
) {
CALLBACK = oneginiBiometricCallback
CRYPTO_OBJECT = cryptoObject
userProfileId = userProfile.profileId
notifyActivity(Constants.COMMAND_START)
}
override fun finishAuthentication() {
CRYPTO_OBJECT = null
CALLBACK = null
notifyActivity(Constants.COMMAND_FINISH)
}
private fun notifyActivity(command: String) {
val intent = Intent(context, BiometricAuthenticationActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
intent.putExtra(Constants.EXTRA_COMMAND, command)
intent.putExtra(AuthenticationActivity.EXTRA_USER_PROFILE_ID, userProfileId)
context.startActivity(intent)
}
companion object {
var CALLBACK: OneginiBiometricCallback? = null
var CRYPTO_OBJECT: BiometricPrompt.CryptoObject? = null
}
}
OneginiBiometricCallback
To control the flow of biometric authentication you should use the OneginiBiometricCallback
callback. It consists of four methods:
-
userAuthenticatedSuccessfully()
should be called when a user successfully authenticated using a Biometric Prompt. -
denyAuthenticationRequest
should be triggered when a user denies the biometric authentication request. -
fallbackToPin
should be invoked when a user decides to resign from biometric authentication and wants to enter their PIN to finish authentication. -
onBiometricAuthenticationError(final int errorCode)
should be called when the Biometric Prompt returned an error. TheerrorCode
parameter represents the code returned from the Biometric Prompt. Based on the code, the Android SDK will perform the necessary actions, such as when abuse is detected, biometric authenticator will be deregistered for all users.
In the example code above you should use the static instance of OneginiBiometricCallback
in the BiometricActivity
and BiometricPrompt.CryptoObject
to react to user actions.
Showing biometric prompt
The Android SDK depends on the androidx.biometric:biometric
library to provide this functionality. When implementing a BiometricPrompt
, it's important to import classes from the androidx.biometric
package and not from android.hardware.biometrics
. For the library version used by the Android SDK, check third-party libraries.
To perform biometric authentication you first need to build the PromptInfo
with the correct parameters:
final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("Your title")
.setSubtitle("Your subtitle")
.build();
val promptInfo = PromptInfo.Builder()
.setTitle("Your title")
.setSubtitle("Your subtitle")
.build();
and then display the prompt to the user.
final Executor executor = ContextCompat.getMainExecutor(context);
final BiometricPrompt biometricPrompt =
new BiometricPrompt(context, executor, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
BiometricAuthenticationRequestHandler.CALLBACK.onBiometricAuthenticationError(errorCode);
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
BiometricAuthenticationRequestHandler.CALLBACK.userAuthenticatedSuccessfully();
}
});
biometricPrompt.authenticate(promptInfo, BiometricAuthenticationRequestHandler.CRYPTO_OBJECT);
val executor = ContextCompat.getMainExecutor(context)
val biometricPrompt = BiometricPrompt(context, executor, object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
BiometricAuthenticationRequestHandler.CALLBACK.onBiometricAuthenticationError(errorCode)
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
BiometricAuthenticationRequestHandler.CALLBACK.userAuthenticatedSuccessfully()
}
})
biometricPrompt.authenticate(promptInfo, BiometricAuthenticationRequestHandler.CRYPTO_OBJECT)
In the authenticate method of the BiometricPrompt
, you need to provide the previously built PromptInfo
object as well as the BiometricPrompt.CryptoObject
you received in the OneginiBiometricAuthenticationRequestHandler.startAuthentication(final UserProfile userProfile, BiometricPrompt.CryptoObject cryptoObject, final OneginiBiometricCallback callback)
method.
BiometricPrompt
required also a callback object in which you will receive the authentication result. This result needs to be passed back to the Android SDK via OneginiBiometricCallback
to finish the process.