Passkeys in the browser
navigator.credentials with PublicKeyCredential
The browser half of a passkey flow is two calls: create() to register a credential and get() to sign a challenge. The reason a wrapper existed is that both used to need every challenge and id field converted to and from base64url by hand. parseCreationOptionsFromJSON and toJSON now do that conversion in the browser, so the server's JSON goes in and the response comes back out in the same shape.
When this applies
Registering or authenticating a passkey from the browser.
The native approach
// The server sends JSON; the browser parses it into the
// ArrayBuffer-shaped options the call expects.
const options = PublicKeyCredential.parseCreationOptionsFromJSON(fromServer);
const credential = await navigator.credentials.create({
publicKey: options,
});
// And back to JSON for the response, without hand-rolled base64url.
await fetch("/register", {
method: "POST",
body: JSON.stringify(credential.toJSON()),
});When the dependency is still right
An answer that always says "the platform covers it" is worse than no answer. These are the cases where this one does not hold.
- Your support target reaches below Chrome 129, Firefox 119 or Safari 18.4. The ceremony itself is years older than those, so the credential calls work while the JSON helpers that make the wrapper unnecessary do not, and the base64url conversion comes back.
- You use the library's server package as well. @simplewebauthn/server generates and verifies the challenge, and the platform has no counterpart for that: this covers the browser side only.
- You rely on the library's browser-capability helpers, such as its checks for a platform authenticator or for conditional UI, which are feature detection it has already written.
- You need the same code path on a browser where conditional mediation behaves differently, which the library smooths over and the raw call does not.
Signs it was hand-rolled
No package is involved in any of these, so nothing would match in a package.json. If the code looks like one of them, this rule applies anyway, and the conditions above still decide.
- base64url encode and decode helpers written next to a credentials.create call to convert challenge and id fields
- a Uint8Array built by looping over atob output to turn a server challenge into bytes