{"record":{"id":"8661d11ce5e7c572","repo":"zeroclaw-labs/zeroclaw","slug":"mfa-is-required-but-user-has-not-completed-mf","errorCode":null,"errorMessage":"MFA is required but user '{}' has not completed MFA verification","messagePattern":"MFA is required but user '(.+?)' has not completed MFA verification","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/security/nevis.rs","lineNumber":147,"sourceCode":"            http_client,\n        })\n    }\n\n    /// Validate a bearer token and resolve the caller's identity.\n    /// Returns `NevisIdentity` on success, or an error if the token is invalid,\n    /// expired, or MFA requirements are not met.\n    pub async fn validate_token(&self, token: &str) -> Result<NevisIdentity> {\n        if token.is_empty() {\n            bail!(\"empty bearer token\");\n        }\n\n        let identity = match self.validation_mode {\n            TokenValidationMode::Local => self.validate_token_local(token).await?,\n            TokenValidationMode::Remote => self.validate_token_remote(token).await?,\n        };\n\n        if self.require_mfa && !identity.mfa_verified {\n            bail!(\n                \"MFA is required but user '{}' has not completed MFA verification\",\n                crate::security::redact(&identity.user_id)\n            );\n        }\n\n        let now = std::time::SystemTime::now()\n            .duration_since(std::time::UNIX_EPOCH)\n            .unwrap_or_default()\n            .as_secs();\n\n        if identity.session_expiry > 0 && identity.session_expiry < now {\n            bail!(\"Nevis session expired\");\n        }\n\n        Ok(identity)\n    }\n\n    /// Validate token by calling the Nevis introspection endpoint.","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/security/nevis.rs#L129-L165","documentation":"The provider was built with require_mfa = true and the resolved identity has mfa_verified = false. In remote mode mfa_verified is true only when the introspection response carries acr = \"mfa\" or an amr entry of fido2/passkey/otp/webauthn (nevis.rs:223-228, 146-151). The token itself is valid; the authentication policy is what fails.","triggerScenarios":"validate_token with a token from a password-only login; the IdP issued the token without amr/acr claims; a custom acr value like \"mfa:2\" that does not equal \"mfa\" exactly.","commonSituations":"SSO flow skips the MFA step for remembered devices; the Nevis client mapper omits the amr claim; require_mfa was enabled after long-lived tokens were already issued.","solutions":["Have the caller re-authenticate and complete an MFA method (FIDO2, passkey, or OTP), then present the new token","Inspect the introspection response for the failing token and confirm acr/amr claims are present; fix the IdP client mappers if not","If MFA is genuinely not required for this deployment, build the provider with require_mfa = false"],"exampleFix":"// before: policy enabled\nlet provider = NevisAuthProvider::new(url, realm, client, secret, \"remote\", None, true, 3600)?;\n\n// after: policy matches what the IdP actually enforces\nlet provider = NevisAuthProvider::new(url, realm, client, secret, \"remote\", None, false, 3600)?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"Match err.to_string().contains(\"MFA is required\") and return 401/403 with an mfa_required signal so the client triggers an MFA re-auth flow; do not retry the same token.","preventionTips":["Confirm the IdP client emits amr and acr claims before enabling require_mfa","Return an mfa_required signal instead of a generic 500 so clients can re-authenticate","Keep a CI test that asserts a password-only token is rejected"],"tags":["auth","nevis","mfa","policy","rust"],"backgroundTag":"mfa-verification-required","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}