Skip to content

Passkey (WebAuthn) ​

WebAuthn/FIDO2 registration and authentication: Touch ID, Windows Hello, hardware keys. Mirrors the TS @better-auth/passkey plugin. Requires the passkey extra (pip install "better-auth-server[passkey]", which pulls in webauthn).

Enable ​

python
from better_auth import BetterAuth
from better_auth.plugins_ext import PasskeyPlugin

auth = BetterAuth(
    secret="a-strong-32-character-minimum-secret",
    plugins=[
        PasskeyPlugin(
            rp_id="example.com", rp_name="Example", origin="https://example.com"
        )
    ],
)

Options ​

OptionTypeDefaultDescription
rp_idstr | NoneNone (hostname of base_url)The relying-party id: the registrable domain, no scheme.
rp_namestr"Better Auth"Human-readable relying-party name.
originstr | list[str] | NoneNone (request Origin header)Expected WebAuthn origin(s).
authenticator_selectiondict | NoneNoneWebAuthn authenticator selection criteria, e.g. {"residentKey": "preferred", "userVerification": "preferred"}.
challenge_cookiestr"better-auth-passkey"Name of the signed challenge cookie.
registrationdict | NoneNoneRegistration ceremony overrides.
authenticationdict | NoneNoneAuthentication ceremony overrides.

Endpoints ​

7 routes under /passkey/:

MethodPath
GET/passkey/generate-register-options
POST/passkey/verify-registration
GET/passkey/generate-authenticate-options
POST/passkey/verify-authentication
GET/passkey/list-user-passkeys
POST/passkey/delete-passkey
POST/passkey/update-passkey

Schema ​

TableColumns
passkeyname, publicKey, userId, credentialID, counter, deviceType, backedUp, transports, createdAt, aaguid

Notes ​

  • Cross-runtime storage parity is exact: publicKey is standard padded base64 of the raw COSE bytes, credentialID is unpadded base64url, deviceType is camelCase ("singleDevice"/"multiDevice"). A row written by the TS plugin verifies here and vice versa.
  • Challenges are single-use: a signed cookie (max age 300s) plus a verification row consumed atomically on verify. A challenge only works for the ceremony it was created for: a registration challenge cannot finish an authentication, and a challenge with no ceremony type is refused with CHALLENGE_NOT_FOUND.
  • /passkey/verify-registration accepts createSession: true to sign the user in right after the passkey is saved. The response is then the passkey row plus session and user, and the session cookie is set.
  • Importing better_auth.plugins_ext without the passkey extra installed raises ModuleNotFoundError.

MIT licensed · API-compatible with better-auth