{"record":{"id":"958491611f2fced2","repo":"microg/GmsCore","slug":"not-allowed-err-958491","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/screenlock/ScreenLockTransportHandler.kt","lineNumber":120,"sourceCode":"    ) = AuthenticatorData(\n        rpId.toByteArray().digest(\"SHA-256\"),\n        userPresent = userPresent,\n        userVerified = userVerified,\n        signCount = signCount,\n        attestedCredentialData = credentialData\n    )\n\n    suspend fun register(\n        options: RequestOptions,\n        callerPackage: String\n    ): AuthenticatorResponseWithUser<AuthenticatorAttestationResponse> {\n        if (options.type != RequestOptionsType.REGISTER) throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR)\n        val knownRegistrationInfo = database.getKnownRegistrationInfo(options.rpId)\n        for (descriptor in options.registerOptions.excludeList.orEmpty()) {\n            val credentialBase64 = descriptor.id.toBase64(Base64.NO_WRAP or Base64.NO_PADDING or Base64.URL_SAFE)\n            val excluded = knownRegistrationInfo.any { it.credential == credentialBase64 }\n            if (store.containsKey(options.rpId, descriptor.id) || excluded) {\n                throw RequestHandlingException(\n                    ErrorCode.NOT_ALLOWED_ERR,\n                    \"An excluded credential has already been registered with the device\"\n                )\n            }\n        }\n        val (clientData, clientDataHash) = getClientDataAndHash(activity, options, callerPackage)\n        val keyId = store.createKey(options.rpId, clientDataHash)\n        val publicKey =\n            store.getPublicKey(options.rpId, keyId) ?: throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR)\n\n        // We're ignoring the signature object as we don't need it for registration\n        val signature = getActiveSignature(options, callerPackage, keyId)\n\n        val skipAttestation = options.registerOptions.skipAttestation\n        val useAndroidKey = !skipAttestation && SDK_INT >= 24 &&\n            runCatching { store.getCertificateChain(options.rpId, keyId).hasValidLeafCertificate() }.getOrDefault(false)\n        val useSafetyNet = !skipAttestation && SDK_INT < 24\n        val aaguid = if (useAndroidKey || useSafetyNet) AAGUID else ByteArray(16)","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockTransportHandler.kt#L102-L138","documentation":"During registration, ScreenLockTransportHandler checks the request's excludeList (credentials the RP says are already registered). If any excluded credential already exists on this device — either in the key store (store.containsKey) or the known-registration database — it throws RequestHandlingException(ErrorCode.NOT_ALLOWED_ERR) with this message. This mirrors the WebAuthn 'excluded credentials' behavior: the authenticator refuses to create a duplicate credential.","triggerScenarios":"Calling register() with options.registerOptions.excludeList containing a credential id that store.containsKey(options.rpId, id) reports present, or whose base64 form matches an entry in database.getKnownRegistrationInfo(options.rpId).","commonSituations":"The user re-runs a registration flow they already completed; the RP frontend re-sends registration after a partial success; sync restored credentials so the device already holds the excluded credential.","solutions":["Treat this as 'credential already exists': catch the exception and proceed to the sign/authentication flow with the existing credential instead of registering again.","If re-registration is genuinely intended, drop the existing credential or send an empty excludeList after confirming with the user.","Check the device's known registrations for the rpId before initiating registration to detect the duplicate upfront."],"exampleFix":"// before\nscreenLockHandler.register(options, callerPackage)\n// after\ntry {\n    screenLockHandler.register(options, callerPackage)\n} catch (e: RequestHandlingException) {\n    if (e.errorCode == ErrorCode.NOT_ALLOWED_ERR && e.message?.contains(\"excluded credential\") == true) {\n        signInWithExistingCredential(options.rpId)\n    } else throw e\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    screenLockHandler.register(options, callerPackage)\n} catch (e: RequestHandlingException) {\n    if (e.errorCode == ErrorCode.NOT_ALLOWED_ERR &&\n        e.message?.contains(\"excluded credential\") == true) {\n        signInWithExistingCredential(options.rpId)\n    } else throw e\n}","preventionTips":["Query the device for known registrations before starting registration","Send excludeList only for genuinely known credentials","Design the UX so 'already registered' leads to sign-in, not an error page"],"tags":["fido","webauthn","android","screen-lock","duplicate-credential"],"backgroundTag":"resource-already-exists","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}