{"record":{"id":"73c55c589ca9d876","repo":"zeroclaw-labs/zeroclaw","slug":"challenge-mismatch-in-authentication-response","errorCode":null,"errorMessage":"Challenge mismatch in authentication response","messagePattern":"Challenge mismatch in authentication response","errorType":"exception","errorClass":null,"httpStatus":401,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/webauthn.rs","lineNumber":431,"sourceCode":"                );\n                anyhow::Error::msg(format!(\"Credential not found: {}\", response.id))\n            })?;\n\n        // 3. Validate client data JSON\n        let client_data_bytes = URL_SAFE_NO_PAD\n            .decode(&response.client_data_json)\n            .context(\"Invalid base64url in client_data_json\")?;\n        let client_data: serde_json::Value =\n            serde_json::from_slice(&client_data_bytes).context(\"Invalid client data JSON\")?;\n\n        let cd_type = client_data[\"type\"].as_str().unwrap_or_default();\n        anyhow::ensure!(\n            cd_type == \"webauthn.get\",\n            \"Expected type 'webauthn.get', got '{cd_type}'\"\n        );\n\n        let cd_challenge = client_data[\"challenge\"].as_str().unwrap_or_default();\n        anyhow::ensure!(\n            cd_challenge == auth_state.challenge,\n            \"Challenge mismatch in authentication response\"\n        );\n\n        let cd_origin = client_data[\"origin\"].as_str().unwrap_or_default();\n        anyhow::ensure!(\n            cd_origin == self.config.rp_origin,\n            \"Origin mismatch: expected '{}', got '{cd_origin}'\",\n            self.config.rp_origin\n        );\n\n        // 4. Verify signature\n        let auth_data_bytes = URL_SAFE_NO_PAD\n            .decode(&response.authenticator_data)\n            .context(\"Invalid base64url in authenticator_data\")?;\n        let new_count =\n            validate_assertion_authenticator_data(&auth_data_bytes, &self.config.rp_id)?;\n","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/webauthn.rs#L413-L449","documentation":"finish_authentication compares the challenge in client_data_json against the challenge stored in the AuthenticationState issued by auth start. A mismatch means the assertion was not generated for this ceremony — stale state, a newer start overwriting the challenge, or inconsistent encoding.","triggerScenarios":"Calling auth start twice (two tabs, re-render) and finishing against the first state; the auth_state persisted client-side going stale; challenge compared with different base64url padding; replaying a captured assertion.","commonSituations":"SPAs refetching auth options on component mount; multiple login attempts racing; server keeping one challenge per session that gets overwritten; tests reusing a fixture assertion across runs.","solutions":["Restart authentication and finish with the assertion built from that same start's options","Bind the auth_state to the session that requested it and discard it once used or expired","Use one consistent base64url encoding for challenges on both sides"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// client: always get fresh options, never cache auth options across re-renders\nconst opts = await fetch('/auth/start', { cache: 'no-store' }).then(r => r.json());\nconst assertion = await navigator.credentials.get({ publicKey: opts });","typeGuard":null,"tryCatchPattern":"catch the mismatch, invalidate the stored auth_state, respond 400 with 'restart-authentication'; the client calls start again","preventionTips":["Bind each challenge to one session and consume it on first finish (single-use)","Expire pending auth states with a TTL","In tests, mint a fresh challenge per assertion"],"tags":["webauthn","challenge","authentication","session-state","rust"],"backgroundTag":"webauthn-challenge-mismatch","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}