{"record":{"id":"c9c17902471b1129","repo":"microg/GmsCore","slug":"err-errormessage-err-errorcode-tostring","errorCode":null,"errorMessage":"err.errorMessage ?: err.errorCode.toString()","messagePattern":"err\\.errorMessage \\?: err\\.errorCode\\.toString\\(\\)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/identity/IdentityCredentialChooserActivity.kt","lineNumber":198,"sourceCode":"            REQ_CODE_FIDO -> handleFidoResult(resultCode, data)\n            REQ_CODE_SIGN_IN -> handleSignInResult(resultCode, data)\n            else -> finishWithGetException(GetCredentialUnknownException(\"Unexpected requestCode=$requestCode\"))\n        }\n    }\n\n    private fun handleFidoResult(resultCode: Int, data: Intent?) {\n        Log.d(TAG, \"handleFidoResult: data: $data\")\n        if (resultCode != RESULT_OK || data == null) {\n            return if (isCreatePath) finishWithCreateException(CreateCredentialUnknownException(\"Passkey flow canceled\"))\n            else finishWithGetException(GetCredentialCancellationException(\"Passkey flow canceled\"))\n        }\n        runCatching {\n            val credentialBytes = data.getByteArrayExtra(FIDO2_KEY_CREDENTIAL_EXTRA)\n                ?: throw IllegalStateException(\"FIDO returned no credential\")\n            val publicKeyCredential = com.google.android.gms.fido.fido2.api.common.PublicKeyCredential\n                .deserializeFromBytes(credentialBytes)\n            (publicKeyCredential.response as? AuthenticatorErrorResponse)?.let { err ->\n                throw IllegalStateException(err.errorMessage ?: err.errorCode.toString())\n            }\n            val json = publicKeyCredential.toJson()\n            val credData = Bundle().apply {\n                putString(if (isCreatePath) PUBKEY_RES_REG_JSON_KEY else PUBKEY_RES_AUTH_JSON_KEY, json)\n            }\n            Log.d(TAG, \"handleFidoResult: $credData\")\n            finishWithCredential(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, credData)\n        }.onFailure { e ->\n            Log.e(TAG, \"handleFidoResult failed\", e)\n            val msg = e.message ?: \"FIDO result error\"\n            if (isCreatePath) finishWithCreateException(CreateCredentialUnknownException(msg))\n            else finishWithGetException(GetCredentialUnknownException(msg))\n        }\n    }\n\n    private fun handleSignInResult(resultCode: Int, data: Intent?) {\n        if (resultCode != RESULT_OK || data == null) {\n            return finishWithGetException(GetCredentialCancellationException(\"Sign-in canceled\"))","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/identity/IdentityCredentialChooserActivity.kt#L180-L216","documentation":"microG's IdentityCredentialChooserActivity converts a FIDO2 authenticator error response into an IllegalStateException whose message is the FIDO error message (or the error code if no message). It is thrown in handleFidoResult when the FIDO2 API returns an AuthenticatorErrorResponse instead of a credential, so the caller of CredentialManager receives an exception instead of a public-key credential result.","triggerScenarios":"The FIDO2 fido2Api.getPendingIntent flow completes and onActivityResult passes the result intent to handleFidoResult; the deserialized PublicKeyCredential.response is an AuthenticatorErrorResponse (user cancelled the authenticator prompt, timeout, no authenticator available, or the authenticator rejected the request).","commonSituations":"User dismisses the biometric/security-key prompt; device has no FIDO2 authenticator or the relying party id/challenge is invalid; testing on emulators lacking hardware-backed key stores.","solutions":["Inspect the IllegalStateException message (it mirrors the FIDO errorMessage/errorCode) and map it to a FidoErrorResponse code to decide whether to retry or cancel","Handle user-cancellation gracefully: treat the error as a cancelled credential flow, not a crash","Verify PublicKeyCredentialRequestOptions / PublicKeyCredentialCreationOptions are well-formed (rp.id matches the calling app's domain, valid challenge) before launching FIDO","Retest on a device with an actual or virtual FIDO2 authenticator"],"exampleFix":"// before\nthrow IllegalStateException(err.errorMessage ?: err.errorCode.toString())\n// after\nval errCode = err.errorCode\nif (errCode == ErrorCode.USER_CANCELED) {\n    finishWithGetException(GetCredentialCancellationException(\"FIDO canceled by user\"))\n} else {\n    throw IllegalStateException(\"FIDO error ${errCode}: ${err.errorMessage}\")\n}","handlingStrategy":"try-catch","validationCode":"// before launching FIDO, ensure options are valid\nrequire(options.rp.id == callingPackageDomain) { \"rp.id must match app domain\" }\nrequire(options.challenge.isNotEmpty()) { \"challenge required\" }","typeGuard":"fun isAuthenticatorErrorResponse(resp: AuthenticatorResponse) = resp is AuthenticatorErrorResponse","tryCatchPattern":"try {\n  val cred = credentialsManager.getCredential(request)\n} catch (e: IllegalStateException) {\n  // FIDO returned AuthenticatorErrorResponse; inspect message for FIDO code\n  if (e.message?.contains(\"cancel\", ignoreCase = true) == true) return canceled()\n  throw e\n}","preventionTips":["Always handle the user-cancel FIDO path as a normal flow, not a crash","Validate rp.id, challenge and user verification requirements before starting FIDO","Test on devices/emulators that actually provide a FIDO2 authenticator","Keep microG updated for FIDO result-handling fixes"],"tags":["fido2","android","credentials","passkey"],"backgroundTag":"authentication-required","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}