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 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.
Source
Thrown at play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockTransportHandler.kt:120
) = AuthenticatorData(
rpId.toByteArray().digest("SHA-256"),
userPresent = userPresent,
userVerified = userVerified,
signCount = signCount,
attestedCredentialData = credentialData
)
suspend fun register(
options: RequestOptions,
callerPackage: String
): AuthenticatorResponseWithUser<AuthenticatorAttestationResponse> {
if (options.type != RequestOptionsType.REGISTER) throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR)
val knownRegistrationInfo = database.getKnownRegistrationInfo(options.rpId)
for (descriptor in options.registerOptions.excludeList.orEmpty()) {
val credentialBase64 = descriptor.id.toBase64(Base64.NO_WRAP or Base64.NO_PADDING or Base64.URL_SAFE)
val excluded = knownRegistrationInfo.any { it.credential == credentialBase64 }
if (store.containsKey(options.rpId, descriptor.id) || excluded) {
throw RequestHandlingException(
ErrorCode.NOT_ALLOWED_ERR,
"An excluded credential has already been registered with the device"
)
}
}
val (clientData, clientDataHash) = getClientDataAndHash(activity, options, callerPackage)
val keyId = store.createKey(options.rpId, clientDataHash)
val publicKey =
store.getPublicKey(options.rpId, keyId) ?: throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR)
// We're ignoring the signature object as we don't need it for registration
val signature = getActiveSignature(options, callerPackage, keyId)
val skipAttestation = options.registerOptions.skipAttestation
val useAndroidKey = !skipAttestation && SDK_INT >= 24 &&
runCatching { store.getCertificateChain(options.rpId, keyId).hasValidLeafCertificate() }.getOrDefault(false)
val useSafetyNet = !skipAttestation && SDK_INT < 24
val aaguid = if (useAndroidKey || useSafetyNet) AAGUID else ByteArray(16)View on GitHub (pinned to 157c9d86ac)
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.
Example fix
// before
screenLockHandler.register(options, callerPackage)
// after
try {
screenLockHandler.register(options, callerPackage)
} catch (e: RequestHandlingException) {
if (e.errorCode == ErrorCode.NOT_ALLOWED_ERR && e.message?.contains("excluded credential") == true) {
signInWithExistingCredential(options.rpId)
} else throw e
} Defensive patterns
Strategy: try-catch
Try / catch
try {
screenLockHandler.register(options, callerPackage)
} catch (e: RequestHandlingException) {
if (e.errorCode == ErrorCode.NOT_ALLOWED_ERR &&
e.message?.contains("excluded credential") == true) {
signInWithExistingCredential(options.rpId)
} else throw e
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- INVALID_STATE_ERR
- Transport ${transport} not supported
- Attachment ${attachment} not supported
- Attestation conveyance preference ${attachment} not supporte
- No response set.
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/958491611f2fced2.
Report an issue: GitHub.