zeroclaw-labs/zeroclaw · error
Expected type 'webauthn.create', got '{cd_type}'
Error message
Expected type 'webauthn.create', got '{cd_type}' What it means
In finish_registration, the client_data_json of a registration response must carry type == "webauthn.create" per the WebAuthn registration ceremony. Any other value — most commonly "webauthn.get" — means the payload belongs to a different ceremony or was constructed incorrectly, and registration is rejected.
Source
Thrown at crates/zeroclaw-runtime/src/security/webauthn.rs:276
/// Complete a WebAuthn registration ceremony.
/// Validates the client response against the registration state,
/// extracts the public key, and stores the credential.
pub fn finish_registration(
&self,
reg_state: &RegistrationState,
response: &RegisterCredentialResponse,
) -> Result<WebAuthnCredential> {
// 1. Validate client data JSON
let client_data_bytes = URL_SAFE_NO_PAD
.decode(&response.client_data_json)
.context("Invalid base64url in client_data_json")?;
let client_data: serde_json::Value =
serde_json::from_slice(&client_data_bytes).context("Invalid client data JSON")?;
// Verify type
let cd_type = client_data["type"].as_str().unwrap_or_default();
anyhow::ensure!(
cd_type == "webauthn.create",
"Expected type 'webauthn.create', got '{cd_type}'"
);
// Verify challenge matches
let cd_challenge = client_data["challenge"].as_str().unwrap_or_default();
anyhow::ensure!(
cd_challenge == reg_state.challenge,
"Challenge mismatch in registration response"
);
// Verify origin
let cd_origin = client_data["origin"].as_str().unwrap_or_default();
anyhow::ensure!(
cd_origin == self.config.rp_origin,
"Origin mismatch: expected '{}', got '{cd_origin}'",
self.config.rp_origin
);View on GitHub (pinned to 88bb9c8533)
Solutions
- Make the client call navigator.credentials.create() and send that exact response to the register-finish endpoint
- Keep registration and authentication response handling in separate code paths; never reuse payloads between them
- In tests, build client_data_json with type 'webauthn.create' and the challenge returned by register start
Example fix
// before: posting an authentication response to register finish
const resp = await navigator.credentials.get({ publicKey: authOptions });
await fetch('/register/finish', { method: 'POST', body: serialize(resp) });
// after
const resp = await navigator.credentials.create({ publicKey: regOptions });
await fetch('/register/finish', { method: 'POST', body: serialize(resp) }); Defensive patterns
Strategy: try-catch
Validate before calling
// optional client-side pre-check to give a clearer error than the server
const cd = JSON.parse(Array.from(atob(resp.response.clientDataJSON.replace(/-/g,'+').replace(/_/g,'/')), c => String.fromCharCode(c.charCodeAt(0))).split('').join(''));
if (cd.type !== 'webauthn.create') throw new Error('wrong ceremony: ensure navigator.credentials.create() response is sent to register finish'); Try / catch
in the HTTP handler wrapping finish_registration, catch the anyhow error and map ceremony-validation failures (type/challenge/origin messages) to HTTP 400 with the error text; do not retry the same payload
Prevention
- Keep register and login fetch handlers in distinct functions with distinct response types
- Add an e2e test that drives the real browser ceremony so payload mixups surface immediately
- Log client_data_json type on failure for fast diagnosis
When it happens
Trigger: Sending an authentication assertion (navigator.credentials.get result) to the registration finish endpoint; hand-built or test client_data_json using the wrong type string; a proxy or client library rewriting the payload.
Common situations: Frontend code mixing up the create/get response handlers and posting to the wrong endpoint; e2e tests synthesizing client data with copy-pasted fields; replayed or cached responses from a previous ceremony.
Related errors
- Expected type 'webauthn.get', got '{cd_type}'
- Unable to extract public key from attestation object ({} byt
- Challenge mismatch in registration response
- Origin mismatch: expected '{}', got '{cd_origin}'
- Credential ID too long ({} bytes, max {MAX_CREDENTIAL_ID_LEN
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c0c4c3ef05fbb97f.
Report an issue: GitHub.