Describe the bug
POST /passkeys/authentication/verify rejects a valid assertion when the authenticator's Backup Eligible (BE) flag differs between registration and a later authentication. The client only receives the generic webauthn_verification_failed; the server log reveals the real cause: "Backup Eligible flag inconsistency detected during login validation".
This breaks passkey login for Apple iCloud Keychain passkeys, which report inconsistent BE/backup flags before vs. after iCloud sync — producing a "first login works, later login fails" pattern. Observed in production on Apple devices (Safari/Brave, iCloud Keychain) and reproduced deterministically in a local Supabase CLI stack.
Exact server log of the failing request:
{"component":"api","error":"Backup Eligible flag inconsistency detected during login validation",
"error_code":"webauthn_verification_failed","level":"warning","method":"POST",
"path":"/passkeys/authentication/verify","status":400}
The client only sees {"code":"webauthn_verification_failed","message":"Credential verification failed"} (the real reason is in WithInternalError, not exposed).
Root cause: go-webauthn enforces BE-flag immutability during FinishLogin. Per the WebAuthn/CTAP behavior of synced credentials (Apple iCloud Keychain), the BE/backup flags can legitimately differ between ceremonies, so strict rejection is inappropriate for these credentials.
To Reproduce
Deterministic reproduction with Chrome DevTools virtual authenticator:
- Enable a virtual authenticator (
WebAuthn.addVirtualAuthenticator, ctap2, internal, resident, UV).
- Register a passkey normally → stored credential has
backup_eligible=false.
- Before the next assertion, re-add the same credential (same
credentialId/privateKey/userHandle) via WebAuthn.addCredential with backupEligibility: true (simulating iCloud flipping the flag after sync).
- Call
signInWithPasskey() → verify returns 400 with the log line above.
Control cases confirming scope (both succeed): BE=false stable across registration + multiple logins, and BE=true stable across registration + multiple logins. Only the BE flip fails.
Expected behavior
Relax/handle the BE-flag inconsistency for backup-eligible (synced) credentials rather than failing the login — matching the more lenient posture of other libraries (e.g. @simplewebauthn/server returns the backup flags for the RP to decide and does not reject). At minimum, surface the specific reason to the client instead of the generic webauthn_verification_failed, so RPs can diagnose without server logs.
Screenshots
N/A (server log included above).
System information
- OS: macOS (also observed from iOS/Safari)
- Browser: Safari, Brave, Chrome (reproduction uses Chrome DevTools virtual authenticator)
- Version of supabase-js: 2.112.3 (pinned;
auth.experimental.passkey: true)
- GoTrue / supabase/auth: v2.195.0 (passkeys beta,
GOTRUE_PASSKEY_ENABLED=true)
Additional context
The strict check lives in go-webauthn's FinishLogin; GoTrue v2.195.0 has no env/config to relax it. Reference: passkey_authentication.go @ v2.195.0.
Describe the bug
POST /passkeys/authentication/verifyrejects a valid assertion when the authenticator's Backup Eligible (BE) flag differs between registration and a later authentication. The client only receives the genericwebauthn_verification_failed; the server log reveals the real cause:"Backup Eligible flag inconsistency detected during login validation".This breaks passkey login for Apple iCloud Keychain passkeys, which report inconsistent BE/backup flags before vs. after iCloud sync — producing a "first login works, later login fails" pattern. Observed in production on Apple devices (Safari/Brave, iCloud Keychain) and reproduced deterministically in a local Supabase CLI stack.
Exact server log of the failing request:
The client only sees
{"code":"webauthn_verification_failed","message":"Credential verification failed"}(the real reason is inWithInternalError, not exposed).Root cause:
go-webauthnenforces BE-flag immutability duringFinishLogin. Per the WebAuthn/CTAP behavior of synced credentials (Apple iCloud Keychain), the BE/backup flags can legitimately differ between ceremonies, so strict rejection is inappropriate for these credentials.To Reproduce
Deterministic reproduction with Chrome DevTools virtual authenticator:
WebAuthn.addVirtualAuthenticator,ctap2, internal, resident, UV).backup_eligible=false.credentialId/privateKey/userHandle) viaWebAuthn.addCredentialwithbackupEligibility: true(simulating iCloud flipping the flag after sync).signInWithPasskey()→verifyreturns 400 with the log line above.Control cases confirming scope (both succeed): BE=false stable across registration + multiple logins, and BE=true stable across registration + multiple logins. Only the BE flip fails.
Expected behavior
Relax/handle the BE-flag inconsistency for backup-eligible (synced) credentials rather than failing the login — matching the more lenient posture of other libraries (e.g.
@simplewebauthn/serverreturns the backup flags for the RP to decide and does not reject). At minimum, surface the specific reason to the client instead of the genericwebauthn_verification_failed, so RPs can diagnose without server logs.Screenshots
N/A (server log included above).
System information
auth.experimental.passkey: true)GOTRUE_PASSKEY_ENABLED=true)Additional context
The strict check lives in
go-webauthn'sFinishLogin; GoTrue v2.195.0 has no env/config to relax it. Reference:passkey_authentication.go@ v2.195.0.