First Account⚓︎
In this first scenario, we will walk you through the required steps to enrol the first account. After scanning the enrol QR code, you need to start a flow and handle callbacks from the Mobile SDK. After these steps, you can proceed to do a QR based login or move directly to AppLogin.
Scanning the (Enrol) QR Code⚓︎
The QR codes generated by the nextAuth server contain binary data. While this is can be configured for iOS's built-in QR code scanner, Android has no native support for scanning QR codes. That is why the nextAuth Mobile SDK also contains a secure QR code scanner that works directly on the input of the camera. Note though that this built-in QR code scanner only handles binary QR codes.
You are however free to use any QR code scanner as long as you can recover the raw binary data, as received from the nextAuth server.
Warning
If you decide to use a third party library for scanning QR codes, make sure that it is maintained and has no unpatched vulnerabilities.
Android⚓︎
Using androidx.camera, you can use the output of the ImageAnalysis as input for the NextAuth.processQR() function that will return a byte array containing the binary QR data, if it detected a valid binary QR code in the input.
...
ImageAnalysis.Builder builder = new ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST);
// recommended to set highest available resolution as there are some versions of
// android that would otherwise crop to the upper right corner instead of
// downsampling.
builder.setResolutionSelector(new ResolutionSelector.Builder()
.setResolutionStrategy(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY).build());
ImageAnalysis imageAnalysis = builder.build();
imageAnalysis.setAnalyzer(cameraExecutor, image -> {
// Use nextAuth's built-in QR code scanner
final byte[] result = NextAuth.getNextAuth().processQR(image);
image.close();
if (result != null && result.length > 0) {
// TODO: Handle the result (e.g. start a flow)
}
});
...
Alternatively, one could use Google's ML Kit:
...
ImageAnalysis.Builder builder = new ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST);
// recommended to set highest available resolution as there are some versions of
// android that would otherwise crop to the upper right corner instead of
// downsampling.
builder.setResolutionSelector(new ResolutionSelector.Builder()
.setResolutionStrategy(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY).build());
ImageAnalysis imageAnalysis = builder.build();
BarcodeScannerOptions options = new BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE).build();
BarcodeScanner scanner = BarcodeScanning.getClient(options);
imageAnalysis.setAnalyzer(cameraExecutor, image -> {
Image mediaImage = image.getImage();
if (mediaImage != null) {
InputImage inputImage = InputImage.fromMediaImage(mediaImage,
image.getImageInfo().getRotationDegrees());
scanner.process(inputImage)
.addOnSuccessListener(barcodes -> {
if (!barcodes.isEmpty()) {
byte[] result = barcodes.get(0).getRawBytes();
if (result != null && result.length > 0) {
// TODO: Handle the result (e.g. start a flow)
}
}
})
.addOnFailureListener(e -> image.close())
.addOnCompleteListener(task -> image.close());
}
});
...
iOS⚓︎
While Apple's Vision framework implements the QR detection logic, it does not natively support decoding binary QR codes. We therefore need the following snippet to process the raw QR code descriptor.
...
func decode(barcodeDescriptor: CIQRCodeDescriptor) -> Data? {
enum Mode: UInt8 {
case byte = 0x04
}
guard let mode = Mode(rawValue: barcodeDescriptor.errorCorrectedPayload[0] >> 4), mode == .byte else {
return nil
}
var location: Int
var length: Int
switch barcodeDescriptor.symbolVersion {
case 1...9:
location = 1
length = Int((UInt16(barcodeDescriptor.errorCorrectedPayload[0] & 0x0f) << 4) | (UInt16(barcodeDescriptor.errorCorrectedPayload[1] & 0xf0) >> 4))
case 10...40:
location = 2
length = Int((UInt16(barcodeDescriptor.errorCorrectedPayload[0] & 0x0f) << 12) | (UInt16(barcodeDescriptor.errorCorrectedPayload[1]) << 4) | (UInt16(barcodeDescriptor.errorCorrectedPayload[2] & 0xf0) >> 4))
default:
return nil
}
var data = Data()
for i in location..<(location + length) {
data.append((barcodeDescriptor.errorCorrectedPayload[i] << 4) | (barcodeDescriptor.errorCorrectedPayload[i+1] >> 4))
}
return data
}
...
Starting a Flow⚓︎
The Mobile SDK is built around the concept of flows. For the Mobile SDK to perform actions such as creating an account or logging in, you need to start a flow. This subsection describes how to start a flow based on the scanned QR code. Note that it is also possible to start a flow, based on an incoming push message or deep link.
Android⚓︎
You start a flow by passing the raw binary data (byte array) from scanning the QR code as input.
byte[] data;
NextAuth.getNextAuth().getFlowManager().start(data);
iOS⚓︎
Similar to the approach discussed for Android, you should pass the decoded binary data from the QR code to the Mobile SDK. This is done by invoking the following method.
var data: Data? = nil
flowService.start(with: data)
Handle Callbacks⚓︎
The expected sequence of FlowUpdate callbacks (for a given Flow with Type=ENROL) to be handled is as follows:
PROCESSINGas itsState-- the flow has started, but does not expect any input (yet). See here for more information.WAIT_FOR_INPUTas itsState. TheCurrentUserInteraction.TypeisSET_SECOND_FACTOR-- asking the user to set their second factor. See here for more information.PROCESSINGas itsState-- the nextAuth Mobile SDK is setting up the user's account.DONEas itsState-- the flow successfully finished, the user is enrolled.
Info
In case the user already has an account in the nextAuth Mobile SDK, the user will not be asked to set up a second factor, but rather to verify their existing one.