Passkeys (WebAuthn)
Autentico supports WebAuthn (FIDO2) passkeys via the go-webauthn/webauthn library. Passkeys use public-key cryptography bound to the user’s device — platform authenticators (Face ID, Touch ID, Windows Hello) and hardware security keys (YubiKey, etc.) are both supported.
Passkey authentication flow
Section titled “Passkey authentication flow”sequenceDiagram
actor User
participant Browser
participant IdP as Autentico
Browser->>IdP: GET /oauth2/passkey/login/begin?username=...&client_id=...
IdP->>IdP: Look up user credentials
IdP->>Browser: { type: "authentication", challenge_id, options }
Browser->>Browser: navigator.credentials.get(options)
User->>Browser: Approve with biometric / security key
Browser->>IdP: POST /oauth2/passkey/login/finish?challenge_id=X
IdP->>IdP: Verify assertion — issue auth code
IdP->>Browser: { redirect: "redirect_uri?code=AUTH_CODE" }
Browser->>Browser: Navigate to redirect URL
Challenges are short-lived (5 minutes) and single-use. The challenge data is stored in the passkey_challenges table.
Passkey signup
Section titled “Passkey signup”New accounts are created through Create account when allow_self_signup is enabled for the client and auth_mode is password_and_passkey or passkey_only. Both the start and completion endpoints check the current policy, including the client’s self-signup override. Disabling signup during an unfinished ceremony prevents its completion. Switching to password also blocks named and discoverable passkey login, including ceremonies already in progress; disabling signup alone still permits existing-user login. Existing users can still add passkeys through Account → Security.
The browser starts a ceremony at GET /oauth2/passkey/register/begin and submits the WebAuthn response to POST /oauth2/passkey/register/finish. Both endpoints use the authentication rate limiter. An HttpOnly, host-only cookie binds the five-minute registration to the initiating browser. Other browsers cannot replace a live pending account or complete its ceremony. The initiating browser can retry; abandoned usernames become available once the registration window expires.
When require_email_verification is enabled, signup requires an email address and configured SMTP delivery. After creating the passkey, the user receives a verification email and sees the verification page. Autentico issues no session or authorization code until the email link is used. Logging in with that passkey, including discoverable login, continues to the verification page until verification succeeds. The page also offers the existing resend action if delivery failed.
Relying party configuration
Section titled “Relying party configuration”The WebAuthn relying party name — shown in the browser’s passkey prompt — is controlled by the passkey_rp_name runtime setting. Set it to your application or organization name.
The relying party ID is derived from AUTENTICO_APP_URL (the hostname component). This is a security boundary in WebAuthn: credentials registered for auth.example.com cannot be used on a different origin.
Enabling passkeys
Section titled “Enabling passkeys”Set auth_mode to password_and_passkey or passkey_only:
curl -X PUT https://auth.example.com/admin/api/settings \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"auth_mode": "password_and_passkey", "passkey_rp_name": "My Company"}'The login page will show the passkey option automatically once this is set.
Login modes
Section titled “Login modes”The passkey_login_mode setting controls how passkeys are presented on the login page. Each mode suits different deployment scenarios and user bases.
username_first (default)
Section titled “username_first (default)”The user types their username first, then authenticates with their registered passkey. The server looks up the user’s credentials and sends them to the browser as allowCredentials, so the authenticator only prompts for matching passkeys.
This is the safest default — it works with every authenticator type and doesn’t depend on discoverable credential support.
discoverable
Section titled “discoverable”A “Sign in with passkey” button appears on the login page. Clicking it triggers a discoverable credential ceremony — the browser or authenticator shows all passkeys registered for this relying party, and the user picks one. No username is needed.
This works best with:
- Apple devices — iCloud Keychain shows an account picker natively
- Windows Hello — built-in account selection
- Hardware security keys — YubiKeys and other FIDO2 keys with resident key storage
The username and password fields remain visible for users who prefer password login or don’t have a passkey registered.
conditional
Section titled “conditional”The browser automatically surfaces registered passkeys in the username field’s autofill dropdown when the login page loads. The user sees their passkey alongside saved passwords without clicking any button — they simply focus the username field and pick a passkey from the autofill suggestions.
This is the most seamless mode for environments where users rely on password managers (Chrome + Google Password Manager, Safari + iCloud Keychain, Firefox). The passkey appears as just another autofill option.
Technically, this uses navigator.credentials.get() with mediation: 'conditional' and adds autocomplete="username webauthn" to the username input. If the browser doesn’t support conditional mediation, the passkey button remains available as a fallback.
passkey_only
Section titled “passkey_only”No username or password fields are shown — only a “Sign in with passkey” button. This is for deployments that have fully committed to passwordless authentication.
Choosing a mode
Section titled “Choosing a mode”| Mode | Best for | Username required | Password fallback |
|---|---|---|---|
username_first |
Maximum compatibility, gradual passkey adoption | Yes | Yes |
discoverable |
Platform authenticators (Apple, Windows Hello), hardware keys | No | Yes |
conditional |
Password managers, autofill-heavy environments | No | Yes |
passkey_only |
Fully passwordless deployments | No | No |
Configuring via API
Section titled “Configuring via API”curl -X PUT https://auth.example.com/admin/api/settings \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"passkey_login_mode": "discoverable"}'The setting takes effect immediately — the next login page render will use the new mode.
Discoverable credentials (resident keys)
Section titled “Discoverable credentials (resident keys)”Discoverable passkey login (discoverable, conditional, and passkey_only modes) relies on resident keys — credentials stored on the authenticator itself, not just on the server. During registration, Autentico requests ResidentKey: preferred, which means most modern authenticators will create a discoverable credential by default.
The authenticator stores a userHandle (the user’s internal ID) alongside the private key. During a discoverable login, the authenticator returns this userHandle in its signed assertion, allowing the server to identify the user without a username.