zeroclaw-labs/zeroclaw · error

MFA is required but user '{}' has not completed MFA verifica

Error message

MFA is required but user '{}' has not completed MFA verification

What it means

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.

Source

Thrown at crates/zeroclaw-runtime/src/security/nevis.rs:147

            http_client,
        })
    }

    /// Validate a bearer token and resolve the caller's identity.
    /// Returns `NevisIdentity` on success, or an error if the token is invalid,
    /// expired, or MFA requirements are not met.
    pub async fn validate_token(&self, token: &str) -> Result<NevisIdentity> {
        if token.is_empty() {
            bail!("empty bearer token");
        }

        let identity = match self.validation_mode {
            TokenValidationMode::Local => self.validate_token_local(token).await?,
            TokenValidationMode::Remote => self.validate_token_remote(token).await?,
        };

        if self.require_mfa && !identity.mfa_verified {
            bail!(
                "MFA is required but user '{}' has not completed MFA verification",
                crate::security::redact(&identity.user_id)
            );
        }

        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        if identity.session_expiry > 0 && identity.session_expiry < now {
            bail!("Nevis session expired");
        }

        Ok(identity)
    }

    /// Validate token by calling the Nevis introspection endpoint.

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Have the caller re-authenticate and complete an MFA method (FIDO2, passkey, or OTP), then present the new token
  2. Inspect the introspection response for the failing token and confirm acr/amr claims are present; fix the IdP client mappers if not
  3. If MFA is genuinely not required for this deployment, build the provider with require_mfa = false

Example fix

// before: policy enabled
let provider = NevisAuthProvider::new(url, realm, client, secret, "remote", None, true, 3600)?;

// after: policy matches what the IdP actually enforces
let provider = NevisAuthProvider::new(url, realm, client, secret, "remote", None, false, 3600)?;
Defensive patterns

Strategy: try-catch

Try / catch

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.

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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