zeroclaw-labs/zeroclaw · error
Authenticator data relying party ID hash mismatch
Error message
Authenticator data relying party ID hash mismatch
What it means
The first 32 bytes of authenticator data must equal SHA-256(rp_id). validate_assertion_authenticator_data recomputes the hash of the configured RP ID and compares; a mismatch means the credential was produced for a different relying party (or the blob is corrupt), so the assertion is rejected to prevent cross-site credential use.
Source
Thrown at crates/zeroclaw-runtime/src/security/webauthn.rs:595
std::fs::set_permissions(
&self.credentials_path,
std::fs::Permissions::from_mode(0o600),
)
.context("Failed to set credentials file permissions")?;
}
Ok(())
}
}
fn validate_assertion_authenticator_data(auth_data: &[u8], rp_id: &str) -> Result<u32> {
anyhow::ensure!(
auth_data.len() >= AUTHENTICATOR_DATA_FIXED_LEN,
"Authenticator data is shorter than the required fixed fields"
);
let expected_rp_id_hash = ring::digest::digest(&ring::digest::SHA256, rp_id.as_bytes());
anyhow::ensure!(
&auth_data[..32] == expected_rp_id_hash.as_ref(),
"Authenticator data relying party ID hash mismatch"
);
anyhow::ensure!(
auth_data[32] & AUTHENTICATOR_FLAG_UP != 0,
"Authenticator data does not assert user presence"
);
Ok(u32::from_be_bytes([
auth_data[33],
auth_data[34],
auth_data[35],
auth_data[36],
]))
}
// ── Attestation parsing ─────────────────────────────────────────
View on GitHub (pinned to 88bb9c8533)
Solutions
- Compare the rp_id passed to finish_authentication with the rp_id stored on the credential record from registration; they must be byte-identical.
- Use the eTLD+1 of the origin the browser actually runs on ("example.com" covers app.example.com; "localhost" only works on localhost).
- If the RP ID changed intentionally, old credentials cannot be salvaged: delete them and re-run the registration ceremony.
- Check for payload corruption if the RP ID is definitely correct (hash mismatch can also indicate mangled authenticatorData).
Example fix
// before: asserting with a hardcoded host that differs from registration webauthn::finish_authentication(&cred, &assertion, "api.example.com").await?; // after: always derive rp_id from the stored credential / single config source let rp_id = cred.rp_id.clone(); // recorded at begin_registration time webauthn::finish_authentication(&cred, &assertion, &rp_id).await?;
Defensive patterns
Strategy: validation
Validate before calling
fn rp_id_matches_registration(cred: &Credential, rp_id: &str) -> bool {
cred.rp_id == rp_id
}
// derive the assertion rp_id from the stored credential, never from the request:
let rp_id = cred.rp_id.as_str();
assert!(rp_id_matches_registration(&cred, rp_id)); Try / catch
match webauthn::finish_authentication(&cred, &assertion, &cred.rp_id).await {
Err(e) if e.to_string().contains("relying party ID hash mismatch") => {
// credential belongs to another RP: prompt re-registration, never auto-retry
prompt_re_registration(&user).await;
Err(e)
}
other => other,
} Prevention
- Store the rp_id on the credential at registration and reuse that exact value for assertions.
- Keep one config constant for the public origin/RP ID shared by frontend and backend.
- When the domain changes, plan a re-registration campaign; WebAuthn credentials are not portable across RP IDs.
When it happens
Trigger: finish_authentication invoked with an rp_id different from the one used at registration (e.g. registered on "example.com", asserting against "api.example.com" or "localhost"), a frontend served from a different origin than the configured RP ID, or a credential key copied from another deployment.
Common situations: Domain changes during deployment (apex vs www), running the web UI on localhost while the server expects the production host, staging and production sharing a credential database, RP ID typos in config, proxy frontends that change the effective origin.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Authenticator data is shorter than the required fixed fields
- Authenticator data does not assert user presence
- Invalid OTP code; estop resume denied
- Expected type 'webauthn.create', got '{cd_type}'
- No registered credentials for user '{user_id}'
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/0c2ab728327a9800.
Report an issue: GitHub.