Skip to content
Customer Identity · available nowBuild your first Test integration.Create a Test project
NamoID
All posts
NamoID Blog

Passwordless Sign-In with Passkey Autofill

Add passkey autofill without breaking existing sign-in. Review conditional UI, cancellation, challenge handling, fallback choices, and server verification.

A passkey button is useful. A sign-in form that offers the right passkey while someone enters their account is more useful.

Passkey autofill uses conditional mediation: the browser waits for the user to select an available credential. It can coexist with your existing sign-in form.

This guide covers that coexistence. It focuses on cancellation, server state, and failure handling rather than another introduction to public-key cryptography.

Keep one form and two deliberate choices

A relying party is the service that accepts the credential. Its server owns the authentication decision.

The browser supplies the credential-selection interface. Your application should still offer a clear alternative when that interface is unavailable.

The passkeys.dev bootstrapping guide describes the username field and conditional request used for this experience.

<label for="account">Email or username</label>
<input id="account" name="account" autocomplete="username webauthn">
<button type="submit">Continue</button>

The autofill annotation belongs on the account field. It does not enrol a credential or verify a person.

Keep the alternative action understandable. “Use another method” should lead to a defined flow, not silently lower the assurance requirement.

Model the browser operation as a cancellable task

A conditional request can remain pending while the user considers another method.

Treat it as a task with an owner. Cancel that task when the component unmounts or the user chooses a competing authentication flow.

Sign-in form
  |-- start conditional request --> browser waits
  |-- user selects a passkey ----> server verifies --> session
  '-- user chooses another path -> cancel request --> alternative flow

Avoid starting a second credential request while the first remains active. Component rerenders should not create additional authentication attempts.

A cancelled request is an expected state. It should not appear as an account-security incident or a failed password attempt.

Use an explicit integration contract

Download the example controller. It separates browser behaviour from your application's server calls.

import { startPasskeyAutofill } from "/blog/resources/passkey-autofill-controller.mjs";
 
const task = startPasskeyAutofill({
  loadOptions: async ({ signal }) => {
    const response = await fetch("/example/passkeys/options", {
      method: "POST",
      signal,
    });
    if (!response.ok) throw new Error("Options unavailable");
    return response.json();
  },
  verify: async (assertion, { signal }) => {
    const response = await fetch("/example/passkeys/verify", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(assertion),
      signal,
    });
    if (!response.ok) throw new Error("Verification failed");
  },
});

These are example application routes, not NamoID API endpoints. Implement their authentication context, request protection, rate limits, and response contract before use.

Call task.cancel() before starting the alternative flow. Await task.done when you need the resulting status.

Once verification starts, disable competing sign-in actions until completion. Aborting a fetch cannot undo a session already created by the server.

The controller requires the WebAuthn JSON helpers. Browsers without those helpers return an unavailable status; your existing form remains usable.

WebAuthn Level 3 defines JSON conversion and conditional authentication. Feature detection is safer than browser-name guesses.

Give each challenge a bounded lifetime

A browser request may wait longer than your server permits. Decide what happens when the stored challenge expires.

Return a recoverable result and offer a fresh attempt. Do not extend an old challenge indefinitely because the page remains open.

Associate the response with the correct authentication attempt. Do not resolve it through a mutable global “latest challenge” value.

For concurrent tabs, define whether each tab receives independent state. A success in one tab should not accidentally authenticate the other attempt.

This is application state management. The browser's credential picker cannot supply that policy for you.

Verify before creating the session

A returned credential is input to verification, not proof that verification has succeeded.

Use a maintained WebAuthn server library, such as the implementation described in py_webauthn's authentication documentation. Configure the expected challenge, relying-party identifier, permitted origin, and required user verification.

Look up the registered public key and account through your server's credential records. Do not accept the browser's account label as authoritative.

Create the application session only after verification succeeds. Follow your usual session-rotation and cookie rules.

NamoID's current passkey helpers use environment-specific relying-party identifiers and single-use Redis challenges. Custom-domain changes therefore need an enrolment plan.

A passkey registered for an old identifier does not automatically become a credential for a new one.

Test the paths users actually take

Download the release checklist. Record the browser, operating system, credential provider, and result for each run.

ScenarioExpected behaviour
Conditional mediation unavailableExisting sign-in remains usable
No eligible credentialUser can choose another method
User selects the alternativePending passkey request is cancelled
Challenge expires before selectionServer rejects it; interface offers a fresh attempt
Component unmounts during options fetchNo later credential prompt starts
Wrong origin or relying-party identifierVerification fails without a session
Existing account has several credentialsThe selected registered credential identifies the account

The downloadable controller has mock-based lifecycle tests. Those tests do not establish compatibility with a particular browser or physical authenticator.

Use the passkey adoption guide for rollout planning. Review the account-recovery guide before removing an existing method.

Get help with your passwordless integration

Contact the NamoID team with your framework, sign-in flow, and target devices. We can help review passkey integration and fallback decisions.

Message our engineers in Slack for an early response. Include sanitized errors and the step where the flow stops. Keep credentials and customer data out of the message.