microg/GmsCore · warning · RequestHandlingException

NOT_ALLOWED_ERR

NOT_ALLOWED_ERR

Error message

An excluded credential has already been registered with the device

What it means

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.

Source

Thrown at play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt:152

        val rpIdHash = options.rpId.toByteArray().digest("SHA-256")
        val appIdHash =
            options.authenticationExtensions?.fidoAppIdExtension?.appId?.toByteArray()?.digest("SHA-256")
        if (!options.registerOptions.parameters.isNullOrEmpty() && options.registerOptions.parameters.all { it.algorithmIdAsInteger != -7 })
            throw IllegalArgumentException("Can't use CTAP1 protocol for non ES256 requests")
        if (options.registerOptions.authenticatorSelection?.requireResidentKey == true)
            throw IllegalArgumentException("Can't use CTAP1 protocol when resident key required")
        val hasCredential = options.registerOptions.excludeList.orEmpty().any { cred ->
            ctap1DeviceHasCredential(connection, clientDataHash, rpIdHash, cred) ||
                    if (appIdHash != null) {
                        ctap1DeviceHasCredential(connection, clientDataHash, appIdHash, cred)
                    } else {
                        false
                    }
        }
        while (true) {
            try {
                val response = connection.runCommand(U2fRegistrationCommand(clientDataHash, rpIdHash))
                if (hasCredential) throw RequestHandlingException(
                    ErrorCode.NOT_ALLOWED_ERR,
                    "An excluded credential has already been registered with the device"
                )
                require(response.userPublicKey[0] == 0x04.toByte())
                val coseKey = CoseKey(
                    EC2Algorithm.ES256,
                    response.userPublicKey.sliceArray(1 until 33),
                    response.userPublicKey.sliceArray(33 until 65),
                    1
                )
                val credentialData =
                    AttestedCredentialData(ByteArray(16), response.keyHandle, coseKey.encode())
                val authData = AuthenticatorData(
                    options.rpId.toByteArray().digest("SHA-256"),
                    true,
                    false,
                    0,
                    credentialData

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Treat this as success-from-the-user's-perspective: the key is already registered — complete the flow without a new registration (WebAuthn InvalidStateError semantics).
  2. Ask the user to use a different authenticator, or remove the existing credential from the device before re-registering.
  3. Verify the RP's excludeCredentials list is correct and not duplicating the credential being created.
  4. Have the user sign in instead of register — the existing credential can authenticate.

Example fix

// before (caller surface)
try {
    val resp = handler.register(options, callerPackage)
} catch (e: RequestHandlingException) {
    if (e.code == ErrorCode.NOT_ALLOWED_ERR) {
        // credential already on this authenticator
        toast("This key is already registered — please sign in instead")
    }
}
// after
// let the user continue with the existing credential rather than failing the ceremony
val resp = runCatching { handler.register(options, callerPackage) }
    .recoverIfRequestHandling(NOT_ALLOWED_ERR) { existingCredentialFallback() }
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: run ctap1 check or ask RP to send correct excludeCredentials before registering

Try / catch

try { handler.register(options, pkg) } catch (e: RequestHandlingException) { if (e.code == ErrorCode.NOT_ALLOWED_ERR) completeWithExistingCredential() }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/aa0d13081a8a5222. Report an issue: GitHub.