microg/GmsCore · info · MissingPinException
null
Error message
null
What it means
CTAP2 registration requires a PIN when the authenticator enforces user verification (requiresPin) but has no built-in UV. If no PIN was supplied and the client has not already asked the user for one (pinRequested false), the handler throws MissingPinException (message "null") up to the activity, which should prompt the user for the PIN and retry with it.
Source
Thrown at play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/TransportHandler.kt:232
// PREFERRED is the default, according to the standard
// https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-userverification
// If preferred, only return true if connection is capable of user verification
else -> connection.hasClientPin || connection.hasUserVerificationSupport
}
// If the authenticator has a built-in verification method, let that take precedence over
// client PIN
val requiresPin = requireUserVerification && !connection.hasUserVerificationSupport && connection.hasClientPin
val (response, keyHandle) = when {
connection.hasCtap2Support && (requireResidentKey || requiresPin) -> {
try {
var pinToken: ByteArray? = null
// If we previously requested a pin and the user cancelled it (ie. pinRequested
// is true and pin is still null), don't throw the exception, and pass the request
// to the authenticator without a pin.
if (requiresPin && !pinRequested && pin == null) {
throw MissingPinException()
}
if (requiresPin && pin != null && SDK_INT >= 23) {
pinToken = ctap2getPinToken(connection, pin)
}
// Authenticators seem to give a response even without a PIN token, so we'll allow
// the client to call this even without having a PIN token set
ctap2register(connection, options, clientDataHash, requireResidentKey, requireUserVerification, pinToken)
} catch (e: Ctap2StatusException) {
if (e.status == 0x36.toByte()) {
throw MissingPinException()
} else if (e.status == 0x31.toByte()) {
throw WrongPinException()
} else {
throw e
}
}View on GitHub (pinned to 157c9d86ac)
Solutions
- Catch MissingPinException, prompt the user for the device PIN, and retry register() with pin set and pinRequested = true.
- Set userVerification to 'discouraged'/'preferred' in the request if the flow does not truly require UV, avoiding the PIN path.
- Detect the authenticator's PIN requirement up front (ctap2 clientPin info) and collect the PIN before the first attempt.
- Tell the user they can remove/set the PIN via their platform's security-key manager if PIN entry is undesirable.
Example fix
// before
val resp = handler.register(options, callerPackage)
// after
var pin: String? = null
var pinRequested = false
val resp = try {
handler.register(options, callerPackage, pinRequested, pin)
} catch (e: MissingPinException) {
pinRequested = true
pin = promptUserForPin() // AuthenticatorActivity PIN dialog
handler.register(options, callerPackage, pinRequested, pin)
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check authenticator options via ctap2 getInfo: if clientPin set and uv unsupported, PIN will be needed
Try / catch
try { handler.register(options, pkg, pinRequested, pin) } catch (e: MissingPinException) { val pin = promptPin(); handler.register(options, pkg, true, pin) } Prevention
- Probe the authenticator's clientPin/UV capabilities before the ceremony
- Only set userVerification='required' when the flow truly needs it
- Always drive register/sign through the activity flow that can show the PIN dialog
When it happens
Trigger: register() on a CTAP2 connection where requiresPin is true (requireUserVerification && !hasUserVerificationSupport && hasClientPin) and pin == null while pinRequested == false, i.e. the first attempt before any PIN prompt.
Common situations: Registering on a security key that has a PIN set but no biometrics, with an RP requesting userVerification='required'; first call in the retry loop before AuthenticatorActivity collects the PIN; caller invoking the handler directly, bypassing the PIN prompt UI.
Related errors
- Transport ${transport} not supported
- Attachment ${attachment} not supported
- Attestation conveyance preference ${attachment} not supporte
- No response set.
- PublicKeyCredentialType ${type} not supported
AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06).
Data as JSON: /api/errors/423fc11c19f996f7.
Report an issue: GitHub.