Skip to content

Passkey login fails with webauthn_verification_failed ("Backup Eligible flag inconsistency") for Apple iCloud synced passkeys on subsequent logins #2732

Description

@drandreumartinez
  • I confirm this is a bug with Supabase, not with my own application.
  • I confirm I have searched the Docs, GitHub Discussions, and Discord.

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:

  1. Enable a virtual authenticator (WebAuthn.addVirtualAuthenticator, ctap2, internal, resident, UV).
  2. Register a passkey normally → stored credential has backup_eligible=false.
  3. 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).
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions