navigator.credentials.create() to register a new credential.rp, user, challenge and pubKeyCredParams.
All other members are optional and fall back to browser defaults.pubKeyCredParams should list EdDSA (-8), ES256 (-7) and RS256 (-257). Omitting
RS256 breaks registration on Windows Hello TPM devices running Windows 10 or older
Windows 11.residentKey to required and userVerification to required in
authenticatorSelection creates discoverable credentials that work for usernameless
login and Conditional UI.attestation: none no longer zeroes the AAGUID,
so you still see which passkey provider created the credential without cryptographic
verification.PublicKeyCredentialCreationOptions is the WebAuthn dictionary that tells the browser
how to create a new passkey or security key credential. The
Relying Party server generates it, the frontend passes it to
navigator.credentials.create({ publicKey }) and the
authenticator answers with a new public key and an
attestation.
Only four members are required: rp, user, challenge and pubKeyCredParams.
Everything else is optional, but the optional members decide which
authenticators can register, whether the passkey works for
usernameless login and how much you learn about the device.
A typical registration request for a synced passkey, serialized as JSON (binary values are base64url encoded):
{ "rp": { "id": "passkeys.eu", "name": "Corbado Passkeys Demo" }, "user": { "id": "dXNyLZ...DU10Tc", "name": "john@doe.com", "displayName": "John Doe" }, "challenge": "888fix4Bus...pHHr3Y", "pubKeyCredParams": [ { "type": "public-key", "alg": -8 }, { "type": "public-key", "alg": -7 }, { "type": "public-key", "alg": -257 } ], "timeout": 300000, "excludeCredentials": [ { "type": "public-key", "id": "Xk2b...9fQ", "transports": ["internal", "hybrid"] } ], "authenticatorSelection": { "residentKey": "required", "requireResidentKey": true, "userVerification": "required" }, "hints": [], "attestation": "none", "extensions": { "credProps": true } }
Continue reading for a breakdown of every member and the values we recommend for different use cases.
As shown in the registration flow, sending PublicKeyCredentialCreationOptions to the frontend is the first step of every WebAuthn registration. The backend stores the challenge, the browser forwards the options to the authenticator and the backend later verifies the response against exactly these options. Only generate creation options for a fully authenticated session (e.g. after MFA), otherwise an attacker with a stolen password can register their own passkey.
Subscribe to our Passkeys Substack for the latest news.
| Member | Required | Default | What it controls |
|---|---|---|---|
rp | Yes | rp.id = current domain | Which Relying Party the credential is bound to |
user | Yes | none | Which account the credential belongs to |
challenge | Yes | none | Random value that prevents replay |
pubKeyCredParams | Yes | none | Accepted signature algorithms, in order of preference |
timeout | No | browser specific | How long the browser waits for the user (300000 ms recommended) |
excludeCredentials | No | [] | Credentials that must not be registered again on the same device |
authenticatorSelection | No | see below | Attachment, discoverability and user verification |
hints | No | [] | UI hints: security-key, client-device or hybrid |
attestation | No | "none" | Whether and how the authenticator proves its make and model |
attestationFormats | No | [] | Preferred attestation statement formats (WebAuthn Level 3) |
extensions | No | {} | Additional features such as credProps, prf or credProtect |
Here is an explanation of every member as defined in the WebAuthn Level 3 specification.
"rp": { "id": "passkeys.eu", "name": "Corbado Passkeys Demo" }
rp identifies the Relying Party, the service the user registers with.
passkeys.eu for app.passkeys.eu. If you
leave it out, the browser uses the current domain. The credential can later only be used
on this domain and its subdomains, so choose it carefully. It cannot be changed without
re-registering all passkeys. To share passkeys across different domains, see
Related Origin Requests.rp.icon and user.icon members
were removed after Level 1."user": { "id": "dXNyLZ...DU10Tc", "name": "john@doe.com", "displayName": "John Doe" }
user describes the account the credential is created for.
user.id on
the same authenticator replaces the existing passkey."challenge": "888fix4Bus...pHHr3Y"
The cryptographic challenge is a random value of at
least 16 bytes generated by the server for every ceremony. The authenticator signs it and
the server verifies that the response contains the exact challenge it issued. Each
challenge must be used only once and should expire with the timeout.
"pubKeyCredParams": [ { "type": "public-key", "alg": -8 }, { "type": "public-key", "alg": -7 }, { "type": "public-key", "alg": -257 } ]
pubKeyCredParams lists the signature algorithms the server accepts for the new key pair, identified by their COSE algorithm identifier. The order expresses preference: the authenticator picks the first algorithm in the list that it supports. If it supports none of them, registration fails. If you send an empty list, browsers fall back to ES256 (-7) and RS256 (-257). Read more in our pubKeyCredParams deep dive.
| COSE ID | Name | Algorithm | Recommendation |
|---|---|---|---|
| -8 | EdDSA | EdDSA (Ed25519) | Include, used by some security keys |
| -19 | Ed25519 | Ed25519 (fully specified, RFC 9864) | Only in addition to -8, support is still rare |
| -7 | ES256 | ECDSA with P-256 and SHA-256 | Include, used by Apple, Google and most keys |
| -257 | RS256 | RSASSA-PKCS1-v1_5 with SHA-256 | Include, needed for Windows Hello on TPM devices |
| -35 | ES384 | ECDSA with P-384 and SHA-384 | Optional, test on old clients first |
| -36 | ES512 | ECDSA with P-521 and SHA-512 | Optional, test on old clients first |
| -37 | PS256 | RSASSA-PSS with SHA-256 | Optional, rarely used by authenticators |
| -65535 | RS1 | RSASSA-PKCS1-v1_5 with SHA-1 | Do not use |
The WebAuthn specification recommends that Relying Parties include at least EdDSA (-8), ES256 (-7) and RS256 (-257) to support the widest range of authenticators. In practice almost every passkey is created with ES256. RS256 is the one most often forgotten and causes registration errors on Windows 10 and older Windows 11 devices whose Windows Hello keys live in the TPM.
Adding stronger algorithms such as ES384 or ES512 does not make your existing passkeys stronger, because authenticators still pick the first algorithm they support. Your server library also has to be able to verify every algorithm you list. If you add more than the standard three, run a regression test on old clients (especially Windows 10) before going live.
The IANA COSE registry now marks -7, -8, -35 and -36 as deprecated in favor of the fully specified identifiers from RFC 9864 (e.g. -19 for Ed25519). WebAuthn Level 3 explicitly recommends to keep using -7, -8, -35 and -36 instead of or in addition to the new identifiers, because most authenticators do not support them yet.
"timeout": 300000
timeout is a hint in milliseconds for how long the browser should wait for the user to complete the ceremony. Browsers may ignore or cap it. WebAuthn Level 3 recommends a range of 300000 to 600000 ms with 300000 ms (five minutes) as default, because the user may need to unlock a phone via QR code or find a security key. Let the challenge on the server expire after the same time.
"excludeCredentials": [ { "type": "public-key", "id": "Xk2b...9fQ", "transports": ["internal", "hybrid"] } ]
excludeCredentials lists credentials the user already has.
If the authenticator already holds one of them, it refuses to create a second credential
and the browser returns an InvalidStateError. This prevents duplicate passkeys for the
same account in the same passkey provider. Each entry is a
credential descriptor with the credential id, the type and optionally the stored
transports. Always fill it with all of the
user's existing credential IDs.
"authenticatorSelection": { "authenticatorAttachment": "platform", "residentKey": "required", "requireResidentKey": true, "userVerification": "required" }
authenticatorSelection defines the requirements an authenticator must meet.
"platform" restricts registration to the authenticator of
the current device (Face ID, Touch ID, Windows Hello,
Android screen lock). "cross-platform"
restricts it to roaming authenticators such as security keys or a phone via
QR code. If you leave it out, the user can choose
any authenticator. This is a product decision: "platform" reduces enrollment to a
single biometric prompt, while leaving it out adds a choice to the dialog but allows
passkeys on a phone or security key."required", "preferred" or "discouraged". If it is missing, the
value of requireResidentKey decides, which means "discouraged" by default. Set it to
"required" to create a
discoverable credential.
Only discoverable credentials work for usernameless login and
Conditional UI. Read more about
resident keys.true if
residentKey is "required", otherwise false, for older browsers."required", "preferred" (default) or "discouraged".
"required" forces a biometric or PIN check. It also excludes security keys without a
PIN or biometric sensor. See user verification for
details.Security keys used as a second factor after the password, without a PIN, need
residentKey and userVerification set to "discouraged". If you support both passkeys
and such security keys, treat them as two separate configurations and decide per user
which one applies before you generate the options.
"hints": ["client-device"]
hints were added in WebAuthn Level 3. They tell the browser which UI to show first, in order of preference:
If hints contradict authenticatorAttachment, browsers that support hints follow the
hints. Browsers that do not support hints ignore them, so set a matching
authenticatorAttachment as well: "platform" together with "client-device" and
"cross-platform" together with "security-key" or "hybrid".
"attestation": "none", "attestationFormats": []
attestation is a single string (not a list) that defines whether the authenticator should prove its make and model with an attestation statement:
attestationFormats (Level 3) is an optional list of preferred attestation statement
formats such as "packed", "tpm", "android-key" or "apple", in order of preference.
Most implementations leave it empty.
Many server libraries let you configure a list of accepted attestation types. The options
sent to the browser still contain only one attestation value.
Since WebAuthn Level 3 the browser no longer zeroes the AAGUID when
you request "none". It only strips the attestation statement. So you usually still see
which passkey provider created the credential (e.g.
Google Password Manager), you just cannot
verify it cryptographically. Showing this provider name in the user's account settings
helps users find their passkey later. Synced passkeys from Apple and Google return no
attestation statement even if you request "direct". If you want to
restrict authenticators by AAGUID, decide
up front how you treat passkeys without attestation. An allowlist blocks every
authenticator you did not list, a denylist is easier to operate but cannot be enforced
cryptographically for synced passkeys.
"extensions": { "credProps": true }
extensions request additional processing. The most relevant ones for registration are:
rk: true or false in the client extension results, so you
know whether the created credential is discoverable. Useful when residentKey is
"preferred".Note that the backup eligibility (BE) and backup state (BS) values are not extensions. They are flags in the authenticator data of every response and tell you whether a passkey is syncable and synced. Store them together with the AAGUID for every registration and analyze them, instead of rejecting synced passkeys up front.
Servers send the options as JSON with base64url encoded binary values. Instead of decoding
them manually, you can use PublicKeyCredential.parseCreationOptionsFromJSON(), which is
available since Chrome and Edge 129, Firefox 119 and
Safari 18.4, and send the result back
with credential.toJSON().
Experiment with passkey flows in the Passkeys Debugger.
They define every parameter of a WebAuthn registration: which Relying Party and user the credential belongs to, which algorithms are accepted, which authenticators may be used and whether attestation is requested. The server generates them with a fresh challenge and later verifies the response against them.
Include EdDSA (-8), ES256 (-7) and RS256 (-257). This is the set the WebAuthn specification recommends for the widest compatibility. Without RS256, older Windows Hello devices fail to register.
Set it to "platform" if adoption is your main goal and users should create the passkey
on the device they are using right now. Leave it out if users should also be able to
register a security key or a passkey on their phone via QR code.
Yes. Some deployments use two configurations: one for passkeys (discoverable, user verification required) and one for security keys as a second factor (not discoverable, user verification discouraged).
Igor Gjorgjioski
Senior Product Lead, VicRoads
We hit 80% mobile passkey activation across 5M+ users without replacing our IDP.
See how VicRoads scaled passkeys to 5M+ users, alongside their existing IDP.
Read the case studyBoth are sent by the backend and contain a challenge. PublicKeyCredentialCreationOptions
are used to create a new credential with navigator.credentials.create(), while
PublicKeyCredentialRequestOptions are used
to log in with an existing credential via navigator.credentials.get().
Use "none" for consumer passkeys, because it avoids extra prompts and certificate
handling. Use "direct" if you need to verify the AAGUID cryptographically, e.g. to
enforce an authenticator allowlist. "indirect" lets the browser replace or anonymize the
attestation, which is rarely useful in practice. "enterprise" returns device-identifying
attestation and only works on managed devices for RP IDs allowed by browser or enterprise
policy. Apple and Google synced passkeys return no attestation statement, whatever value
you request.
excludeCredentials lists credential IDs the user already registered. If the
authenticator holds any of those credentials, it refuses to create a new one and the
browser returns an InvalidStateError. Always populate it with all existing credential
IDs for a user to prevent duplicate passkeys within the same
passkey provider.
requireResidentKey is a legacy boolean from WebAuthn Level 1 kept for backward
compatibility with older browsers. residentKey is the modern string field introduced in
Level 2, accepting "required", "preferred" or "discouraged". Set both consistently:
requireResidentKey: true when residentKey is "required" and false otherwise.
hints tell the browser which UI to show first: "client-device", "security-key" or
"hybrid". When hints contradict authenticatorAttachment, Level 3 browsers follow the
hints. Browsers without Level 3 support ignore hints entirely, so always set a matching
authenticatorAttachment as a fallback: "platform" paired with "client-device" and
"cross-platform" paired with "security-key" or "hybrid".
Corbado is the Passkey Intelligence Platform for large-scale CIAM teams running consumer authentication. We help you see what IDP logs and generic analytics tools can't: where passkeys, passwords, OTP, social login and fallback journeys succeed, stall or fail, which devices and browsers create friction, and when an OS update silently breaks login. Two products: Corbado Observe layers process mining and observability across authentication journeys. Corbado Connect adds managed passkeys with analytics built in alongside your IDP. VicRoads runs passkeys for 5M+ users with Corbado (more than 80% passkey activation rate). Talk to a Passkey Expert →
Table of Contents
Related Articles