Prompt and scope
You own a website that already has password login and want to add Passkeys. On supported browsers, users should authenticate with a platform unlock or security key while retaining understandable fallback and recovery paths. Design the frontend and server collaboration, including navigator.credentials.create(), navigator.credentials.get(), challenges, RP IDs, origin checks, and credential lifecycle boundaries.
WebAuthn is a public-key authentication API between the browser and an authenticator. The private key stays in the authenticator and the website stores the public key. The frontend obtains server options, calls the browser API, serializes the result, and renders state; the server must verify signatures, consume challenges, and bind credentials to accounts.
What the interviewer is testing
A strong answer separates registration and sign-in into two short stateful flows: the server creates a one-time unpredictable challenge, the frontend calls the authenticator, and the server verifies the returned challenge, origin, RP ID, signature, and counter before creating a session. It also covers HTTPS secure contexts, user cancellation, timeouts, device migration, multiple credentials, and recovery instead of treating “Face ID appeared” as protocol success.
The interviewer will notice whether you understand that conditional autofill is a UX capability, not a substitute for server verification. They may also ask why deleting a server credential should be followed by WebAuthn signal APIs so the authenticator can update its state.
Questions to clarify first
Supported clients and account policy
Confirm target browsers, mobile and desktop platforms, whether synced discoverable credentials are allowed, and whether passwords or security keys remain available. Those choices affect residentKey, userVerification, credential selection, and help text.
Domains and deployment topology
Confirm production and login subdomains, iframes, reverse proxies, and tenant-specific domains. The RP ID must be a valid related domain for the current origin. A preview domain moving to production cannot safely reuse configuration by assumption.
Recovery and high-risk actions
Ask how a user proves control when every authenticator is lost, and whether reset revokes sessions, notifies the user, or delays high-risk actions. Recovery is part of the identity lifecycle; it cannot be reduced to a frontend “use password” link.
30-second answer framework
“Both registration and sign-in start with a one-time unpredictable challenge from the server. The frontend passes options to WebAuthn. During registration, the server verifies the challenge, origin, RP ID, attestation policy, and public key before binding the credential ID to the account. During sign-in, it verifies the assertion signature, challenge, RP ID, origin, and signature counter before creating a session. The frontend distinguishes unsupported, cancelled, timed-out, and rejected states. Conditional mediation can provide autofill but is not required for correctness. Losing a device goes through a risk-controlled recovery, revokes old sessions, and allows a new credential to be registered.”
Step-by-step solution
Step 1: Let the server create the challenge
The registration or sign-in page first asks the server to create a flow. The server generates at least 16 bytes of random challenge data and stores its hash, account, purpose, expiry, and consumed state. The frontend must not generate or reuse it: otherwise an old assertion could be moved to another flow. The server returns publicKey options for the current RP, and the frontend does not rewrite security-sensitive fields.
Step 2: Design registration
In an HTTPS secure context, the frontend calls navigator.credentials.create(). Options include the RP, user ID, display name, accepted public-key algorithms, user-verification preference, and discoverable-credential policy. After the Promise resolves, the frontend serializes the credential ID, client data, attestation response, and required extensions to the server. The private key never leaves the authenticator.
Step 3: Verify registration on the server
The server checks that the challenge matches the flow, the origin is allowed, the RP ID hash is correct, and the signature and public-key algorithm satisfy policy. It decides whether to validate attestation based on privacy and compatibility goals. On success it stores only the public key, credential ID, account, signature counter, and necessary device label. It should not retain the entire raw object or identifiable device data forever.
Step 4: Design sign-in and conditional autofill
For sign-in, the server creates a new challenge and the frontend calls navigator.credentials.get() with it, then submits the assertion. The server verifies the challenge, origin, RP ID, signature, and counter before resolving the account from the credential ID. With conditional mediation, the page can request discoverable credentials after the user interacts with the username field, allowing the browser to show Passkeys in autofill. This changes discovery UX, not the verification contract.
Step 5: Handle frontend state and failures
Cancellation, timeout, unsupported browsers, blocked permission policy, and server rejection should become distinct user-understandable states. AbortController can cancel a pending call when the user leaves or clicks again, preventing an old Promise from overwriting current state. The frontend should not display signatures, credential IDs, or internal server errors, and should never mark an incomplete registration as successful.
Step 6: Recovery, revocation, and credential management
Account settings should list credential labels, creation time, and last-used time, and allow one credential to be deleted. After the server deletes it, a later unknown credential ID can trigger PublicKeyCredential.signalUnknownCredential(); a successful sign-in can use signalAllAcceptedCredentials() to synchronize IDs still accepted by the server. Losing every authenticator uses a risk-controlled recovery such as a password plus risk checks, a verified email, or support review. Revoke old sessions and then require a new Passkey registration.
Step 7: Verify and evolve
Test different browsers, platform authenticators, synced credentials, security keys, cancellation, timeout, bad origins, expired and repeated challenges, counter anomalies, and post-recovery old credentials. Detect capabilities and receive policy from the server instead of guessing from user-agent strings. New extensions should be optional with fallback UI, and an unknown extension must not change core server verification.
High-quality sample answer
I would treat Passkey as a server-driven public-key authentication flow. When a user starts registration, the frontend requests a one-time challenge and publicKey options from the server, then calls WebAuthn over HTTPS. The server alone verifies the challenge, origin, RP ID, signature, algorithm, and any required attestation, then stores the credential ID, public key, counter, and account binding. The private key remains in the authenticator.
Sign-in starts with another server challenge. The frontend calls navigator.credentials.get(), submits the assertion, and the server verifies its signature, challenge, origin, RP ID, and counter before creating a session. If conditional mediation is supported, I offer Passkey autofill after username-field interaction, but it follows the same server verification.
The frontend distinguishes unsupported, cancelled, timed-out, and rejected states, and cancels pending calls when the page is abandoned. Account settings list and delete credentials; after deletion, WebAuthn signals keep the browser from suggesting an unknown credential. Losing a device requires risk-controlled recovery, revokes old sessions, notifies the user, and registers a replacement credential. Tests cover cross-platform authenticators, wrong origins, expired or repeated challenges, counter anomalies, browser fallback, and old credentials after recovery so UX changes never weaken protocol verification.
Common mistakes
- Mistake: Generating the challenge in the frontend or putting it in a page constant. → Why it fails: An attacker can replay an old assertion and the server cannot establish flow freshness. → Fix: Generate it on the server, store it briefly, consume it once, and bind it to the account and purpose.
- Mistake: Signing the user in when the Promise resolves. → Why it fails: A browser object is not proof that the server verified origin, RP ID, and signature. → Fix: Complete assertion validation on the server and let the frontend render structured outcomes.
- Mistake: Detecting support with user-agent strings. → Why it fails: Browser versions, platform authenticators, and permission policy vary. → Fix: Use capability APIs and server policy, then provide an explicit password or security-key route.
- Mistake: Deleting the database credential without synchronizing device state. → Why it fails: The authenticator still believes the credential exists and keeps offering an unusable option. → Fix: Call WebAuthn signal APIs in an appropriate authenticated state and provide a re-registration path.
Follow-up questions and responses
Follow-up 1: Why can’t the RP ID be any API domain?
The RP ID must be a related domain for the current origin and is checked by both browser and server. An unrelated value makes creation fail or expands the trust scope. For multi-tenant domains, explicitly configure each trusted domain; never accept an arbitrary client-provided string.
Follow-up 2: The user registered on a phone, but the desktop has no key. What now?
If synced discoverable credentials are allowed, the browser and credential manager may provide the same account’s Passkey across devices. Otherwise offer a cross-device QR flow or a security key. The frontend should say that the device cannot use the credential and provide an action; the server still verifies the same challenge and assertion rules, including origin.
Follow-up 3: Should a counter rollback lock the account immediately?
First distinguish synced platform credentials, backup restoration, and genuine cloning risk. Treat an anomaly as a risk signal that can require extra verification and notify the user, rather than permanently locking without context. High-value actions can require another registered credential or support review, with the decision recorded for policy tuning.
Follow-up 4: Does recovery weaken Passkey security?
It does if recovery relies only on a potentially compromised email link. Use tiered risk checks, session revocation, a cooling period, notification, and delayed high-risk actions. After recovery, register a new credential and remove old ones. Explain the availability versus account-takeover trade-off instead of promising that users can never lose access.