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.
The SET_SECOND_FACTOR 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 biometrics) if the user does not yet have their 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 ask the user to provide the allowed second factor types. Note that it is mandatory to include at least the types mentioned in RequiredSecondFactorTypes. You can pass the user’s input back to the nextAuth Mobile SDK by using the inputSecondFactor method.
Danger
On both Android and iOS, if the user enables biometrics in the nextAuth SDK, registers one or more biometrics, and then later registers an additional biometric, then the protected biometric keys will be wiped, which will invalidate all of the previously registered biometrics in your app, meaning that only the last registered biometric will be valid. On iOS, the wiping of the keys additionally occurs when the user removes one or more biometrics but still has others present. In other words, the removal of a single biometric on iOS effectively invalidates all of them.
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 the 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 copies of the PIN (even partial copies) remain in memory any longer than strictly necessary. Additionally, the classes can check whether the PIN is common (i.e. easy to guess) and can verify that two entered 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
To best prevent attackers from learning the user’s PIN:
- implement your own PIN pad (do not use the standard numeric keyboard);
- disable screenshots or touch event logging while the user enters their PIN;
- do not provide any visual feedback as to which numeric buttons were pressed.
Biometrics⚓︎
Our Android SDK requires that you request a CryptoObject from the SDK and that you have the user authenticate on that object using their biometrics before sending it back as an input for the inputSecondFactor() method. Refer to the following code snippet for an example of how to do 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 enable biometric authentication require an evaluated LAContext, and it is crucial to pass .deviceOwnerAuthenticationWithBiometrics as the evaluated policy. We provide sample code below to illustrate the required procedure. Note that the SDK will invalidate the provided context. Note that when flow.currentInteraction?.type == .setSecondFactor, biometrics always succeed, so no fallback will be shown in the biometric prompt, regardless of the value provided.
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 of all, our SDK includes properties/methods to determine whether biometrics can be enabled (canEnableBiometrics) and to determine whether they are enabled (hasEnabledBiometrics).
The canEnableBiometrics property/method allows your app to show options for enabling biometrics only if appropriate. For example, you may wish to show options on your enrolment screen such as “enable biometrics” or “add biometrics” only if the device supports them.
Similarly, the hasEnabledBiometrics property/method allows your app to show additional options to the user depending on the current biometrics state. For example, you may wish to show options such as “add biometrics” or “remove biometrics” depending on whether they are currently enabled.
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. One way that you could use these methods is to display a prominent button on your app’s main screen to enable biometrics in case they have not yet been enabled. Using these APIs, it is also possible to provide a toggle button 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 first be asked to verify their PIN and then confirm their biometric through a biometric prompt.
Info
If you set 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) to be handled is as follows:
State=WAIT_FOR_INPUT,CurrentUserInteraction.Type=VERIFY_SECOND_FACTORandCurrentInteraction.SecondFactorInfo.RequiredSecondFactorTypes= [PIN] -- asking the user to verify their PIN. See here for more information.State=PROCESSING-- the nextAuth Mobile SDK is verifying the user’s PIN.State=WAIT_FOR_INPUT,CurrentUserInteraction.Type=SET_SECOND_FACTORandCurrentInteraction.SecondFactorInfo.RequiredSecondFactorTypes= [BIOMETRICS] -- PIN verification was successful; the SDK is now asking the user to set their biometrics. See here for more information.State=PROCESSING-- the nextAuth Mobile SDK is updating the user’s second factors.State=DONE-- the flow successfully finished; the user has enabled biometrics 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) to be handled is as follows:
State=WAIT_FOR_INPUTandCurrentUserInteraction.Type=VERIFY_SECOND_FACTOR-- asking the user to verify with one of the second factor types inCurrentInteraction.SecondFactorInfo.AllowedSecondFactorTypes. See here for more information.State=PROCESSING-- the nextAuth Mobile SDK is verifying the user’s second factor.State=DONE-- the flow successfully finished; the user has disabled 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 biometrics.
Change PIN⚓︎
You can change the PIN by first having the user input their old PIN or biometrics, and then setting a new PIN. To start this flow, call the sfChangePIN() method.
The expected sequence of FlowUpdates (for a given Flow with Type = CHANGE_PIN) to be handled is as follows:
State=WAIT_FOR_INPUTandCurrentUserInteraction.Type=VERIFY_SECOND_FACTOR-- asking the user to verify with one of the second factor types inCurrentInteraction.SecondFactorInfo.AllowedSecondFactorTypes. See here for more information.State=PROCESSING-- the nextAuth Mobile SDK is verifying the user’s second factor.State=WAIT_FOR_INPUT,CurrentUserInteraction.Type=SET_SECOND_FACTORandCurrentInteraction.SecondFactorInfo.RequiredSecondFactorTypes= [PIN] -- the verification of the second factor was successful and the SDK is asking the user to set a new PIN. See here for more information.State=PROCESSING-- the nextAuth Mobile SDK is updating the user’s PIN.State=DONE-- the flow successfully finished; the user has changed their PIN.
Just like for the initial setting of the PIN, it is your app’s responsibility to verify that the user entered their new PIN correctly (e.g. by having them input it twice), and optionally require that the chosen PIN is not common (i.e. not easy to guess).
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
disallowPinChangeWithBiometricis set totruein the configuration. - remove all accounts in the app, which will effectively reset the Mobile SDK, and then enrol the accounts again. Accounts can be removed from both the app and the server.
- reinstall the app and enrol the accounts again. Note that when using this option, the existing accounts will not be removed from the server.