---
title: "Offer passkeys first · Agent Wranglers"
url: https://agentwranglers.se/playbook/accounts/sign-in-methods/passkeys-first
updated: 2026-10-20
licence: "All rights reserved"
---

# Offer passkeys first

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Recommended
- Chapter: [Sign-in methods](https://agentwranglers.se/playbook/accounts/sign-in-methods)
- Rule: Offer passkeys as the main way to sign in, through autofill. Invite people to create one right after they sign in another way.
- How: Follow WebAuthn Level 3, a W3C Recommendation since 25 August 2026. On the sign-in page, check `PublicKeyCredential.isConditionalMediationAvailable()`, then start a request with `mediation: 'conditional'`. Give the username field `autocomplete="username webauthn"`. After a password sign-in, offer a passkey. Where `PublicKeyCredential.getClientCapabilities()` reports `conditionalCreate`, call `navigator.credentials.create()` with `mediation: 'conditional'`, and the password manager can add one without a prompt. Set `residentKey` to required and `userVerification` to preferred. Make `user.id` random bytes, at most 64, with no email or name in it. Store the credential ID, public key, counter, transports, backup flags and a name. Allow several passkeys per account. The [passkey addresses rule](https://agentwranglers.se/playbook/website/standard-addresses/passkey-endpoints) points password managers to your pages.
- Check: In a browser test with a virtual authenticator, such as Playwright over the Chrome DevTools Protocol, register a passkey and sign in through autofill. Both succeed. The stored user handle holds no email or name.

Example:

```js
if (await PublicKeyCredential.isConditionalMediationAvailable?.()) {
  const response = await fetch('/passkeys/sign-in-options');
  const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(await response.json());
  const credential = await navigator.credentials.get({ publicKey, mediation: 'conditional' });
  await fetch('/passkeys/sign-in', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(credential.toJSON()),
  });
}
```

```python
from webauthn import generate_registration_options, options_to_json
from webauthn.helpers.structs import (
    AuthenticatorSelectionCriteria, PublicKeyCredentialDescriptor, ResidentKeyRequirement, UserVerificationRequirement,
)

def passkey_registration_options(user) -> str:
    options = generate_registration_options(
        rp_id="example.com",
        rp_name="Example Bakery",
        user_id=user.webauthn_handle,  # 32 random bytes from secrets.token_bytes, made once per user
        user_name=user.email,
        authenticator_selection=AuthenticatorSelectionCriteria(
            resident_key=ResidentKeyRequirement.REQUIRED,
            user_verification=UserVerificationRequirement.PREFERRED,
        ),
        exclude_credentials=[PublicKeyCredentialDescriptor(id=p.credential_id) for p in user.passkeys],
    )
    return options_to_json(options)
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers accounts playbook: https://agentwranglers.se/playbook/accounts/sign-in-methods/passkeys-first`.

- Previous: [Keep your own user ID](https://agentwranglers.se/playbook/accounts/sign-in-methods/own-user-id)
- Next: [Verify every passkey answer on the server](https://agentwranglers.se/playbook/accounts/sign-in-methods/passkey-verification)
- [Sign-in methods](https://agentwranglers.se/playbook/accounts/sign-in-methods)
