Expo Easy Passkey

Troubleshooting

Fix setup and runtime issues

Start with the step that failed. If the system passkey sheet never appears, check platform association and build setup first. If the sheet appears and verification fails, check your server challenge, origin, and credential storage.

Expo Go

Expo Go cannot load this native module. Importing the package and calling getPasskeyAvailability still work; ceremony methods reject with ERR_PASSKEY_UNSUPPORTED and point you to a development build or production build.

npx expo run:ios
npx expo run:android

With EAS:

eas build --profile development --platform ios
eas build --profile development --platform android

Web

Web and SSR imports are supported for capability detection only. getPasskeyAvailability() returns { supported: false, platform: "web" }, and ceremony methods reject with ERR_PASSKEY_UNSUPPORTED. Browser WebAuthn ceremonies are a future feature, not part of the current package contract.

ERR_PASSKEY_UNSUPPORTED

The platform cannot run passkey ceremonies.

Check:

  • The app is not running on web (browser WebAuthn is not implemented yet).
  • The app is not running in Expo Go.
  • The device has a supported OS version.
  • The device has screen lock, biometrics, or another user verification method enabled.
  • Android has a credential provider available. On an emulator, sign in with a Google account and set a screen lock first.
  • The app is running on a real device for final testing.

Show another sign-in method when this happens.

if (!getPasskeyAvailability().supported) {
  showEmailFallback();
}

ERR_PASSKEY_CANCELED

The user closed the system passkey UI. This is expected.

if (error instanceof PasskeyError && error.code === "ERR_PASSKEY_CANCELED") {
  return;
}

Do not log this as a production error unless you are measuring funnel drop-off.

ERR_PASSKEY_NO_CREDENTIAL

The platform could not find a passkey that matches the authentication request. This can happen when the user has not registered a passkey for the account, the credential was removed from their password manager, or the rpId/allow-list does not match the stored credential.

Offer another sign-in method or a passkey registration path.

ERR_PASSKEY_INVALID_CREDENTIAL

The platform returned an invalid public-key credential response. Log the code and message, then check the challenge, rpId, association files, and platform logs.

Android No create options available

On Android, a native message like No create options available means Credential Manager could not find any passkey create option that both matches your request and can be handled by an enabled credential provider.

Common causes:

  • The device or emulator has no Google account or no enabled passkey-capable password manager account. Sign in on the Android emulator before testing passkeys.
  • The device or emulator has no secure screen lock, such as PIN, pattern, password, or biometrics.
  • The installed app does not match assetlinks.json, or the RP ID does not match the associated domain.
  • The registration options are malformed or incompatible with the available provider.

After your RP ID, Digital Asset Links, and app signing are known-good, this error is a strong signal that passkey setup is not ready on the device. Show a recovery message such as "Add a Google account or enable a passkey-capable password manager, then try again."

For a typical app-managed passkey flow, a platform authenticator preference is enough. Use stricter residentKey or userVerification values only when your relying party policy requires them:

authenticatorSelection: {
  authenticatorAttachment: "platform",
  residentKey: "preferred",
  userVerification: "preferred",
}

On iOS, unsupported registration constraints such as authenticatorAttachment: "cross-platform", residentKey: "discouraged", or pubKeyCredParams without ES256 (alg: -7) fail with ERR_PASSKEY_VALIDATION before biometric or device-authentication UI. See Platforms for the full reachable attestation, algorithm, attachment, and resident-key policy.

Associations

If native UI fails before a credential is created, verify:

  • iOS Associated Domains contains webcredentials:<domain>.
  • apple-app-site-association is valid JSON with no .json extension in the URL.
  • Android assetlinks.json includes the correct package name and SHA-256 certificate fingerprint.
  • rp.id and rpId use the same RP ID domain as the associated domain.

Use the domain only:

rp: { id: "example.com", name: "Example" }

Not URLs:

rp: { id: "https://example.com", name: "Example" }

Android debug builds, preview builds, and production builds usually need different SHA-256 fingerprints in assetlinks.json.

Server verification

If native registration or authentication succeeds but the server rejects the response, check:

  • The challenge sent to the app matches the challenge stored on the server.
  • The challenge was not reused.
  • expectedOrigin contains the exact origin in clientDataJSON.
  • expectedRPID matches the same RP ID domain as rp.id and rpId.
  • Credential IDs are encoded consistently, usually base64url.
  • The server stores the credential public key after registration.
  • The server updates the sign counter after authentication if your verifier returns one.

For iOS and web ceremonies, the expected origin is normally the HTTPS relying-party origin, such as https://example.com. Android Credential Manager derives an android:apk-key-hash: origin from the installed app's signing certificate. Add only explicitly trusted certificate-derived Android origins to the server allowlist; keep expectedRPID as the associated domain.

Invalid response

ERR_PASSKEY_INVALID_RESPONSE means the native layer returned a value that does not match WebAuthn JSON. Capture the native response in development and compare it to the API docs before sending it to server verification.

Binding drift

Run pnpm bindgen:check if Swift or Kotlin generated files look stale after changing crates/passkey-ffi.

On this page