Mobile authentication with Push

Two factor authentication can be implemented using GCM push notifications. It's a very secure and user friendly way of authentication. You can use this feature to allow users to confirm their transactions. Confirmation can be done using a simple Accept button, PIN, Fingerprint or a FIDO authenticator. All transaction information is encrypted and no sensitive information is sent through GCM.

Configuration

In order to use mobile authentication with push notifications the device has to support Google Play Services. It's your responsibility to check if push notifications are supported on the device. Once push notifications are available on the device the app should fetch the registration id, which is used as a push token during the mobile authentication with push enrollment. Please refer to the Google documentation on how to obtain this registration id. In order to use push mobile authentication with additional fingerprint verification you need to make sure that device has a fingerprint scanner and it fulfills all fingerprint requirements.

You also need to set up GCM in the Google Console and the Token Server as described in the Google documentation and Token Server documentation.

Enrollment

After you've set up GCM and enrolled the used for mobile authentication, you need to perform one additional enrollment step to allow your users to perform mobile authentication with push notifications. The user can be enrolled on only a single device (application instance) at a time. If the user has two mobile devices on which the application is installed, the user can only enroll for mobile authentication with push on one of these devices. All of the GCM communication must be handled by your application. This includes fetching the registration id. The reason why this responsibility is in the app is that one app can only have one registration id. This allows the app to support more push messaging features besides mobile authentication provided the SDK.

To check if a particular user profile has already enrolled for mobile authentication with push the SDK exposes the method: UserClient#isUserEnrolledForMobileAuthWithPush(UserProfile userProfile). Please note, that this method checks if the user has enrolled for push enrollment in the past and that the user still has valid PGP keys stored on the device. Hoverer, the method doesn't have an ability to check if the registration ID used for enrollment with GCM is still valid or not.

Example code to initialize mobile authentication with push enrollment for the currently authenticated user

final UserClient userClient = OneginiSDK.getOneginiClient(context).getUserClient();
userClient.enrollUserForMobileAuthWithPush("myRegistrationId", new OneginiMobileAuthWithPushEnrollmentHandler() {

    @Override
    public void onSuccess() {
      showToast("Mobile authentication enabled");
    }

    @Override
    public void onError(final OneginiMobileAuthWithPushEnrollmentError error) {
      @OneginiMobileAuthWithPushEnrollmentError.MobileAuthWithPushEnrollmentErrorType final int errorType = error.getErrorType();
      if (errorType == OneginiMobileAuthWithPushEnrollmentError.DEVICE_DEREGISTERED) {
        new DeregistrationUtil(SettingsActivity.this).onDeviceDeregistered();
      }

      showToast("Mobile authentication error - " + error.getMessage());
    }
);

Request handling

Once a user is enrolled, he/she should be able to receive push mobile authentication requests. To verify if the user is already enrolled for push mobile authentication, you should use the UserClient#isUserEnrolledForMobileAuthWithPush(UserProfile userProfile) method.

The entire push mobile authentication process from can be described as follows. In this flow we call the initiator of the mobile authentication request 'portal':

1. Portal -> Token Server: Initialize mobile authentication.
2. Token Server -> GCM: Send the push notification.
3. GCM -> Token Server: Push notification delivery report.
4. Token Server -> Portal: Identifier of the initialized mobile authentication transaction.
5. GCN -> APP: push notification.
6. APP -> SDK: Delegate handling of the push notification to the SDK.
7. SDK -> Token Server: Fetch PGP encrypted message from server.
8. Token Server -> SDK: Base64 encoded PGP encrypted authentication data.
9. SDK -> APP: The push message is decrypted and the SDK triggers a request handler so the app can show 
        a dialogue to the end-user.
10. APP -> SDK: Application responds with the authentication result.
11. SDK -> Token Server: The authentication result is send encrypted to the Token Server.
12. Token Server -> SDK: If the used push type requires feedback on the result e.g. pin usage 
        the result is communicated back to the SDK which will optionally perform a retry.
13. Token Server -> Portal: A callback is sent to inform the portal about the mobile 
        authentication result.

As you can see from the diagram above, the application has the following responsibilities:

  1. Passing push notification received from GCM to the SDK
  2. Responding to authentication requests using the *RequestHandler interfaces
    1. Displaying a dialog to the end-user when the authentication challenge is received
    2. Sending the users' response back to the SDK
  3. Handling the completion of the mobile authentication request

The Following paragraphs explain those steps and show how the responsibilities mentioned above can be implemented.

Passing a push notification (containing a mobile authentication request) to the SDK

Push notifications are handled by the application that delegates the handling of the push notification to the SDK. The SDK will determine if it should do something with the push notification or not before handling it. This allows the app to support multiple types of push notifications next to the mobile authentication push notification. You can trigger it with handleMobileAuthenticationRequest() on the UserClient instance.

public void handleMobileAuthWithPushRequest(final Bundle pushMessage, final OneginiMobileAuthenticationHandler mobileAuthenticationHandler);

Then the SDK will use OneginiMobileAuthenticationHandler to perform the request and end in one of the two methods of OneginiMobileAuthenticationHandler:

  • Mobile Authentication was successful

    void onSuccess();
    
  • Mobile Authentication failed due to an error

    void onError(OneginiMobileAuthenticationError error);
    

    Please note, that the user has an ability to deny incoming mobile authentication request. In such case the onError method will be called with an ACTION_CANCELED error type.

For more info on error handling see the error handling topic guide.

Example code push notification handling

@Override
protected void onHandleIntent(final Intent intent) {
  final Bundle extras = intent.getExtras();
  if (!extras.isEmpty()) {
    OneginiSDK.getOneginiClient(this).getUserClient().handleMobileAuthWithPushRequest(extras, new OneginiMobileAuthenticationHandler() {
      @Override
      public void onSuccess() {
        // Method called when mobile authentication request was successful.
      }

      @Override
      public void onError(final OneginiMobileAuthenticationError oneginiMobileAuthenticationError) {
        Toast.makeText(GCMListenerService.this, oneginiMobileAuthenticationError.getMessage(), Toast.LENGTH_SHORT).show();
        @OneginiMobileAuthenticationError.MobileAuthenticationEnrollmentErrorType final int errorType = oneginiMobileAuthenticationError.getErrorType();
        if (errorType == OneginiMobileAuthenticationError.USER_DEREGISTERED) {
          // the user was deregister, for example he provided a wrong PIN for too many times. You can handle the deregistration here, but since this application
          // supports multiple profiles we handle it when the user tries to login the next time because we don't know which user profile was deregistered at
          // this point.
        } else if (errorType == OneginiMobileAuthenticationError.DEVICE_DEREGISTERED) {
          new DeregistrationUtil(getApplicationContext()).onDeviceDeregistered();
        }
      }
    });
  }
}

As you can see from example above when user got deregistered an error is reported to the app through onError method. To enable mobile authentication for that user he needs to register and enroll once again.

Types of mobile authentication with push

You can configure different types of mobile authentication with push in the Token Server mobile authentication configuration. For every type you can choose between different methods of authentication. These are: Push, Push with PIN, Push with fingerprint and Push with FIDO. The types of for mobile authentication must be configured on the Token Server first before you can use them. You can define multiple types with the same authentication method. Each type of authentication need a proper handler implementation to be provided in the SDK builder, as described below.

Push

The first type of mobile authentication request, the most simple one, is Push. In this type user have possibility to simply accept or deny request. To support the Push type in your app you need to implement OneginiMobileAuthWithPushRequestHandler interface and set its instance during the SDK initialization step by calling setMobileAuthWithPushRequestHandler(OneginiMobileAuthWithPushRequestHandler handler) method on OneginiClientBuilder. Then you need to handle all interface methods in order to show/hide user interface with (for example) buttons allowing to accept or deny request.

Example how to set OneginiMobileAuthWithPushRequestHandler

new OneginiClientBuilder(applicationContext, createPinRequestHandler, pinAuthenticationRequestHandler)
  .setMobileAuthWithPushRequestHandler(new MobileAuthenticationRequestHandler(applicationContext))
  .build();

Example how to implement OneginiMobileAuthWithPushRequestHandler

public class MobileAuthenticationRequestHandler implements OneginiMobileAuthWithPushRequestHandler {

  public static OneginiAcceptDenyCallback CALLBACK;

  private final Context context;

  public MobileAuthenticationRequestHandler(final Context context) {
    this.context = context;
  }

  @Override
  public void startAuthentication(final OneginiMobileAuthenticationRequest oneginiMobileAuthenticationRequest,
                                  final OneginiAcceptDenyCallback oneginiAcceptDenyCallback) {
    CALLBACK = oneginiAcceptDenyCallback;
    openActivity(oneginiMobileAuthenticationRequest.getUserProfile().getProfileId(), oneginiMobileAuthenticationRequest.getMessage());
  }

  @Override
  public void finishAuthentication() {
    closeActivity();
  }

  private void openActivity(final String profileId, final String message) {
    final Intent intent = new Intent(context, MobileAuthenticationActivity.class);
    intent.putExtra(MobileAuthenticationActivity.EXTRA_COMMAND, MobileAuthenticationActivity.COMMAND_START);
    intent.putExtra(MobileAuthenticationActivity.EXTRA_PROFILE_ID, profileId);
    intent.putExtra(MobileAuthenticationActivity.EXTRA_MESSAGE, message);
    intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
  }

  private void closeActivity() {
    final Intent intent = new Intent(context, MobileAuthenticationActivity.class);
    intent.putExtra(MobileAuthenticationActivity.EXTRA_COMMAND, MobileAuthenticationActivity.COMMAND_FINISH);
    intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
  }
}

You need to implement both startAuthentication and finishAuthentication methods:

  • startAuthentication(final OneginiMobileAuthenticationRequest oneginiMobileAuthenticationRequest, final OneginiAcceptDenyCallback oneginiAcceptDenyCallback) is called when new mobile authentication request is received. First parameter is OneginiMobileAuthenticationRequest object which contains message you should present to the user and UserProfile object that specifies request's receiver. The Second parameter is a callback object that you should use when user accepts or denies the request, by calling acceptAuthenticationRequest or denyAuthenticationRequest respectively.
  • finishAuthentication method is called after end of processing the user response to the request.

Push with PIN

The Second type of mobile authentication request is Push with PIN. In this type the user also have a possibility to accept or deny request. Comparing to simple Push the Push with PIN requires the user to enter his PIN in order to successfully accept the mobile authentication request. To support Push with PIN type you need to implement the OneginiMobileAuthWithPushPinRequestHandler interface and set its instance during SDK initialization step by calling the setMobileAuthWithPushPinRequestHandler(OneginiMobileAuthWithPushPinRequestHandler handler) method on the OneginiClientBuilder. You also need to handle all interface methods in order to show/hide user interface with a PIN input and buttons allowing to accept or deny request.

Example how to implement OneginiMobileAuthWithPushPinRequestHandler


public class MobileAuthWithPushPinRequestHandler implements OneginiMobileAuthWithPushPinRequestHandler {

  public static OneginiPinCallback CALLBACK;

  private final Context context;

  private String message;
  private String userProfileId;
  private int failedAttemptsCount;
  private int maxAttemptsCount;

  public MobileAuthWithPushPinRequestHandler(final Context context) {
    this.context = context;
  }

  @Override
  public void startAuthentication(final OneginiMobileAuthenticationRequest oneginiMobileAuthenticationRequest, final OneginiPinCallback oneginiPinCallback, final AuthenticationAttemptCounter attemptCounter) {
    CALLBACK = oneginiPinCallback;
    message = oneginiMobileAuthenticationRequest.getMessage();
    userProfileId = oneginiMobileAuthenticationRequest.getUserProfile().getProfileId();
    failedAttemptsCount = maxAttemptsCount = 0;
    startActivity();
  }

  @Override
  public void onNextAuthenticationAttempt(final AuthenticationAttemptCounter attemptCounter) {
    this.failedAttemptsCount = attemptCounter.getFailedAttempts();
    this.maxAttemptsCount = attemptCounter.getMaxAttempts();
    startActivity();
  }

  @Override
  public void finishAuthentication() {
    closeActivity();
  }

  private void startActivity() {
    final Intent intent = new Intent(context, MobileAuthenticationPinActivity.class);
    intent.putExtra(MobileAuthenticationPinActivity.EXTRA_COMMAND, MobileAuthenticationPinActivity.COMMAND_START);
    intent.putExtra(MobileAuthenticationPinActivity.EXTRA_MESSAGE, message);
    intent.putExtra(MobileAuthenticationPinActivity.EXTRA_PROFILE_ID, userProfileId);
    intent.putExtra(MobileAuthenticationPinActivity.EXTRA_FAILED_ATTEMPTS_COUNT, failedAttemptsCount);
    intent.putExtra(MobileAuthenticationPinActivity.EXTRA_MAX_FAILED_ATTEMPTS, maxAttemptsCount);
    intent.addFlags(FLAG_ACTIVITY_NEW_TASK);

    context.startActivity(intent);
  }

  private void closeActivity() {
    final Intent intent = new Intent(context, MobileAuthenticationPinActivity.class);
    intent.putExtra(MobileAuthenticationPinActivity.EXTRA_COMMAND, MobileAuthenticationPinActivity.COMMAND_FINISH);
    intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
  }
}

You need to implement the startAuthentication, onNextAuthenticationAttempt and finishAuthentication methods:

  • startAuthentication(final OneginiMobileAuthenticationRequest oneginiMobileAuthenticationRequest, final OneginiPinCallback oneginiPinCallback, final AuthenticationAttemptCounter attemptCounter) the app receives new mobile authentication request. First parameter is OneginiMobileAuthenticationRequest object which contains message you should present to the end user, UserProfile object that specifies request's receiver. Second parameter is a callback object you should use when user accepts or denies incoming request by calling: acceptAuthenticationRequest(pinEnteredByUser) or denyAuthenticationRequest respectively. Third parameter is AuthenticationAttemptCounter that holds information about failed, remaining and max PIN attempts.
  • onNextAuthenticationAttempt(final AuthenticationAttemptCounter attemptCounter) is called when user entered wrong pin. The attemptCounter param object gives you the information about failed attempts count via getFailedAttempts() method indicating how many times wrong PIN was entered during current authentication attempt. Same way you can get max attempts with getMaxAttempts() method and remaining attempts with getRemainingAttempts(). You can display those values to the user.
  • finishAuthentication is called after end of processing the user response to the request.

Push with fingerprint

The third type of mobile authentication request is Push with fingerprint. In this type the user also have a possibility to accept or deny request. The Push with fingerprint requires the user to scan his fingerprint in order to successfully accept the mobile authentication request. To support Push with fingerprint type you need to implement the OneginiMobileAuthWithPushFingerprintRequestHandler interface and set its instance during SDK initialization step by calling the setMobileAuthWithPushFingerprintRequestHandler(OneginiMobileAuthWithPushFingerprintRequestHandler mobileAuthenticationFingerprintRequestHandler) method on the OneginiClientBuilder. You also need to handle all interface methods in order to show/hide user interface with a fingerprint input and buttons allowing the end-user to accept or deny the mobile authentication request as well as perform a fallback to PIN.

Example how to implement OneginiMobileAuthWithPushFingerprintRequestHandler

public class MobileAuthWithPushFingerprintRequestHandler implements OneginiMobileAuthWithPushFingerprintRequestHandler {

  public static OneginiFingerprintCallback fingerprintCallback;
  private final Context context;

  public MobileAuthWithPushFingerprintRequestHandler(final Context context) {
    this.context = context;
  }

  @Override
  public void startAuthentication(final OneginiMobileAuthenticationRequest oneginiMobileAuthenticationRequest, final OneginiFingerprintCallback oneginiFingerprintCallback) {
    fingerprintCallback = oneginiFingerprintCallback;
    startFingerprintActivity(MSG_EXTRA_ASK_TO_ACCEPT_OR_DENY);
  }

  @Override
  public void onNextAuthenticationAttempt() {
    startFingerprintActivity(MSG_EXTRA_RECEIVED_FINGERPRINT);
  }

  @Override
  public void onFingerprintCaptured() {
    startFingerprintActivity(MSG_EXTRA_SHOW_SCANNING);
  }

  @Override
  public void finishAuthentication() {
    startFingerprintActivity(MSG_EXTRA_CLOSE);
  }

  private void startFingerprintActivity(final String action) {
    final Intent intent = new Intent(context, MobileAuthenticationFingerprintActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
    intent.putExtra(FingerprintActivity.MSG_EXTRA_ACTION, action);
    context.startActivity(intent);
  }
}

You need to implement the startAuthentication, onNextAuthenticationAttempt, onFingerprintCaptured and finishAuthentication methods:

  • startAuthentication(final OneginiMobileAuthenticationRequest oneginiMobileAuthenticationRequest, final OneginiFingerprintCallback oneginiFingerprintCallback) the app receives new mobile authentication request. First parameter is OneginiMobileAuthenticationRequest object which contains message you should present to the end user, UserProfile object that specifies request's receiver. Second parameter is a callback object you should use when user accepts or denies incoming request by calling: acceptAuthenticationRequest(pinEnteredByUser) or denyAuthenticationRequest respectively. It also contains fallbackToPin() method that should be used when user decides not to scan the fingerprint but use PIN.
  • onNextAuthenticationAttempt() is called when user's fingerprint scan was verified as invalid. Note that scanner will be blocked when used will scan too many his fingerprint too many times without success. This is controlled by Android SDK and the number of attempts might differ depending on device vendor. When the attempts limit will be reached SDK will automatically fallback to PIN authentication.
  • onFingerprintCaptured() is called when fingerprint was scanned but not yet verified. You can show an information to the user that the verification process just started.
  • finishAuthentication() is called after end of processing the user response to the request.

Push with FIDO

The fourth type of mobile authentication request is Push with FIDO. In this type the user also have a possibility to accept or deny request. The Push with FIDO requires the user to authenticate. To support Push with fingerprint type you need to implement the OneginiMobileAuthenticationFidoRequestHandler interface and set its instance during SDK initialization step by calling the public OneginiClientBuilder setMobileAuthWithPushFidoRequestHandler(final OneginiMobileAuthWithPushPinRequestHandler mobileAuthenticationFidoRequestHandler) method on the OneginiClientBuilder. You also need to handle all interface methods in order to show/hide buttons allowing the end-user to accept or deny the mobile authentication request or perform a fallback to PIN.

Example how to implement OneginiMobileAuthWithPushPinRequestHandler

public class MobileAuthWithPushPinRequestHandler implements OneginiMobileAuthWithPushPinRequestHandler {

  public static OneginiFidoCallback CALLBACK;

  private String userProfileId;
  private String message;
  private final Context context;

  public MobileAuthWithPushPinRequestHandler(final Context context) {
    this.context = context;
  }

  @Override
  public void startAuthentication(final OneginiMobileAuthenticationRequest oneginiMobileAuthenticationRequest, final OneginiFidoCallback oneginiFidoCallback) {
    CALLBACK = oneginiFidoCallback;
    userProfileId = oneginiMobileAuthenticationRequest.getUserProfile().getProfileId();
    message = oneginiMobileAuthenticationRequest.getMessage();
    notifyActivity(COMMAND_START);
  }

  @Override
  public void finishAuthentication() {
    notifyActivity(COMMAND_FINISH);
  }

  private void notifyActivity(final String command) {
    final Intent intent = new Intent(context, MobileAuthenticationFidoActivity.class);
    intent.putExtra(EXTRA_COMMAND, command);
    intent.putExtra(EXTRA_USER_PROFILE_ID, userProfileId);
    intent.putExtra(EXTRA_MESSAGE, message);
    intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
  }
}

You need to implement the startAuthentication, and finishAuthentication methods:

  • startAuthentication(final OneginiMobileAuthenticationRequest oneginiMobileAuthenticationRequest, final OneginiFidoCallback oneginiFidoCallback) the app receives new mobile authentication request. First parameter is OneginiMobileAuthenticationRequest object which contains message you should present to the end user, UserProfile object that specifies request's receiver. Second parameter is a callback object you should use when user accepts or denies incoming request by calling: acceptAuthenticationRequest() or denyAuthenticationRequest() respectively. It also contains fallbackToPin() method that should be used when user decides not to scan the fingerprint but use PIN.
  • finishAuthentication() is called after end of processing the user response to the request.