FerrisKeyDocs

WebAuthn (FIDO2 passkeys)

WebAuthn lets users sign in with a hardware security key, platform biometrics (Touch ID, Face ID, Windows Hello) or a synced passkey (iCloud Keychain, Google Password Manager). The private key never leaves the user’s device. FerrisKey only stores the public key.

How it works

WebAuthn uses public-key cryptography. During registration the user’s device creates a key pair and sends the public key to FerrisKey. During sign-in the device signs a challenge with the private key, and FerrisKey checks the signature against the stored public key. There is no shared secret to phish or to leak from the server, and the biometric or PIN check happens on the device.

FerrisKey requires that check. An assertion the authenticator did not mark as user-verified is refused with a 401, so a security key that only needs a touch, with no PIN or biometric, cannot sign anyone in.

A passkey is a way in, not a second factor

A passkey signs the user in on its own. FerrisKey does not ask for a passkey after a password: the only second-factor challenge it issues is the TOTP one. A user who has passkeys but no authenticator faces no further challenge once the password is right.

Where the origin comes from

The WebAuthn origin is the web app URL (WEBAPP_URL, or the request’s host when WEBAPP_URL_FROM_REQUEST is on), and the relying-party ID is that URL’s hostname. A passkey registered at login.example.com does not work at auth.example.com. Pick the hostname before users start registering. Configuration lists both variables.

Registering a passkey

A user gets a passkey in one of two ways. From the account page, they use the self-service flow in Account security, which needs a fresh password check. At sign-in, the configure_passkey required action sends them through the steps below.

When an administrator updates a user’s required actions, FerrisKey refuses configure_passkey for a user who has no credential at all, so a passkey is added on top of an existing way to sign in.

Ask for creation options

The portal calls POST /login-actions/webauthn-public-key-create-options with the step token. FerrisKey builds a registration challenge, excludes the passkeys the user already holds, and stores the challenge on the auth session.

The browser prompts the user

The browser’s navigator.credentials.create() asks for a Touch ID scan, Face ID, or a tap on a hardware key. The authenticator returns the public key, the credential ID and attestation data.

Send the credential back

The portal calls POST /login-actions/webauthn-public-key-create. FerrisKey refuses it with a 403 unless configure_passkey is still due for this sign-in. It checks the response against the stored challenge, which stays valid for 5 minutes, then saves the public key as a credential of type webauthn-public-key-credential and removes the required action.

Signing in with a passkey

Turn on passkey_enabled on the realm (Authentication > Sign-in methods in the console) and the sign-in page shows a passkey button. The setting only controls that button: the passkey endpoints do not check it.

Ask for request options

The portal calls POST /login-actions/passkey-request-options. With a username in the body, the challenge is scoped to that user’s passkeys, and a user with none gets a 401. Without one, the challenge is discoverable and the browser proposes the passkeys it holds, so the page cannot be used to probe which accounts exist.

The user authenticates

The browser calls navigator.credentials.get(). The authenticator prompts for a biometric or PIN and signs the challenge.

Signature verified

The portal calls POST /login-actions/passkey-authenticate with the assertion. FerrisKey verifies the signature, checks that the credential belongs to the user named by the assertion’s user handle, and requires user verification. On success the response carries login_url, which holds the authorization code, and the SSO session cookie is set.

Required actions still apply

A user-verified passkey answers the TOTP challenge, so a user who has both a passkey and an authenticator is not asked for a code. It does not skip required actions: if update_password, verify_email or configure_otp is due, passkey-authenticate answers 403 and the sign-in does not complete. The client’s maintenance and consent rules apply as well.

Supported authenticators

TypeExamplesCross-device
PlatformTouch ID, Face ID, Windows Hello, Android biometricsNo
RoamingYubiKey, SoloKeys, Titan Security KeyYes
Synced passkeysiCloud Keychain, Google Password ManagerYes

Patterns

Passkey-first sign-in

Combine passkeys with the authorization code flow. The user clicks the passkey button, authenticates with their device and is redirected back with a code, with no password typed. The password stays accepted next to the passkey, so this removes typing rather than the password itself.

Passkeys next to TOTP

Offer passkeys to users whose devices support them. Those who do not keep their password and, if you require it, an authenticator. Both paths end at the same token endpoint.

Multiple passkeys

Users can register several passkeys, for example Touch ID on a laptop and a YubiKey as a backup. Each is stored on its own and any one of them signs the user in. Removing one is refused if it would leave the account with no way to sign in.