I'm trying to follow the official documentation to implement Passkey registration. There is also a codelab demonstrating the topic (note that it works as intended on my Android device.)
I created this minimal test-case available on github, which replicates what's taking place in the codelab, in Java instead of Kotlin.
private void signUp() {
CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest = new CreatePublicKeyCredentialRequest(fetchRegistrationJsonFromServer());
credentialManager.createCredentialAsync(
createPublicKeyCredentialRequest,
this,
new CancellationSignal(),
getMainExecutor(),
new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
@Override
public void onResult(CreateCredentialResponse result) {
Log.d(TAG, "Registration success");
}
@Override
public void onError(CreateCredentialException e) {
Log.e(TAG, "Registration failure - " + e.getClass().getName());
if (e instanceof CreatePublicKeyCredentialDomException) {
// Handle the passkey DOM errors thrown according to the
// WebAuthn spec.
Log.e(TAG, String.valueOf(((CreatePublicKeyCredentialDomException)e).getDomError()));
} else if (e instanceof CreateCredentialCancellationException) {
// The user intentionally canceled the operation and chose not
// to register the credential.
} else if (e instanceof CreateCredentialInterruptedException) {
// Retry-able error. Consider retrying the call.
} else if (e instanceof CreateCredentialProviderConfigurationException) {
// Your app is missing the provider configuration dependency.
// Most likely, you're missing the
// "credentials-play-services-auth" module.
} else if (e instanceof CreateCredentialUnknownException) {
} else if (e instanceof CreateCustomCredentialException) {
// You have encountered an error from a 3rd-party SDK. If
// you make the API call with a request object that's a
// subclass of
// CreateCustomCredentialRequest using a 3rd-party SDK,
// then you should check for any custom exception type
// constants within that SDK to match with e.type.
// Otherwise, drop or log the exception.
}
}
}
);
}
private String fetchRegistrationJsonFromServer() {
String response = Utils.readFromAssets(this, "reg.txt");
return response.replace("<userId>", getEncodedUserId())
.replace("<userName>", username)
.replace("<userDisplayName>", username)
.replace("<challenge>", getEncodedChallenge());
}
private String getEncodedUserId() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[64];
random.nextBytes(bytes);
return Base64.encodeToString(bytes, Base64.NO_WRAP | Base64.URL_SAFE | Base64.NO_PADDING);
}
private String getEncodedChallenge() {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[32];
random.nextBytes(bytes);
return Base64.encodeToString(bytes, Base64.NO_WRAP | Base64.URL_SAFE | Base64.NO_PADDING);
}
Currently, it fails with a CreatePublicKeyCredentialDomException
of type androidx.credentials.exceptions.domerrors.SecurityError
.
Note that my app is handling Digital Asset Links properly, according to this document. Please refer to this SO question for reference.
Currently, the only part which looks problematic to me is the registration template. I tried to replace the "rp.id" value with com.unibeam.passkey1
but it did not work.
Answering my own question... The solution was pretty logical:
Using unibeam.github.io
as "rp.id" in the registration template. Only this way, the underlying system is able to discover https://unibeam.github.io/.well-known/assetlinks.json
!
Note that is absolutely not documented, as if Google doesn't want people to implement passkey authentication.