Authenticate user with PIN

Determine if we can login user

The UserClient contains the getUserProfiles method which returns set of all registered UserProfile value objects. You can check size of that set to determine if there is need to register new user or if there is possibility to login. If the method isRegisteredAtLeastOneUser from the example below will return false you can assume that no user is authenticated on the device. In that case user have to register before logging in.

Example code

private boolean isRegisteredAtLeastOneUser() {
  final Set<UserProfile> userProfiles = OneginiSDK.getOneginiClient(this).getUserClient().getUserProfiles();
  return userProfiles.size() > 0;
}

You can also use UserClient#isUserRegistered() method in order to check if specified user is registered on the device.

Login registered user

When at least one user has already registered there is possibility to log in that user using authenticateUser method from the UserClient. This method requires two arguments:

  • UserProfile the UserProfile ValueObject that we want to authenticate,
  • OneginiAuthenticationHandler the authentication handler to return the authentication result to.

The result of authentication is an access token with optionally a refresh token. The authentication handler contains:

  • an onSuccess method which lets you know that authentication was finished successfully - at this point, you can request data on behalf of the user,
  • an onError method which is called in every other case.

You can find more information about authentication in user authentication section.

PIN request handlers

At this point the app would crash with a NullPointerException due to the lack of PIN request handlers for creating and verifying a PIN. To prevent that 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

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 SDK whenever there will be a need to create a new PIN (during registration, or during the change pin action). You have to call OneginiPinCallback#acceptAuthenticationRequest(pinEnteredByUser) in order to successfully finish PIN creation process. The pinLength parameter determines the required pin length. In order to cancel the PIN creation process you can call the OneginiPinCallback#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 the OneginiPinValidationError#getErrorType() value with the OneginiPinValidationError's static fields. Please note that there are also the OneginiPinValidationError#getMessage() and OneginiPinValidationError#getCause() methods which can return more information about the error.
  • finishPinCreation() is called whenever correct PIN was created or the action was canceled by the user. Below is sample code for creating a custom PIN request handler. In this example we created an additional PinWithConfirmationHandler 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 which 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);
  }
}

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

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 SDK whenever there will be a to need authenticate the user. You have to call OneginiPinCallback#acceptAuthenticationRequest(pinEnteredByUser) in order to successfully finish the PIN creation process or OneginiPinCallback#denyAuthenticationRequest() to cancel it.
  • onNextAuthenticationAttempt(final AuthenticationAttemptCounter attemptCounter) - this method is called when the PIN that is provided by the user is incorrect, but his failed attempts limit hasn't been reached yet. The method's parameter is AuthenticationAttemptCounter 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 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);
  }
}

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