Skip to content

Second Factor Management⚓︎

As discussed in User Interaction, 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 SecondFactor callback with the FIRST_SET context. As a developer, you have the choice of having your user just set a PIN code, just the biometric or both at the same time. You do this by passing the user's inputs to the SDK by calling the sfInput() method. We do recommend to set always set a PIN, possibly complemented with a biometric.

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().getSecondFactorManager().sfInput(pin);
NextAuth.getNextAuth().getSecondFactorManager().sfInput(cryptoObject);

NextAuth.getNextAuth().getSecondFactorManager().sfInput(pin, cryptoObject);
NextAuth.default.sfInput(pin)
NextAuth.default.sfInput(info)

NextAuth.default.sfInput(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.wipe()
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()

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 sfInput() 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().getSecondFactorManager().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 sfInput 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 sfInfo.context == .biometricsAdd, 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 sfInfo.context == .biometricsAdd {
    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 NextAuth.default.sfInput(context: context, info: sfInfo)
    } 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 a biometric, the user will be first asked to confirm their biometric through a biometric prompt.

  • On Android, you need to show this biometric prompt on receiving a SecondFactor callback with a BIOMETRICS_ADD context (and showBiometrics = true). You pass the result back to the SDK by calling the sfInput() method.
  • On iOS, this is handled from the nextAuth mobile SDK (depending on the OS version, the user will be shown a biometric prompt or not).

After confirming the user's biometric, you will receive (another -- Android) SecondFactor callback with a BIOMETRICS_ADD context (and showBiometrics = false), to have the user authenticate their intent of adding biometrics as a second factor. This is done by having the user enter their PIN and passing the result back to the SDK by calling the sfInput() method.

Disable Biometrics 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.

After calling the sfChangePIN() method, you will receive a SecondFactor callback with the PIN_CHANGE_VERIFY context. You should have the user present their second factor (to authenticate their intent) and pass it to the SDK by calling the sfInput() method.

If the second factor proved to be correct, you will then receive another SecondFactor callback with the PIN_CHANGE_SET context, so that the user can set their new PIN. Otherwise (if the second factor was not correct or something went wrong), you will receive a SecondFactor callback with the PIN_CHANGE_VERIFY context and a result indicating what went wrong.

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.

Finally, you will receive a SecondFactor callback with the PIN_CHANGE_VERIFY context and SUCCESS result.

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.
  • remove all accounts in the app, which will effectively reset the Mobile SDK, and enroll 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.