WebAuthn options JSON builder / inspector
Save on GoogleFree WebAuthn and passkey options builder. Build registration or authentication options, or paste JSON to check it. Everything runs in your browser. Nothing is uploaded.
Configure WebAuthn options
Results & Output
What does a WebAuthn options builder and inspector do?
WebAuthn lets websites sign users in with passkeys, security keys and device biometrics. Before the browser can ask for a credential, your server sends an options object. This tool builds that object from a form, or inspects JSON you paste, and checks it for common mistakes such as a padded challenge, an rp.id that includes https://, a string algorithm, or an email used as the user ID. Browsers and libraries differ slightly, so treat the results as guidance and test in your target browsers.
WebAuthn developer references:
Click Copy to copy a snippet. Snippets are examples, so replace placeholders such as example.com and <credentialId> with your own values. Browser and library support changes, so check current documentation for newer features.
| Developer Question | Copy-ready Snippet | Explanation |
|---|---|---|
| What is WebAuthn? | if (window.PublicKeyCredential) { /* WebAuthn is available */ } | A web standard for passwordless sign-in. The device keeps a private key and your server stores only the public key. |
| What is a passkey? | "authenticatorSelection": { "residentKey": "required", "userVerification": "required" } | A WebAuthn credential that is discoverable and that a platform can sync across a user’s devices. |
| What is the relying party (RP)? | "rp": { "id": "example.com", "name": "Example Inc" } | Your website or service. Its rp.id is the domain that each credential is bound to. |
| What is an authenticator? | "authenticatorAttachment": "platform" | The device or key that creates and stores credentials, such as a phone, a laptop or a security key. |
| Platform vs cross-platform authenticator | "authenticatorAttachment": "cross-platform" | Platform means built in, such as Touch ID or Windows Hello. Cross-platform means roaming, such as a USB security key. |
| What is a discoverable credential (resident key)? | "residentKey": "required" | A credential stored on the authenticator so users can sign in without typing a username. |
| WebAuthn vs FIDO2 vs CTAP | // WebAuthn (browser API) + CTAP2 (authenticator protocol) = FIDO2 | WebAuthn is the browser API, CTAP is how the browser talks to the authenticator, and FIDO2 is the two together. |
| Does WebAuthn need HTTPS? | if (!window.isSecureContext) console.warn("WebAuthn needs HTTPS"); | Yes, it needs a secure context. http://localhost is allowed for development. |
| Which browsers support WebAuthn? | const supported = !!window.PublicKeyCredential; | All current major browsers support the core API. Check compatibility tables for newer features. |
| Can WebAuthn replace passwords? | "userVerification": "required" | Yes, or it can act as a second factor. Most sites keep a recovery path. |
| How do I create WebAuthn registration options? | {
"rp": { "name": "Example Inc", "id": "example.com" },
"user": { "id": "Zr7Qm2Lw9XcVb1NtKd5HyA", "name": "alex@example.com", "displayName": "Alex Doe" },
"challenge": "q83vhHn9LhbwYoGg3aEDjpO6c3WZ1X8mR4tKzP0uYjw",
"pubKeyCredParams": [{ "type": "public-key", "alg": -7 }, { "type": "public-key", "alg": -257 }]
} | Send rp, user, challenge and pubKeyCredParams from your server. |
| Which pubKeyCredParams should I use? | "pubKeyCredParams": [{ "type": "public-key", "alg": -7 }, { "type": "public-key", "alg": -257 }, { "type": "public-key", "alg": -8 }] | ES256 (-7) and RS256 (-257) cover most authenticators. Add EdDSA (-8) if your library supports it. |
| What does alg -7 mean? | { "type": "public-key", "alg": -7 } | -7 is the COSE identifier for ES256, which is ECDSA with the P-256 curve and SHA-256. |
| What does alg -257 mean? | { "type": "public-key", "alg": -257 } | -257 is RS256, RSASSA-PKCS1-v1_5 with SHA-256. Some platform authenticators, such as Windows Hello, use it. |
| What does alg -8 mean? | { "type": "public-key", "alg": -8 } | -8 is EdDSA, usually Ed25519. Not every authenticator or library supports it. |
| What should user.id be? | const userId = require("crypto").randomBytes(32).toString("base64url"); | A random, opaque ID of up to 64 bytes. Never use an email address or a username. |
| user.name vs user.displayName | "user": { "id": "<base64url>", "name": "alex@example.com", "displayName": "Alex Doe" } | name is the account identifier, such as an email. displayName is the friendly name shown to the user. |
| How do I prevent duplicate passkeys? | "excludeCredentials": [{ "type": "public-key", "id": "<credentialId>" }] | List the user’s existing credential IDs in excludeCredentials during registration. |
| What does attestation "none" mean? | "attestation": "none" | The server does not ask for attestation data. It is the best choice for most sites. |
| When should I use direct attestation? | "attestation": "direct" | Only when you must verify authenticator models. Browsers may show users an extra prompt. |
| What is authenticatorSelection? | "authenticatorSelection": { "residentKey": "required", "userVerification": "preferred" } | It holds your preferences for attachment, resident key and user verification. |
| residentKey vs requireResidentKey | "residentKey": "required", "requireResidentKey": true | residentKey is the current option. requireResidentKey is the older boolean, so set it to true when residentKey is "required". |
| How do I create authentication options? | {
"challenge": "q83vhHn9LhbwYoGg3aEDjpO6c3WZ1X8mR4tKzP0uYjw",
"rpId": "example.com",
"userVerification": "preferred",
"timeout": 60000
} | Send a fresh challenge, your rpId and a user verification preference. |
| What is allowCredentials? | "allowCredentials": [{ "type": "public-key", "id": "<credentialId>" }] | The list of credentials the user may sign in with. Empty or omitted allows any passkey for your site. |
| How do I do username-less login? | "allowCredentials": [] | Use discoverable credentials and omit allowCredentials, so the browser shows the user’s passkeys. |
| What does userVerification do? | "userVerification": "required" | required demands biometrics or a PIN, preferred asks when possible, and discouraged skips it. |
| What is rpId in authentication? | "rpId": "example.com" | Your site’s domain. It must match the rp.id used at registration. |
| What timeout should I set? | "timeout": 60000 | Many sites use 60,000 ms or more so users have enough time. This is a rule of thumb. |
| What are transports? | "transports": ["internal", "hybrid"] | Hints such as usb, nfc, ble, internal and hybrid that help the browser find a matching authenticator. |
| What are hints in WebAuthn options? | "hints": ["client-device"] | Level 3 hints suggest which kind of authenticator UI to show. Browser support varies, so test it. |
| How do I show an autofill passkey prompt? | <input type="text" name="username" autocomplete="username webauthn"> | Add autocomplete="username webauthn" to the username field, then call get() with mediation "conditional". |
| Can I use WebAuthn as a second factor? | "userVerification": "discouraged" | Yes. Pass that user’s credential IDs in allowCredentials and set userVerification as your policy needs. |
| How do I generate a challenge in Node.js? | const challenge = require("crypto").randomBytes(32).toString("base64url"); | Use a secure random generator with 32 bytes and encode it as base64url. |
| How do I generate a challenge in Python? | import secrets, base64
challenge = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode() | Use the secrets module and encode the bytes as base64url without padding. |
| How do I generate a challenge in the browser for testing? | const bytes = crypto.getRandomValues(new Uint8Array(32)); | Use crypto.getRandomValues. In production the challenge must come from your server. |
| How long should a WebAuthn challenge be? | require("crypto").randomBytes(32) | The specification says at least 16 random bytes. 32 bytes is common. |
| What is base64url? | s.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") | URL-safe base64. It uses - and _ instead of + and /, and has no = padding. |
| Convert base64url to ArrayBuffer in JavaScript | const buf = Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0)).buffer; | Restore the standard characters, decode with atob, and copy the bytes. |
| Convert ArrayBuffer to base64url in JavaScript | const s = btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); | Build a string from the bytes, use btoa, then make it URL-safe. |
| Convert base64url to Buffer in Node.js | const buf = Buffer.from(s, "base64url"); | Node.js supports the base64url encoding directly. |
| Why is my challenge not valid base64url? | /^[A-Za-z0-9_-]+$/.test(challenge) | It probably contains +, / or = characters. Only A-Z, a-z, 0-9, - and _ are valid. |
| Can I reuse a WebAuthn challenge? | delete req.session.challenge; | No. Create a new one for every ceremony, store it on the server, and delete it after use. |
| navigator.credentials.create example | const cred = await navigator.credentials.create({ publicKey: options }); | Pass your registration options as publicKey. The browser prompts the user and returns a credential. |
| navigator.credentials.get example | const assertion = await navigator.credentials.get({ publicKey: options }); | Pass your authentication options as publicKey to sign in with a credential. |
| How do I convert JSON options for the browser? | const options = PublicKeyCredential.parseCreationOptionsFromJSON(json); | Use parseCreationOptionsFromJSON in browsers that support it. Otherwise convert the base64url fields to buffers yourself. |
| How do I parse authentication options from JSON? | const options = PublicKeyCredential.parseRequestOptionsFromJSON(json); | Use parseRequestOptionsFromJSON in browsers that support it. |
| How do I send the credential to the server? | await fetch("/webauthn/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(cred.toJSON()) }); | Call toJSON() in browsers that support it, then POST the result. |
| How do I detect WebAuthn support? | if (window.PublicKeyCredential) { showPasskeyButton(); } | Check that PublicKeyCredential exists before you show passkey buttons. |
| How do I check for a platform authenticator? | const ok = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); | Ask whether a user-verifying platform authenticator is available. |
| How do I check conditional UI support? | const ok = await PublicKeyCredential.isConditionalMediationAvailable(); | Use isConditionalMediationAvailable before you start conditional mediation. |
| How do I start conditional mediation? | const assertion = await navigator.credentials.get({ publicKey: options, mediation: "conditional" }); | Call get() with mediation set to "conditional" and an input that has autocomplete="username webauthn". |
| How do I cancel a WebAuthn request? | const ac = new AbortController();
navigator.credentials.get({ publicKey: options, signal: ac.signal });
ac.abort(); | Pass an AbortSignal and call abort(). This also lets you start a new request. |
| How do I read clientDataJSON? | const clientData = JSON.parse(new TextDecoder().decode(cred.response.clientDataJSON)); | Decode the bytes as text and parse the JSON. |
| How do I read extension results? | const ext = cred.getClientExtensionResults(); | Call getClientExtensionResults on the credential. |
| What must the server verify at registration? | clientData.type === "webauthn.create" | The challenge, the origin, the type, the rpIdHash, the user presence and verification flags, and the attestation if you request it. |
| What must the server verify at login? | clientData.type === "webauthn.get" | The challenge, the origin, the type, the rpIdHash, the flags, the signature against the stored public key, and the signCount. |
| How do I verify the origin? | clientData.origin === "https://example.com" | Compare clientData.origin exactly with your expected origin or a list of origins you allow. |
| What is the rpIdHash? | require("crypto").createHash("sha256").update("example.com").digest() | The SHA-256 hash of the rp.id inside authenticatorData. It must match your own rp.id. |
| What do the UP and UV flags mean? | const up = !!(flags & 0x01), uv = !!(flags & 0x04); | Bit 0 is user present and bit 2 is user verified. Check UV when you require verification. |
| What is signCount? | if (newCount !== 0 && newCount <= stored.counter) flagPossibleClone(); | A counter in authenticatorData. If it is non-zero and does not increase, the key may be cloned. Many passkeys always send 0. |
| What should I store for each credential? | { "credentialId": "...", "publicKey": "...", "counter": 0, "transports": ["internal"] } | The credential ID, public key, counter, transports, user ID and creation time. |
| SimpleWebAuthn install for Node.js | npm install @simplewebauthn/server @simplewebauthn/browser | SimpleWebAuthn has a server package and a browser package. |
| SimpleWebAuthn registration functions | import { generateRegistrationOptions, verifyRegistrationResponse } from "@simplewebauthn/server"; | Import the functions that generate and verify registration data. |
| SimpleWebAuthn authentication functions | import { generateAuthenticationOptions, verifyAuthenticationResponse } from "@simplewebauthn/server"; | Import the functions that generate and verify authentication data. |
| WebAuthn library for Python | pip install webauthn | The py_webauthn project installs as the webauthn package. |
| WebAuthn library for Go | go get github.com/go-webauthn/webauthn/webauthn | The go-webauthn project provides a Go library. |
| WebAuthn library for Java | <artifactId>webauthn-server-core</artifactId> | Yubico publishes a Java server library. |
| WebAuthn library for .NET | dotnet add package Fido2.AspNet | The Fido2 package supports ASP.NET Core. |
| WebAuthn library for Ruby | gem "webauthn" | The webauthn-ruby gem supports Rails. |
| How do I give a passkey a name? | { "id": "<credentialId>", "nickname": "Alex’s MacBook" } | Let users name each passkey and store the name next to the credential ID. |
| What are the BE and BS flags? | const be = !!(flags & 0x08), bs = !!(flags & 0x10); | Bit 3 is backup eligibility and bit 4 is backup state. Together they show whether a passkey can sync and is synced. |
| Do passkeys sync across devices? | "hints": ["client-device"] | Platform providers can sync them, and your site cannot control that. Offer more than one sign-in option. |
| How does cross-device sign-in work? | "transports": ["hybrid"] | The user scans a QR code with a phone and approves over Bluetooth. This uses the hybrid transport. |
| Should I keep a password fallback? | // Offer recovery: backup codes or a verified email link | Keep a recovery path, such as backup codes or a verified email link, so users are not locked out. |
| How do I let users remove a passkey? | DELETE FROM credentials WHERE id = $1 AND user_id = $2; | Delete the stored credential for that user and require a recent sign-in first. |
| When should I offer to create a passkey? | if (await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()) showPasskeyPrompt(); | After a successful sign-in, if a platform authenticator is available. |
| How do I migrate existing password users? | if (!req.session.userId) return res.sendStatus(401); | Keep passwords working and let signed-in users add a passkey from their security settings. |
| What is the AAGUID? | 00000000-0000-0000-0000-000000000000 | It identifies the authenticator model. With attestation "none" it may be all zeros. |
| NotAllowedError in WebAuthn | if (e.name === "NotAllowedError") { /* cancelled or timed out */ } | The user cancelled, the request timed out, or the page lacked permission. Show a retry option. |
| InvalidStateError in WebAuthn | if (e.name === "InvalidStateError") { /* already registered */ } | A credential for this account already exists on the authenticator. Tell the user it is already registered. |
| SecurityError in WebAuthn | if (e.name === "SecurityError") { /* check rp.id and HTTPS */ } | The rp.id does not match the page’s domain, or the page is not a secure context. |
| NotSupportedError in WebAuthn | if (e.name === "NotSupportedError") { /* add -7 and -257 */ } | No requested algorithm or authenticator option is supported. Check pubKeyCredParams. |
| ConstraintError in WebAuthn | if (e.name === "ConstraintError") { /* relax authenticatorSelection */ } | Your authenticatorSelection requirements cannot be met, for example required user verification on a key that lacks it. |
| AbortError in WebAuthn | if (e.name === "AbortError") { /* ignore */ } | The request was cancelled with an AbortSignal, often because a new request started. |
| TypeError when calling credentials.create | const options = PublicKeyCredential.parseCreationOptionsFromJSON(json); | Options often contain strings where the API expects buffers. Convert the base64url fields or use parseCreationOptionsFromJSON. |
| rp.id is not a registrable domain suffix | "rp": { "id": "example.com" } | The rp.id must be the page’s domain or a parent domain, without a scheme, port or path. |
| WebAuthn on localhost | "rp": { "id": "localhost", "name": "Dev" } | localhost is treated as secure, and the rp.id is localhost. |
| WebAuthn inside an iframe | <iframe src="https://auth.example.com" allow="publickey-credentials-get *; publickey-credentials-create *"></iframe> | A cross-origin iframe needs permission from the parent page. |
| Related origin requests for multiple domains | { "origins": ["https://example.co.uk"] } | A site can list other origins in /.well-known/webauthn. Browser support varies, so check before relying on it. |
| How long should a challenge live? | const expiresAt = Date.now() + 5 * 60 * 1000; | Expire it after a few minutes. Five minutes is a common rule of thumb. |
| Where do I store the challenge? | req.session.challenge = challenge; | On the server, tied to the session, never in a cookie the client can change. |
| Does the server store the private key? | { "publicKey": "<COSE public key>" } | No. The private key stays on the authenticator. The server stores only the public key. |
| How do I rate limit WebAuthn endpoints? | app.use("/webauthn", rateLimit({ windowMs: 60000, max: 20 })); | Limit options and verify requests per IP and per account. |
| Is the user handle private? | crypto.randomBytes(32) | Use a random opaque value so it reveals nothing about the account. |
| How do I avoid account enumeration? | "allowCredentials": [] | Return options that look the same whether or not the account exists. |
| Which origins should I allow? | const expectedOrigins = ["https://example.com"]; | List only the exact origins you control and compare them exactly. |
| How do I protect passkey registration? | if (!req.session.userId) return res.sendStatus(401); | Require a signed-in session, and recent re-authentication for sensitive changes. |
| What does the credProps extension do? | "extensions": { "credProps": true } | It reports whether the created credential is discoverable. Support varies by browser. |
| What is the prf extension? | "extensions": { "prf": { "eval": { "first": "<base64url salt>" } } } | It lets a credential derive secrets for apps. Support varies, so check before relying on it. |
| What is the largeBlob extension? | "extensions": { "largeBlob": { "support": "preferred" } } | It can store small data with a credential. Support is limited, so test your targets. |
| Can I limit attestation formats? | "attestationFormats": ["none"] | Level 3 adds attestationFormats. Support varies by browser. |
| How do I test WebAuthn without a security key? | DevTools > More tools > WebAuthn > Enable virtual authenticator environment | Use the virtual authenticator in Chrome DevTools. |
| How do I run local HTTPS for testing? | mkcert -install && mkcert localhost | Use mkcert to create a trusted local certificate. |
| How do I debug the options object? | console.log(JSON.stringify(options, null, 2)); | Log the options as formatted JSON before you call the API. |
| How do I check my options before I send them? | { "challenge": "<base64url>", "rpId": "example.com" } | Use the builder above to inspect your JSON for common mistakes. |
FAQ
| Question | Answer |
|---|---|
| What is WebAuthn options JSON? | It is the set of options a server sends to the browser to start passkey or WebAuthn registration (PublicKeyCredentialCreationOptions) or sign-in (PublicKeyCredentialRequestOptions). It holds the challenge, the relying party, the user, the allowed algorithms and authenticator preferences. In JSON form, binary values such as the challenge and the user ID are base64url strings. |
| What is the difference between registration and authentication options? | Registration (create) options include rp, user, pubKeyCredParams, excludeCredentials, authenticatorSelection and attestation. Authentication (get) options include challenge, rpId, allowCredentials and userVerification. Both need a fresh server-generated challenge. |
| How long should a WebAuthn challenge be? | The WebAuthn specification says a challenge should be at least 16 random bytes, and 32 bytes is common. Generate it on the server with a secure random generator, use it once, and verify it when the response comes back. |
| What should the rp.id be? | The rp.id is your site’s domain, or a registrable domain suffix of it, without https://, a port or a path. For example, example.com works for https://login.example.com. The browser rejects other values. |
| How do I set up passkeys with discoverable credentials? | Set authenticatorSelection.residentKey to required or preferred, and userVerification to required or preferred. For username-less sign-in, leave allowCredentials empty or omit it in the authentication options. |
| Is it safe to paste my WebAuthn options here? | The checks run in your browser and nothing is uploaded. The tool inspects options only and never needs private keys. Avoid pasting real user IDs or personal data, because the Share link button puts your form in the URL. A challenge made here is for testing, and production challenges must come from your server. |