Skip to content

Second Factor Management⚓︎

As discussed in Interactions, the Mobile SDK will determine when the user should present their second factor (either PIN or biometrics). However, it is also possible to trigger certain second factor interactions manually in response to user interface events, e.g. for toggling biometrics or changing the PIN.

Initial Setting of Second Factor⚓︎

This flow will be triggered by the SDK when the user is enrolling an account at a server that requires a secondary authentication factor (PIN or biometric), if the user does not have its second factor registered with the SDK.

You will receive a FlowUpdate callback for a flow with State=WAIT_FOR_USER_INPUT and CurrentInteraction.Type = SET_SECOND_FACTOR. Depending on the values present in CurrentInteraction.SecondFactorInfo.AllowedSecondFactorTypes and .RequiredSecondFactorTypes, you can request from the user all allowed second factor types, but must include the required second factor types. You can pass the user's input back to the nextAuth Mobile SDK by using the inputSecondFactor method.

Danger

When the user registers an additional biometric in the mobile OS after enabling biometrics in the nextAuth SDK, the protected biometric key will be wiped and the user will no longer be able to use their biometric in your app. On iOS, the protected biometric key will also be wiped if the user removes a biometric (i.e. any change in the current set of biometrics, whereby current should be regarded as current at the time of enabling biometric authentication in the nextAuth SDK).

NextAuth.getNextAuth().getFlowManager().inputSecondFactor(pin);
NextAuth.getNextAuth().getFlowManager().inputSecondFactor(pin, cryptoObject);
FlowService.inputSecondFactor(pin)
FlowService.inputSecondFactor(pin, and: info)

PIN⚓︎

When setting a PIN, you must make use of PIN class provided by the Mobile SDK for your platform (i.e., PinContainer on Android, and PIN on iOS). These classes have been designed to prevent that (partial) copies of the PIN remain in memory any longer than strictly necessarily. Additionally, they also implement utility functionality to check if the PIN is easy to guess and if two PINs are equal. You should ask the user to enter their chosen PIN twice and verify that both match before sending the entered code to the Mobile SDK.

int requiredLength = 4;
PinContainer pin = new PinContainer(requiredLength);
PinContainer pinCopy = new PinContainer(requiredLength);

// manipulate the PIN
pin.addDigit();
pin.removeDigit();

// check if the input is complete
pin.isComplete();

// optional, check if the PIN is not common
!pin.isCommon();

// have the user input their PIN again and check if the two PINs are equal
pin.equals(pinCopy);

// reset the PIN's value and length to zero
pin.reset()
var requiredLength = 4
var pin = PIN(requiredLength: requiredLength)
var pinCopy = PIN(requiredLength: requiredLength)

// manipulate the PIN
pin.pushDigit()
pin.popDigit()

// check if the input is complete
pin.isComplete?

// optional, check if the PIN is not common
!pin.isCommon

// have the user input their PIN again and check if the two PINs are equal
pin == pinCopy

// reset the PIN's value and length to zero
pin.reset()

Tip

Maximally avoid attackers from learning the user's PIN:

  • implement your own PIN pad (do not use the standard numeric keyboard), and
  • disable screenshots or touch event logging while the user enters their PIN.

Biometrics⚓︎

Our Android SDK requires that you request a CryptoObject from the SDK and have the user authenticate on that object using their biometric before sending it back as an input for the inputSecondFactor() method. Refer to the code snippet below to get started with this.

import androidx.biometric.BiometricPrompt;

// retrieve the cryptoObject for the user to authenticate on from the sdk
final BiometricPrompt.CryptoObject cryptoObject = NextAuth.getNextAuth().getFlowManager().getCryptoObject();

// construct the biometeric prompt
final BiometricPrompt biometricPrompt = new BiometricPrompt(this, new BiometricExecutor(), biometricAuthenticationCallback);

// show the biometric prompt to the user
runOnUiThread(() -> {
     biometricPrompt.authenticate(biometricPromptInfo, cryptoObject);
});

// set the texts for the biometric prompt
biometricPromptInfo = new BiometricPrompt.PromptInfo.Builder()
    .setTitle(...)
    .setSubtitle(...)
    .setDescription(...)
    .setNegativeButtonText(...)
    .build();

// handle the callbacks from the biometric prompt
biometricAuthenticationCallback = new BiometricPrompt.AuthenticationCallback() {
    public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
        ...
    }

    public void onAuthenticationFailed() {
        ...
    }

    public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
        super.onAuthenticationSucceeded(result);
        // cryptoObject for the sfInput method -- put your code to handle it here
        BiometricPrompt.CryptoObject cryptoObject = result.getCryptoObject();
         ...
    }
};

// biometric executor class
private static class BiometricExecutor implements Executor {
    private final Handler handler = new Handler(Looper.getMainLooper());

    public void execute(@NonNull Runnable command) {
        handler.post(command);
    }
}

On iOS, the inputSecondFactor functions which enabled biometric authentication require an evaluated LAContext, where it is crucial to pass .deviceOwnerAuthenticationWithBiometrics as the evaluated policy. We again provide some sample code below to get you started. Note that the SDK will invalidate the provided context. When flow.currentInteraction?.type == .setSecondFactor, biometrics must succeed and therefore no fallback will be shown in the biometric prompt (even if you provided one).

let context = LAContext()
context.localizedReason = NSLocalizedString("NA_SECOND_FACTOR_CONTEXT_REASON", comment: "")
context.localizedCancelTitle = NSLocalizedString("NA_SECOND_FACTOR_CONTEXT_CANCEL_TITLE", comment: "")
context.localizedFallbackTitle = NSLocalizedString("NA_SECOND_FACTOR_CONTEXT_FALLBACK_TITLE", comment: "")

if flow.currentInteraction?.type == .setSecondFactor {
    context.localizedFallbackTitle = ""
}

context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: context.localizedReason) { success, error in
    guard success else {
        // TODO: Dismiss the view in case `sfInfo.context == .biometricsAdd` and invoke `NextAuth.default.sfCancel()` to abort the pending flow, but allow the user to enter their PIN otherwise. Optionally check whether the error is of type `LAError`. For instance, the error's `code` property will be equal to `.userCancel` if the user dismissed the system prompt.
        return
    }

    do {
        try flowService.inputSecondFactor(context: context)
    } catch {
        // TODO: Allow the user to enter their PIN.
    }
}

Toggle Biometrics⚓︎

First, our SDK includes properties and methods to query whether biometrics can be enabled and to retrieve the current biometrics state. The former should be used to determine whether the relevant should be displayed, will the latter allows you to call the relevant action based on the user's method.

Warning

On devices that support Face ID, iOS will show a permissions prompt the first time biometrics are initialised. You should therefore set the NSFaceIDUsageDescription key in your Info.plist to detail what Face ID will be used for.

<key>NSFaceIDUsageDescription</key>
<string>Acme needs access to Face ID in order to use it as second factor.</string>

Check whether the option to enable biometric authentication should be displayed.

NextAuth.getNextAuth().getSecondFactorManager().canEnableBiometrics();

Indicates whether biometric authentication has already been enabled successfully.

NextAuth.getNextAuth().getSecondFactorManager().hasEnabledBiometrics();

Check whether the option to enable biometric authentication should be displayed.

NextAuth.default.canEnableBiometrics

Indicates whether biometric authentication has already been enabled successfully.

NextAuth.default.hasEnabledBiometrics

You can now toggle the biometrics state through the sfBiometricsAdd() and sfBiometricsRemove() methods respectively. For instance, you could display a prominent button on your app's main screen to enable biometrics have they have not yet been enabled. Using these APIs, it is also possible to present a toggle in your app's settings screen to allow the user to enable or disable biometrics.

Info

You cannot toggle biometrics if the user did not set a PIN.

Invoking this method will enable biometric authentication.

NextAuth.getNextAuth().getSecondFactorManager().sfBiometricsAdd();

Invoking this method will disable biometric authentication.

NextAuth.getNextAuth().getSecondFactorManager().sfBiometricsRemove();

Invoking this method will enable biometric authentication.

NextAuth.default.sfBiometricsAdd()

Invoking this method will disable biometric authentication.

NextAuth.default.sfBiometricsRemove()

Enable Biometrics as a Second Factor⚓︎

When adding biometrics as a second factor, a flow is started where the user will be first asked to verify their pin and then confirm their biometric through a biometric prompt.

Info

By setting legacyBioAddFlow to true in the configuration, the order is reversed: the user will first be asked to provide their biometric before being asked to verify their pin.

The expected sequence of FlowUpdates (for a given Flow with Type=ADD_BIOMETRICS and) to be handled is as follows:

  1. WAIT_FOR_INPUT as its State. The CurrentUserInteraction.Type is VERIFY_SECOND_FACTOR and CurrentInteraction.SecondFactorInfo.RequiredSecondFactorTypes=[PIN] -- asking the user to verify their pin. See here for more information.
  2. PROCESSING as its State -- the nextAuth Mobile SDK is verifying the user's pin.
  3. WAIT_FOR_INPUT as its State. The CurrentUserInteraction.Type is SET_SECOND_FACTOR and CurrentInteraction.SecondFactorInfo.RequiredSecondFactorTypes=[BIOMETRICS] -- indicating that pin verification was successful, asking the user to set their biometric. See here for more information.
  4. PROCESSING as its State -- the nextAuth Mobile SDK is updating the user's second factors.
  5. DONE as its State -- the flow successfully finished, the user has enabled biometric as a second factor.

Disable Biometrics as a Second Factor⚓︎

When removing biometrics as a second factor, a flow is started where the user will asked to confirm with one of their current second factors.

The expected sequence of FlowUpdates (for a given Flow with Type=REMOVE_BIOMETRICS and) to be handled is as follows:

  1. WAIT_FOR_INPUT as its State. The CurrentUserInteraction.Type is VERIFY_SECOND_FACTOR -- asking the user to verify with one of CurrentInteraction.SecondFactorInfo.AllowedSecondFactorTypes. See here for more information.
  2. PROCESSING as its State -- the nextAuth Mobile SDK is verifying the user's second factor.
  3. DONE as its State -- the flow successfully finished, the user has disabled biometric as a second factor.

After calling the sfBiometricsRemove() method, you will receive a SecondFactor callback with the BIOMETRICS_REMOVE context. The user will authenticate their intent by inputting their PIN or biometric.

Change PIN⚓︎

You can change the PIN by first having the user input their old PIN or biometric and then setting a new code. To start this flow, call the sfChangePIN() method.

The expected sequence of FlowUpdates (for a given Flow with Type=CHANGE_PIN and) to be handled is as follows:

  1. WAIT_FOR_INPUT as its State. The CurrentUserInteraction.Type is VERIFY_SECOND_FACTOR -- asking the user to verify with one of CurrentInteraction.SecondFactorInfo.AllowedSecondFactorTypes. See here for more information.
  2. PROCESSING as its State -- the nextAuth Mobile SDK is verifying the user's second factor.
  3. WAIT_FOR_INPUT as its State. The CurrentUserInteraction.Type is SET_SECOND_FACTOR and CurrentInteraction.SecondFactorInfo.RequiredSecondFactorTypes=[PIN] -- indicating that pin verification was successful, asking the user to set a new pin. See here for more information.
  4. PROCESSING as its State -- the nextAuth Mobile SDK is updating the user's pin.
  5. DONE as its State -- the flow successfully finished, the user has changed their pin.

Just like for the initial setting of the PIN, it is the app's responsibility to verify that the user entered their new PIN correctly (e.g., by having them input it twice), and optionally that the PIN is not common.

Blocked PIN⚓︎

Upon three consecutive unsuccessful attempts to enter the PIN, the PIN will be blocked and can no longer be used as a second factor.

To unblock the PIN, you can:

  • if biometrics are enabled, use your biometric: upon successful verification by the Second Factor Server, the PIN will no longer be blocked. Note that this does not work when disallowPinChangeWithBiometric is set to true in the configuration.
  • remove all accounts in the app, which will effectively reset the Mobile SDK, and enrol these again. Accounts can be removed from both the app and server.
  • reinstall the app and enrol your accounts again. Note that, when using this option, the existing accounts will not be removed from the server.