Passkeys

The account you registered last week
should still let you in.

Every profile carries its own authenticator. A passkey enrolled inside a profile is captured, kept on your disk, and replayed the next time that identity signs in - next process, next week, or on another machine.

Why this breaks

A passkey belongs to a device. You are running many identities.

Sites are switching sign-in to passkeys by default, and a passkey is deliberately bound to the authenticator that created it. On an ordinary machine that authenticator is the machine, which is fine for one personal account and wrong for everything else you do here.

One keychain, many accounts

The platform authenticator is shared by every profile on the computer. Register a second account on the same site and you are asking one device to hold two identities that were never supposed to meet.

Automation cannot reach it

Touch ID and Windows Hello want a human finger. An agent that hits a passkey prompt stops there, which is how a script that logged in fine last month quietly becomes a script that cannot log in at all.

The identity does not move

Change machine and the credential stays behind, because that is exactly what hardware binding is for. Your cookies moved. Your passkey did not, and the account is now unreachable from the new machine.

enrol & replay turn it off
// Nothing to enable. Capture and replay are on by default.
const { browser, page } = await ab.launch({ profile: "seller-04" })

// Register a passkey on the site, by hand or with the agent.
// It lands in <profile>/passkeys.json, not in the OS keychain.
await page.goto("https://webauthn.io")

// Next week, next process, next machine: same profile, still signed in.
const { page: later } = await ab.launch({ profile: "seller-04" })
await later.getByRole("button", { name: "Sign in with a passkey" }).click()
// Per profile, if you would rather approve each enrolment yourself.
await ab.launch({ profile: "seller-04", webauthnCapture: false })

# Python, same profile format, same store
browser = await launch_async(profile="seller-04", webauthn_capture=False)

One authenticator per profile.

The profile is the device. Its authenticator answers the site's WebAuthn call inside the browser, and the credential is written to a file in that profile rather than to the computer's keychain.

Enrolment is captured automatically. On by default when you create a profile. Nothing to wire up, and no prompt to click through on every registration.
Replay needs no special code. The agent, or you, clicks the site's own sign-in button. The assertion is answered below the automation layer.
It travels with the identity. The store is part of the profile, so a profile export or an opt-in cloud sync carries the passkey along with the cookies and the fingerprint.
Off is a per-profile switch. Turn capture off for a profile and every enrolment asks first, which is what you want on an identity you handle by hand.

The test we run against this is deliberately brutal. Register a passkey on webauthn.io in a profile, close the browser, delete the entire local profile directory, then restore the same profile on a clean directory and sign in using nothing but that passkey. It is part of the suite for both the Node and Python SDKs, so it has to keep passing to ship. Repeat it yourself on the free tier: the walkthrough is here.

What it is actually used for.

Many accounts, one site

Each profile enrols its own credential, so accounts on the same site never share an authenticator. The site sees separate devices because, in every way it can check, they are.

Agents that hold accounts

An agent registering today and returning next week needs the same credential to still be there. Randomising per launch is the opposite of what a long-lived identity needs. More on agent setups.

Handing an identity over

Export the profile and the person who imports it can sign in, without a password reset, an SMS code, or a shared phone. The credential is in the file they received.

Limits, stated plainly

What this does not do.

Device-bound sessions are a different thing

Google can bind a session to a key held in the TPM or Secure Enclave, and that key is not exportable by design. Copying the profile does not copy it, so we disable that mechanism rather than let it half-work. What crosses the network, and what does not.

It is not a password manager

This stores WebAuthn credentials for the profile that created them. It does not sync your passwords, and it does not import passkeys out of iCloud Keychain or a phone.

A passkey is not a clean record

Staying signed in is not the same as being trusted. A burnt account, a bad proxy or robotic behaviour still gets flagged, and nothing on this page changes that.

Questions, answered.

In a file inside the profile directory, next to that profile's cookies and storage. It is not written to the macOS keychain, Windows Hello or a phone, which is what makes it belong to the identity rather than to the machine. Export the profile and the passkey goes with it.

Yes, and that is the point. Each profile has its own authenticator, so ten profiles are ten separate devices as far as the site is concerned. A platform authenticator on a normal machine is one device shared by every account, which is why the second account is usually where this breaks.

It sees a platform authenticator and gets a valid WebAuthn assertion. What it cannot do is read a serial number that identifies our software, because there is nothing vendor-specific in the credential. Nobody should promise more than that: WebAuthn attestation is a live area and any site that demands hardware attestation from a real security key is asking for something no software authenticator can produce.

With cloud sync on for that profile, the passkey store travels with it and the account signs in on the other machine. With sync off, nothing leaves your disk, and you move the identity yourself with a profile export. Both paths carry the same file.

Passkeys, yes. Google also runs device-bound sessions whose private key sits in the TPM or Secure Enclave and is not exportable by design, so that part cannot be moved by us or anyone else. We turn that mechanism off rather than pretend the copy works. It is described on the security page.

No. The agent clicks the same button a person clicks. There is no separate passkey API to call, because the credential is answered inside the browser, below the automation layer.

Sign in once. Keep the account.

Unlimited local profiles on the free tier, each with its own authenticator. No credit card.

Windows, macOS & Linux · Node 18+ or Python 3.9+