{"record":{"id":"aa0d13081a8a5222","repo":"microg/GmsCore","slug":"not-allowed-err-aa0d13","errorCode":"NOT_ALLOWED_ERR","errorMessage":"An excluded credential has already been registered with the device","messagePattern":"An excluded credential has already been registered with the device","errorType":"error_code","errorClass":"RequestHandlingException","httpStatus":null,"severity":"warning","filePath":"play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt","lineNumber":152,"sourceCode":"        val rpIdHash = options.rpId.toByteArray().digest(\"SHA-256\")\n        val appIdHash =\n            options.authenticationExtensions?.fidoAppIdExtension?.appId?.toByteArray()?.digest(\"SHA-256\")\n        if (!options.registerOptions.parameters.isNullOrEmpty() && options.registerOptions.parameters.all { it.algorithmIdAsInteger != -7 })\n            throw IllegalArgumentException(\"Can't use CTAP1 protocol for non ES256 requests\")\n        if (options.registerOptions.authenticatorSelection?.requireResidentKey == true)\n            throw IllegalArgumentException(\"Can't use CTAP1 protocol when resident key required\")\n        val hasCredential = options.registerOptions.excludeList.orEmpty().any { cred ->\n            ctap1DeviceHasCredential(connection, clientDataHash, rpIdHash, cred) ||\n                    if (appIdHash != null) {\n                        ctap1DeviceHasCredential(connection, clientDataHash, appIdHash, cred)\n                    } else {\n                        false\n                    }\n        }\n        while (true) {\n            try {\n                val response = connection.runCommand(U2fRegistrationCommand(clientDataHash, rpIdHash))\n                if (hasCredential) throw RequestHandlingException(\n                    ErrorCode.NOT_ALLOWED_ERR,\n                    \"An excluded credential has already been registered with the device\"\n                )\n                require(response.userPublicKey[0] == 0x04.toByte())\n                val coseKey = CoseKey(\n                    EC2Algorithm.ES256,\n                    response.userPublicKey.sliceArray(1 until 33),\n                    response.userPublicKey.sliceArray(33 until 65),\n                    1\n                )\n                val credentialData =\n                    AttestedCredentialData(ByteArray(16), response.keyHandle, coseKey.encode())\n                val authData = AuthenticatorData(\n                    options.rpId.toByteArray().digest(\"SHA-256\"),\n                    true,\n                    false,\n                    0,\n                    credentialData","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt#L134-L170","documentation":"During CTAP1/U2F registration the handler first runs a CTAP1 authentication command to check whether any of the request's excluded credentials is already on the token. If the device responds (hasCredential is true), the token refuses to re-register and the library throws NOT_ALLOWED_ERR to mirror the authenticator's 'credential excluded' behavior required by WebAuthn.","triggerScenarios":"ctap1register() (called by register()) detects an existing credential: ctap1DeviceHasCredential/test-of-user-presence loop shows the excluded credentialId is present, so after U2fRegistrationCommand the exception is thrown instead of returning a response.","commonSituations":"Re-registering a security key that already holds the credential (user re-runs registration for the same account); credentialId list built from stale/duplicate allowList entries; user keeps the same key plugged in while the RP re-enrolls it.","solutions":["Treat this as success-from-the-user's-perspective: the key is already registered — complete the flow without a new registration (WebAuthn InvalidStateError semantics).","Ask the user to use a different authenticator, or remove the existing credential from the device before re-registering.","Verify the RP's excludeCredentials list is correct and not duplicating the credential being created.","Have the user sign in instead of register — the existing credential can authenticate."],"exampleFix":"// before (caller surface)\ntry {\n    val resp = handler.register(options, callerPackage)\n} catch (e: RequestHandlingException) {\n    if (e.code == ErrorCode.NOT_ALLOWED_ERR) {\n        // credential already on this authenticator\n        toast(\"This key is already registered — please sign in instead\")\n    }\n}\n// after\n// let the user continue with the existing credential rather than failing the ceremony\nval resp = runCatching { handler.register(options, callerPackage) }\n    .recoverIfRequestHandling(NOT_ALLOWED_ERR) { existingCredentialFallback() }","handlingStrategy":"try-catch","validationCode":"// pre-flight: run ctap1 check or ask RP to send correct excludeCredentials before registering","typeGuard":null,"tryCatchPattern":"try { handler.register(options, pkg) } catch (e: RequestHandlingException) { if (e.code == ErrorCode.NOT_ALLOWED_ERR) completeWithExistingCredential() }","preventionTips":["Treat NOT_ALLOWED_ERR on register as 'already registered', not a hard failure","Send accurate excludeCredentials from the RP","Offer sign-in when the user re-uses a registered key"],"tags":["fido","webauthn","u2f","ctap1","credential-excluded"],"backgroundTag":"invalid-state-transition","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"}