zeroclaw-labs/zeroclaw · error

Expected type 'webauthn.get', got '{cd_type}'

Error message

Expected type 'webauthn.get', got '{cd_type}'

What it means

finish_authentication requires the client_data_json of an authentication response to carry type == "webauthn.get", the WebAuthn authentication ceremony type. A different value — commonly "webauthn.create" — means the payload belongs to the registration ceremony or was malformed, and authentication is rejected.

Source

Thrown at crates/zeroclaw-runtime/src/security/webauthn.rs:425

                ::zeroclaw_log::record!(
                    WARN,
                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Reject)
                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                        .with_attrs(::serde_json::json!({"credential_id": response.id})),
                    "webauthn verify refused: credential id not in store"
                );
                anyhow::Error::msg(format!("Credential not found: {}", response.id))
            })?;

        // 3. 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")?;

        let cd_type = client_data["type"].as_str().unwrap_or_default();
        anyhow::ensure!(
            cd_type == "webauthn.get",
            "Expected type 'webauthn.get', got '{cd_type}'"
        );

        let cd_challenge = client_data["challenge"].as_str().unwrap_or_default();
        anyhow::ensure!(
            cd_challenge == auth_state.challenge,
            "Challenge mismatch in authentication response"
        );

        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
        );

        // 4. Verify signature

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Send only navigator.credentials.get() results to the auth-finish endpoint
  2. Keep create/get response serializers separate; validate ceremony type client-side before posting
  3. In tests, generate client_data_json with type 'webauthn.get' and the challenge from auth start

Example fix

// before: registration response posted to login finish
const resp = await navigator.credentials.create({ publicKey: regOptions });
await fetch('/auth/finish', { method: 'POST', body: serialize(resp) });
// after
const resp = await navigator.credentials.get({ publicKey: authOptions });
await fetch('/auth/finish', { method: 'POST', body: serialize(resp) });
Defensive patterns

Strategy: try-catch

Try / catch

in the auth-finish HTTP handler, catch ceremony-validation errors (type/challenge/origin) and map to 400 with the message; do not retry the payload; client should call auth start again

Prevention

When it happens

Trigger: Posting a navigator.credentials.create (registration) response to the auth-finish endpoint; hand-rolled test clients writing the wrong type string; client libraries or proxies that rewrite client_data_json.

Common situations: Frontend handlers wiring the create response into the login flow; e2e fixtures reusing registration client data for login; copy-paste errors in custom FIDO clients.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/fc1e0df451fa6500. Report an issue: GitHub.