Overview

HUAWEI FIDO2 provides your app with FIDO2 based on the WebAuthn standard. It provides Android Java APIs for apps and browsers, and allows users to complete authentication through roaming authenticators (USB, NFC, and Bluetooth authenticators) and platform authenticators (fingerprint and 3D face authenticator).

What You Will Create

In this codelab, you will use the demo project that has been created for you to call HUAWEI FIDO2 client APIs. Through the demo project, you will:

What You Will Learn

Other features

FIDO(BioAuthn)

Hardware Requirements

Software Requirements

To integrate HUAWEI HMS Core services, you must complete the following preparations:

For details, please refer to Preparations for Integrating HUAWEI HMS Core.

Enabling FIDO

  1. On the app information page of the project, click the Manage APIs tab.
  2. Toggle on the FIDO switch.

Adding the AppGallery Connect Configuration File of Your App

Move the downloaded agconnect-services.json file to the app directory of your Android Studio project.

Configuring the Maven Repository Address for the HMS Core SDK

Open the build.gradle file in the root directory of your Android Studio project.

Go to allprojects > repositories and configure the Maven repository address for the HMS Core SDK.

allprojects { repositories { google() jcenter() // Add the following line maven { url 'https://developer.huawei.com/repo/' } } }

Go to buildscript > repositories and configure the Maven repository address for the HMS Core SDK.

buildscript { repositories { google() jcenter() // Add the following line. maven { url 'https://developer.huawei.com/repo/' } } }

Go to buildscript > dependencies and add dependency configurations.

buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.3.0' // Add the following line classpath 'com.huawei.agconnect:agcp:1.4.1.300' } }

Adding Build Dependencies

Open the build.gradle file in the app directory.

Add build dependencies.

dependencies { // Add the following line implementation 'com.huawei.hms:fido-fido2:5.0.2.303' }

Add the AppGallery Connect plug-in dependency to the file header.

apply plugin: 'com.huawei.agconnect'

Configure the signature in android.

Copy the JKS file (for example, FIDO2 Android Sample.jks) obtained during integration preparations to the app directory of your project, and configure the signature in the build.gradle file.

android { signingConfigs { config { keyAlias 'FIDO2 Android Sample' keyPassword '******' storeFile file('FIDO2 Android Sample.jks') storePassword '******' } } buildTypes { debug { signingConfig signingConfigs.config } release { signingConfig signingConfigs.config minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }

Click Sync Now to synchronize the project.

FIDO2 includes two operations: registration and authentication. The processes are similar for the two operations.

  1. Create an activity.
  2. Obtain the challenge value and related policy from the FIDO server, and initiate a request. (Only the FIDO client APIs are provided here. For details about the interaction with the FIDO server, please refer to related specifications and contact the FIDO server vendor to obtain the related API description.)
  3. Call Fido2Client.getRegistrationIntent() to initiate registration, or call Fido2Client.getAuthenticationIntent() to initiate authentication.
  4. Call Fido2Intent.launchFido2Activity() in the callback to start registration (requestCode is Fido2Client.REGISTRATION_REQUEST) or authentication (requestCode is Fido2Client.AUTHENTICATION_REQUEST). The callback will be executed in the main thread.
  5. Call Fido2Client.getFido2RegistrationResponse() or Fido2Client.getFido2AuthenticationResponse() in the callback Activity.onActivityResult() to obtain the registration or authentication result.
  6. Send the registration or authentication result to the FIDO server for verification. (Only the FIDO client APIs are provided here. For details about the interaction with the FIDO server, please refer to related specifications and contact the FIDO server vendor to obtain the related API description.)

The following is the sample code for using FIDO2-related APIs:

Java sample code:

package com.huawei.hms.kit.fidocp; import android.app.Activity; import android.app.AlertDialog; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.huawei.hms.fidocp.R; import com.huawei.hms.support.api.fido.fido2.Algorithm; import com.huawei.hms.support.api.fido.fido2.Attachment; import com.huawei.hms.support.api.fido.fido2.AttestationConveyancePreference; import com.huawei.hms.support.api.fido.fido2.AuthenticatorMetadata; import com.huawei.hms.support.api.fido.fido2.AuthenticatorSelectionCriteria; import com.huawei.hms.support.api.fido.fido2.Fido2; import com.huawei.hms.support.api.fido.fido2.Fido2AuthenticationRequest; import com.huawei.hms.support.api.fido.fido2.Fido2AuthenticationResponse; import com.huawei.hms.support.api.fido.fido2.Fido2Client; import com.huawei.hms.support.api.fido.fido2.Fido2Extension; import com.huawei.hms.support.api.fido.fido2.Fido2Intent; import com.huawei.hms.support.api.fido.fido2.Fido2IntentCallback; import com.huawei.hms.support.api.fido.fido2.Fido2RegistrationRequest; import com.huawei.hms.support.api.fido.fido2.Fido2RegistrationResponse; import com.huawei.hms.support.api.fido.fido2.Fido2Response; import com.huawei.hms.support.api.fido.fido2.NativeFido2AuthenticationOptions; import com.huawei.hms.support.api.fido.fido2.NativeFido2RegistrationOptions; import com.huawei.hms.support.api.fido.fido2.PublicKeyCredentialCreationOptions; import com.huawei.hms.support.api.fido.fido2.PublicKeyCredentialDescriptor; import com.huawei.hms.support.api.fido.fido2.PublicKeyCredentialParameters; import com.huawei.hms.support.api.fido.fido2.PublicKeyCredentialRequestOptions; import com.huawei.hms.support.api.fido.fido2.PublicKeyCredentialRpEntity; import com.huawei.hms.support.api.fido.fido2.PublicKeyCredentialType; import com.huawei.hms.support.api.fido.fido2.PublicKeyCredentialUserEntity; import java.io.UnsupportedEncodingException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; public class Fido2DemoActivity extends AppCompatActivity { private TextView reusltView; private Fido2Client fido2Client; private byte[] regCredentialId = null; private String rpId = "com.huawei.hms.fido2.test"; private String user = "fidoCp"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fido2_demo); reusltView = findViewById(R.id.reusltView); fido2Client = Fido2.getFido2Client(this); } // Obtain the challenge value and related policy from the FIDO server, and initiate a Fido2RegistrationRequest request. private Fido2RegistrationRequest assembleFido2RegistrationRequest() { // TODO: Obtain the challenge value and related policy from the FIDO server. byte[] challengeBytes = getChallege(); // Initiate the Fido2RegistrationRequest request. The first parameter of AuthenticatorSelectionCriteria // specifies whether to use a platform authenticator or a roaming authenticator. If no authenticator needs to be specified, pass null to this parameter. PublicKeyCredentialCreationOptions.Builder builder = new PublicKeyCredentialCreationOptions.Builder(); builder.setRp(new PublicKeyCredentialRpEntity(rpId, rpId, null)) .setChallenge(challengeBytes) .setAttestation(AttestationConveyancePreference.DIRECT) .setAuthenticatorSelection(new AuthenticatorSelectionCriteria(Attachment.PLATFORM, null, null)) .setPubKeyCredParams(new ArrayList<PublicKeyCredentialParameters>( Arrays.asList( new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.ES256), new PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.RS256)))) .setTimeoutSeconds(60L); try { builder.setUser(new PublicKeyCredentialUserEntity(user, user.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { reusltView.append(e.getMessage() + '\n'); } if (regCredentialId != null) { builder.setExcludeList(new ArrayList<PublicKeyCredentialDescriptor>( Arrays.asList( new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, regCredentialId)))); } HashMap<String, Object> extensions = new HashMap<>(); builder.setExtensions(extensions); // Specify a platform authenticator and related extension items. You can specify a platform authenticator or not as needed. useSelectedPlatformAuthenticator(extensions); return new Fido2RegistrationRequest(builder.build(), null); } public void btnRegistrationClicked(View view) { if (!fido2Client.isSupported()) { showMsg("FIDO2 is not supported."); return; } Fido2RegistrationRequest request = assembleFido2RegistrationRequest(); // Call Fido2Client.getRegistrationIntent to obtain a Fido2Intent instance and start the FIDO client registration process. fido2Client.getRegistrationIntent(request, NativeFido2RegistrationOptions.DEFAULT_OPTIONS, new Fido2IntentCallback() { @Override public void onSuccess(Fido2Intent fido2Intent) { // Start the FIDO client registration process through Fido2Client.REGISTRATION_REQUEST. fido2Intent.launchFido2Activity(Fido2DemoActivity.this, Fido2Client.REGISTRATION_REQUEST); } @Override public void onFailure(int errorCode, CharSequence errString) { showError("Registration failed." + errorCode + "=" + errString); } }); } // Obtain the challenge value and related policy from the FIDO server, and initiate a Fido2AuthenticationRequest request. private Fido2AuthenticationRequest assembleFido2AuthenticationRequest() { // TODO: Obtain the challenge value and related policy from the FIDO server. byte[] challengeBytes = getChallege(); // Initiate the Fido2RegistrationRequest request. List<PublicKeyCredentialDescriptor> allowList = new ArrayList<>(); allowList.add(new PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, regCredentialId)); PublicKeyCredentialRequestOptions.Builder builder = new PublicKeyCredentialRequestOptions.Builder(); builder.setRpId(rpId).setChallenge(challengeBytes).setAllowList(allowList).setTimeoutSeconds(60L); HashMap<String, Object> extensions = new HashMap<>(); builder.setExtensions(extensions); // Specify a platform authenticator and related extension items. You can specify a platform authenticator or not as needed. useSelectedPlatformAuthenticator(extensions); return new Fido2AuthenticationRequest(builder.build(), null); } public void btnAuthenticationClicked(View view) { if (regCredentialId == null) { showMsg("Please register first."); return; } if (!fido2Client.isSupported()) { showMsg("FIDO2 is not supported."); return; } Fido2AuthenticationRequest request = assembleFido2AuthenticationRequest(); // Call Fido2Client.getAuthenticationIntent to obtain a Fido2Intent instance and start the FIDO client authentication process. fido2Client.getAuthenticationIntent(request, NativeFido2AuthenticationOptions.DEFAULT_OPTIONS, new Fido2IntentCallback() { @Override public void onSuccess(Fido2Intent fido2Intent) { // Start the FIDO client authentication process through Fido2Client.AUTHENTICATION_REQUEST. fido2Intent.launchFido2Activity(Fido2DemoActivity.this, Fido2Client.AUTHENTICATION_REQUEST); } @Override public void onFailure(int errorCode, CharSequence errString) { showError("Authentication failed." + errorCode + "=" + errString); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != Activity.RESULT_OK) { showMsg("Unknown error."); return; } switch (requestCode) { // Receive registration response case Fido2Client.REGISTRATION_REQUEST: Fido2RegistrationResponse fido2RegistrationResponse = fido2Client.getFido2RegistrationResponse(data); if (fido2RegistrationResponse.isSuccess()) { reusltView.append("registration\n"); reusltView.append(fido2RegistrationResponse.getAuthenticatorAttestationResponse().toJson()); reusltView.append("\n"); // save the credentialId regCredentialId = fido2RegistrationResponse.getAuthenticatorAttestationResponse().getCredentialId(); showMsg("Registration successful."); } else { showError("Registration failed.", fido2RegistrationResponse); } break; // Receive authentication response case Fido2Client.AUTHENTICATION_REQUEST: Fido2AuthenticationResponse fido2AuthenticationResponse = fido2Client.getFido2AuthenticationResponse(data); if (fido2AuthenticationResponse.isSuccess()) { reusltView.append("Authentication\n"); reusltView.append(fido2AuthenticationResponse.getAuthenticatorAssertionResponse().toJson()); reusltView.append("\n"); showMsg("Authentication successful."); } else { showError("Authentication failed.", fido2AuthenticationResponse); } } } private void showError(String message) { AlertDialog.Builder builder = new AlertDialog.Builder(Fido2DemoActivity.this); builder.setTitle("Error"); builder.setMessage(message); builder.setPositiveButton("OK", null); builder.show(); } private void showError(String message, Fido2Response fido2Response) { StringBuilder errMsgBuilder = new StringBuilder(); errMsgBuilder.append(message) .append(fido2Response.getFido2Status()) .append("=") .append(fido2Response.getFido2StatusMessage()) .append(String.format(Locale.getDefault(), "(Ctap error: 0x%x=%s)", fido2Response.getCtapStatus(), fido2Response.getCtapStatusMessage())); showError(errMsgBuilder.toString()); } private void showMsg(String message) { AlertDialog.Builder builder = new AlertDialog.Builder(Fido2DemoActivity.this); builder.setTitle("Information"); builder.setMessage(message); builder.setPositiveButton("OK", null); builder.show(); } public void btnClearLogClicked(View view) { reusltView.setText(""); regCredentialId = null; } private byte[] getChallege() { return SecureRandom.getSeed(16); } // Specify a platform authenticator and related extension items. private void useSelectedPlatformAuthenticator(HashMap<String, Object> extensions) { if (!fido2Client.hasPlatformAuthenticators()) { return; } List<String> selectedAuthenticatorList = new ArrayList<>(); for (AuthenticatorMetadata meta : fido2Client.getPlatformAuthenticators()) { // Fingerprint authenticator if (meta.isSupportedUvm(AuthenticatorMetadata.UVM_FINGERPRINT)) { selectedAuthenticatorList.add(meta.getAaguid()); if (meta.getExtensions().contains(Fido2Extension.W3C_WEBAUTHN_UVI.getIdentifier())) { // Indicates whether to verify the fingerprint ID. If the value is true, the same finger must be used for both registration and verification. extensions.put(Fido2Extension.W3C_WEBAUTHN_UVI.getIdentifier(), Boolean.TRUE); } if (meta.getExtensions().contains(Fido2Extension.HMS_R_PA_CIBBE_01.getIdentifier())) { // Indicates whether the authentication credential expires when the biometric feature changes. If the value is true, the key will expire when the fingerprint is enrolled. This is valid only for registration. extensions.put(Fido2Extension.HMS_R_PA_CIBBE_01.getIdentifier(), Boolean.TRUE); } } // Lock screen password authenticator else if (meta.isSupportedUvm(AuthenticatorMetadata.UVM_PASSCODE)) { // selectedAuthenticatorList.add(authenticatorMetadata.getAaguid()); } // Lock screen face authenticator else if (meta.isSupportedUvm(AuthenticatorMetadata.UVM_FACEPRINT)) { // selectedAuthenticatorList.add(authenticatorMetadata.getAaguid()); } } extensions.put(Fido2Extension.HMS_RA_C_PACL_01.getIdentifier(), selectedAuthenticatorList); } }

Kotlin sample code:

package com.huawei.hms.kit.fidocp import android.app.Activity import android.app.AlertDialog import android.content.Intent import android.os.Bundle import android.view.View import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import com.huawei.hms.fidocp.R import com.huawei.hms.support.api.fido.fido2.* import java.io.UnsupportedEncodingException import java.security.SecureRandom import java.util.* class Fido2DemoActivity : AppCompatActivity() { private var reusltView: TextView? = null private var fido2Client: Fido2Client? = null private var regCredentialId: ByteArray? = null // Specify a value based on the actual service requirements. private val rpId = "com.huawei.hms.fido2.test" // Specify a value based on the actual service requirements. private val user = "fidoCp" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_fido2_demo) reusltView = findViewById(R.id.reusltView) fido2Client = Fido2.getFido2Client(this) } // Obtain the challenge value and related policy from the FIDO server, and initiate a Fido2RegistrationRequest request. private fun assembleFido2RegistrationRequest(): Fido2RegistrationRequest { // TODO: Obtain the challenge value and related policy from the FIDO server. val challengeBytes = challege // Initiate the Fido2RegistrationRequest request. The first parameter of AuthenticatorSelectionCriteria // specifies whether to use a platform authenticator or a roaming authenticator. If no authenticator needs to be specified, pass null to this parameter. val builder = PublicKeyCredentialCreationOptions.Builder() builder.setRp(PublicKeyCredentialRpEntity(rpId, rpId, null)) .setUser(PublicKeyCredentialUserEntity(user, user.toByteArray())) .setChallenge(challengeBytes) .setAttestation(AttestationConveyancePreference.DIRECT) .setAuthenticatorSelection(AuthenticatorSelectionCriteria(Attachment.PLATFORM, null, null)) .setPubKeyCredParams(ArrayList( Arrays.asList(PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.ES256), PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, Algorithm.RS256)))) .setTimeoutSeconds(60L) try { builder.setUser(PublicKeyCredentialUserEntity(user, user.toByteArray(charset("UTF-8")))) } catch (e: UnsupportedEncodingException) { reusltView!!.append(e.message + "\n") } if (regCredentialId != null) { builder.setExcludeList(ArrayList( Arrays.asList(PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, regCredentialId)))) } val extensions = HashMap<String, Any>() builder.setExtensions(extensions) // Specify a platform authenticator and related extension items. You can specify a platform authenticator or not as needed. useSelectedPlatformAuthenticator(extensions) return Fido2RegistrationRequest(builder.build(), null) } fun btnRegistrationClicked(view: View?) { if (!fido2Client!!.isSupported) { showMsg("FIDO2 is not supported.") return } val request = assembleFido2RegistrationRequest() // Call Fido2Client.getRegistrationIntent to obtain a Fido2Intent instance and start the FIDO client registration process. fido2Client!!.getRegistrationIntent(request, NativeFido2RegistrationOptions.DEFAULT_OPTIONS, object : Fido2IntentCallback { override fun onSuccess(fido2Intent: Fido2Intent) { // Start the FIDO client registration process through Fido2Client.REGISTRATION_REQUEST. fido2Intent.launchFido2Activity(this@Fido2DemoActivity, Fido2Client.REGISTRATION_REQUEST) } override fun onFailure(errorCode: Int, errString: CharSequence) { showError("Registration failure. $errorCode=$errString") } }) } // Obtain the challenge value and related policy from the FIDO server, and initiate a Fido2AuthenticationRequest request. private fun assembleFido2AuthenticationRequest(): Fido2AuthenticationRequest { // TODO: Obtain the challenge value and related policy from the FIDO server. val challengeBytes = challege // Initiate the Fido2RegistrationRequest request. val allowList: MutableList<PublicKeyCredentialDescriptor> = ArrayList() allowList.add(PublicKeyCredentialDescriptor(PublicKeyCredentialType.PUBLIC_KEY, regCredentialId)) val builder = PublicKeyCredentialRequestOptions.Builder() builder.setRpId(rpId).setChallenge(challengeBytes).setAllowList(allowList).setTimeoutSeconds(60L) val extensions = HashMap<String, Any>() builder.setExtensions(extensions) // Specify a platform authenticator and related extension items. You can specify a platform authenticator or not as needed. useSelectedPlatformAuthenticator(extensions) return Fido2AuthenticationRequest(builder.build(), null) } fun btnAuthenticationClicked(view: View?) { if (regCredentialId == null) { showMsg("Please register first.") return } if (!fido2Client!!.isSupported) { showMsg("FIDO2 is not supported.") return } val request = assembleFido2AuthenticationRequest() // Call Fido2Client.getAuthenticationIntent to obtain a Fido2Intent instance and start the FIDO client authentication process. fido2Client!!.getAuthenticationIntent(request, NativeFido2AuthenticationOptions.DEFAULT_OPTIONS, object : Fido2IntentCallback { override fun onSuccess(fido2Intent: Fido2Intent) { // Start the FIDO client authentication process through Fido2Client.AUTHENTICATION_REQUEST. fido2Intent.launchFido2Activity(this@Fido2DemoActivity, Fido2Client.AUTHENTICATION_REQUEST) } override fun onFailure(errorCode: Int, errString: CharSequence) { showError("Authentication failure. $errorCode=$errString") } }) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (resultCode != Activity.RESULT_OK) { showMsg("Unknown error.") return } when (requestCode) { Fido2Client.REGISTRATION_REQUEST -> { val fido2RegistrationResponse = fido2Client!!.getFido2RegistrationResponse(data) if (fido2RegistrationResponse.isSuccess) { reusltView!!.append("Registration\n") reusltView!!.append(fido2RegistrationResponse.authenticatorAttestationResponse.toJson()) reusltView!!.append("\n") // save the credentialId regCredentialId = fido2RegistrationResponse.authenticatorAttestationResponse.credentialId showMsg("Registration success.") } else { showError("Registration failed.", fido2RegistrationResponse) } } Fido2Client.AUTHENTICATION_REQUEST -> { val fido2AuthenticationResponse = fido2Client!!.getFido2AuthenticationResponse(data) if (fido2AuthenticationResponse.isSuccess) { reusltView!!.append("Authentication\n") reusltView!!.append(fido2AuthenticationResponse.authenticatorAssertionResponse.toJson()) reusltView!!.append("\n") showMsg("Authentication success.") } else { showError("Authentication failure.", fido2AuthenticationResponse) } } } } private fun showError(message: String) { val builder = AlertDialog.Builder(this@Fido2DemoActivity) builder.setTitle("Error") builder.setMessage(message) builder.setPositiveButton("OK", null) builder.show() } private fun showError(message: String, fido2Response: Fido2Response) { val errMsgBuilder = StringBuilder() errMsgBuilder.append(message) .append(fido2Response.fido2Status) .append("=") .append(fido2Response.fido2StatusMessage) .append(String.format(Locale.getDefault(), "(Ctap error:0x%x=%s)", fido2Response.ctapStatus, fido2Response.ctapStatusMessage)) showError(errMsgBuilder.toString()) } private fun showMsg(message: String) { val builder = AlertDialog.Builder(this@Fido2DemoActivity) builder.setTitle("Information") builder.setMessage(message) builder.setPositiveButton("OK", null) builder.show() } fun btnClearLogClicked(view: View?) { reusltView!!.text = "" regCredentialId = null } private val challege: ByteArray get() = SecureRandom.getSeed(16) // Specify a platform authenticator and related extension items. private fun useSelectedPlatformAuthenticator(extensions: HashMap<String, Any>) { if (!fido2Client!!.hasPlatformAuthenticators()) { return } val selectedAuthenticatorList: MutableList<String> = ArrayList() for (meta in fido2Client!!.platformAuthenticators) { if (!meta.isAvailable) { continue } // Fingerprint authenticator if (meta.isSupportedUvm(AuthenticatorMetadata.UVM_FINGERPRINT)) { selectedAuthenticatorList.add(meta.aaguid) if (meta.extensions.contains(Fido2Extension.W3C_WEBAUTHN_UVI.identifier)) { // Indicates whether to verify the fingerprint ID. If the value is true, the same finger must be used for both registration and verification. extensions[Fido2Extension.W3C_WEBAUTHN_UVI.identifier] = true } if (meta.extensions.contains(Fido2Extension.HMS_R_PA_CIBBE_01.identifier)) { // Indicates whether the authentication credential expires when the biometric feature changes. If the value is true or empty, the key will expire when the fingerprint is enrolled. This is valid only for registration. extensions[Fido2Extension.HMS_R_PA_CIBBE_01.identifier] = true } } else if (meta.isSupportedUvm(AuthenticatorMetadata.UVM_FACEPRINT)) { // selectedAuthenticatorList.add(authenticatorMetadata.getAaguid()); } } extensions[Fido2Extension.HMS_RA_C_PACL_01.identifier] = selectedAuthenticatorList } }

Install the test APK and tap the registration or authentication button.

Well done. You have successfully completed this codelab and learned how to:

For more information, please click the following link:

Related documents

You can click the button below to download the source code.

Download source code

Code copied