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).
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:
To integrate HUAWEI HMS Core services, you must complete the following preparations:
For details, please refer to Preparations for Integrating HUAWEI HMS Core.
Move the downloaded agconnect-services.json file to the app directory of your Android Studio project.
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'
}
}
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.
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:
You can click the button below to download the source code.