zeroclaw-labs/zeroclaw · error

empty bearer token

Error message

empty bearer token

What it means

NevisAuthProvider::validate_token rejects the call before any network or signature work when the token string is empty (nevis.rs:137-139). It is a fail-fast input guard: it always fires immediately and never contacts the Nevis instance. Hitting it means your code extracted no bearer token from the request but still called the validator.

Source

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

        Ok(Self {
            instance_url,
            realm,
            client_id,
            client_secret,
            validation_mode,
            jwks_url,
            require_mfa,
            session_timeout: Duration::from_secs(session_timeout_secs),
            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();

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Reject requests whose Authorization header is missing or has no token before calling validate_token (return 401 early)
  2. Check token.trim().is_empty() in your middleware and log which header name was inspected
  3. Verify your header parsing strips the 'Bearer ' scheme and uses the same header name your clients send

Example fix

// before
let token = auth_header.unwrap_or_default();
let identity = provider.validate_token(&token).await?;

// after
let token = auth_header
    .and_then(|h| h.strip_prefix("Bearer "))
    .map(str::trim)
    .filter(|t| !t.is_empty())
    .ok_or_else(|| anyhow::anyhow!("missing bearer token"))?;
let identity = provider.validate_token(token).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn extract_bearer(header: Option<&str>) -> Option<&str> {
    header?
        .strip_prefix("Bearer ")
        .map(str::trim)
        .filter(|t| !t.is_empty())
}

Try / catch

If validate_token still errors, match on err.to_string().contains("empty bearer token") and return 401 immediately — never retry and never treat it as an IdP outage.

Prevention

When it happens

Trigger: Calling validate_token with an empty string — typically because the Authorization header was missing, used a different scheme or header name, or the 'Bearer ' prefix stripping left an empty value; tests pass an unset env var or String::new().

Common situations: Gateway middleware forwards an empty string when the Authorization header is absent; client sends the token in x-api-key instead; config template ships an empty token value; integration tests forget to inject the token.

Related errors


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