Skip to main content
Supabase Auth’s passkey API is an experimental beta. This plugin pins against current GoTrue behaviour and may need updates if that API changes.
The plugin owns every server round-trip. Only the OS credential prompt is delegated, because WKWebView gates navigator.credentials behind an Apple-approved-browser entitlement — the webview cannot run WebAuthn on macOS at all.

Two ways passkeys become unavailable

They surface differently on purpose:

Device capability

getPasskeyCapability() answers “can this device run a prompt?”. Free, offline, no network. Check it before rendering any passkey UI.

Project configuration

Passkeys disabled on the server surfaces as a configuration error at call time, with the offending setting named in the message.

Ceremony providers

The OS prompt runs behind a CeremonyProvider trait. Precedence:
  1. An app-supplied provider via PluginBuilder::ceremony_provider
  2. The built-in provider for the compile target
  3. Neither — passkeys report unusable
Both built-ins are compiled behind cfg(target_os = …), so a Linux build carries neither.

Supplying your own

options is passed through verbatim as the server’s WebAuthn options JSON, and CeremonyOutcome::Cancelled is a first-class non-error outcome. Providers are invoked on a blocking thread and no timeout of the plugin’s spans the prompt — the server’s challenge TTL is the effective ceiling. The JavaScript route needs the four two-step permissions granted.

Project prerequisites

One-time work for the project owner:
1

Enable passkeys

Dashboard under Authentication → Passkeys, or [auth.passkey] enabled = true in supabase/config.toml for a local stack, or GOTRUE_PASSKEY_ENABLED=true self-hosted.
2

Set the relying-party config

GOTRUE_WEBAUTHN_RP_ID (a bare domain you control), GOTRUE_WEBAUTHN_RP_DISPLAY_NAME, and GOTRUE_WEBAUTHN_RP_ORIGINS — which must include the origin your ceremony asserts.
Changing rp_id later invalidates every enrolled passkey.
3

Optional limits

GOTRUE_PASSKEY_MAX_PASSKEYS_PER_USER (default 10) and GOTRUE_WEBAUTHN_CHALLENGE_EXPIRY_DURATION (default 5 minutes).
4

macOS only: associated domains

A signed build, an App ID with the Associated Domains capability, and an apple-app-site-association file with webcredentials served over HTTPS from https://<rp-id>/.well-known/apple-app-site-association.

Using it

Or through the bindings directly.

Traps worth knowing

  • Registration always requires an authenticated user. A passkey binds to an existing account, so there is no passkey-first sign-up. Sign in another way, then register.
  • Deleting the last passkey is not blocked server-side. Warn before it happens in your UI, and keep another sign-in method on every account.
  • On macOS, an unentitled build reports passkeys as usable. Availability keys on the macOS version floor alone, so the entitlement failure only surfaces at prompt time. A build that looks fine can fail at the prompt.
  • rp_id cannot stay localhost for macOS native ceremonies. An associated domain must be a real domain you control, so rp_id and rp_origins both have to move — and moving rp_id invalidates enrolled passkeys.