HIGH broken access controlaxumbasic auth

Broken Access Control in Axum with Basic Auth

Broken Access Control in Axum with Basic Auth — how this specific combination creates or exposes the vulnerability

Broken Access Control occurs when authorization checks are missing or incorrectly enforced, allowing authenticated users to access or modify resources that should be restricted. In Axum, combining Basic Auth with weak or missing authorization logic can expose this vulnerability because Basic Auth only provides authentication (who you are), not authorization (what you are allowed to do).

When Basic Auth is used in Axum, credentials are typically extracted from the Authorization header and validated (e.g., username/password or API key). If the application then performs endpoint-level routing or handler execution without verifying whether the authenticated identity has permission for that specific resource or action, the unauthenticated attack surface includes these authorization gaps. For example, an authenticated user might iterate through numeric IDs in URLs (e.g., /users/1, /users/2) and access other users’ data because the handler does not compare the authenticated user’s identity against the resource owner.

This pattern maps to the OWASP API Top 10 Broken Object Level Authorization (BOLA) / Insecure Direct Object References (IDOR) category. Basic Auth does not embed roles or scopes in a way that Axum automatically enforces; developers must explicitly implement checks. Without such checks, an attacker who obtains or guesses a valid Basic Auth credential can probe IDs and potentially read, modify, or delete other users’ data, leading to data exposure or privilege escalation.

In the context of a middleBrick scan, endpoints using Basic Auth may be flagged under the Authentication and BOLA/IDOR checks if credentials are accepted but authorization is not validated per request. The scanner tests whether authenticated access to other users’ or higher-privilege resources is possible, and if so, it reports a finding with severity and remediation guidance.

Basic Auth-Specific Remediation in Axum — concrete code fixes

To remediate Broken Access Control when using Basic Auth in Axum, enforce identity-based checks in each handler and normalize authorization data into a request extension or extractor. Below are concrete, working examples.

1. Basic Auth extraction and validation middleware

Create a middleware that parses the Authorization header, validates credentials, and attaches an identity to the request extensions. This keeps authentication separate from authorization and makes identity available downstream.

use axum::{
    async_trait,
    extract::{FromRequest, Request},
    http::StatusCode,
};
use std::convert::Infallible;

#[derive(Debug, Clone)]
pub struct User {
    pub id: String,
    pub role: String,
}

#[derive(Debug)]
pub struct BasicAuth {
    pub user: User,
}

#[async_trait]
impl FromRequest<S> for BasicAuth
where
    S: Send + Sync,
{
    type Rejection = (StatusCode, String);

    async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
        let header = req.headers().get("authorization")
            .ok_or((StatusCode::UNAUTHORIZED, "Missing authorization header".to_string()))?;
        let header_str = header.to_str().map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid header".to_string()))?;
        if !header_str.starts_with("Basic ") {
            return Err((StatusCode::UNAUTHORIZED, "Invalid authorization type".to_string()));
        }
        let encoded = &header_str[6..];
        let decoded = decode_base64(encoded).map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid encoding".to_string()))?;
        let parts: Vec<&str> = decoded.splitn(2, ':').collect();
        if parts.len() != 2 {
            return Err((StatusCode::UNAUTHORIZED, "Invalid credentials".to_string()));
        }
        let (username, password) = (parts[0], parts[1]);
        // Replace with secure credential validation (e.g., constant-time check against a store)
        let user = validate_user(username, password).await.map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid credentials".to_string()))?;
        Ok(BasicAuth { user })
    }
}

async fn validate_user(username: &str, password: &str) -> Result<User, &'static str> {
    // Example hardcoded mapping; use a secure store in production
    match (username, password) {
        ("alice", "s3cr3t") => Ok(User { id: "u-alice".to_string(), role: "user".to_string() }),
        ("admin", "adm1n") => Ok(User { id: "u-admin".to_string(), role: "admin".to_string() }),
        _ => Err("Invalid credentials"),
    }
}

fn decode_base64(input: &str) -> Result<String, base64::DecodeError> {
    let padded = format!( "{:<1$}", input, input.len() + (4 - input.len() % 4) % 4 );
    let decoded = base64::decode_config(padded, base64::STANDARD)?;
    String::from_utf8(decoded).map_err(|_| base64::DecodeError::InvalidByteError(0, 0))
}

2. Handler-level authorization check

In each handler that accesses a user-specific resource, compare the authenticated user’s identity with the target resource owner. Do not rely on route parameters alone.

use axum::{routing::get, Router};

async fn get_user_profile(
    BasicAuth(auth): BasicAuth,
    Path(user_id): Path<String>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    // Ensure the authenticated user is allowed to view this profile
    if auth.user.id != user_id {
        return Err((StatusCode::FORBIDDEN, "Access denied to this resource".to_string()));
    }
    // Proceed to fetch and return the profile
    Ok(format!("Profile for {}", auth.user.id))
}

async fn admin_only(
    BasicAuth(auth): BasicAuth,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    if auth.user.role != "admin" {
        return Err((StatusCode::FORBIDDEN, "Admin role required".to_string()));
    }
    Ok("Admin dashboard")
}

fn build_router() -> Router {
    Router::new()
        .route("/users/:user_id", get(get_user_profile))
        .route("/admin", get(admin_only))
        .layer(middleware::from_fn_with_extractor(|req, extensions| async move {
            // Use the extractor in middleware if needed; here we rely on handler extraction
            Ok(req)
        }))
}

3. Centralized authorization utility

For complex rules, implement a utility that encapsulates authorization logic, reducing duplication and ensuring consistent checks across endpoints.

enum Resource {
    Profile(String), // user_id
    AdminPanel,
}

fn authorize(user: &User, resource: &Resource) -> bool {
    match resource {
        Resource::Profile(owner_id) => user.id == *owner_id,
        Resource::AdminPanel => user.role == "admin",
    }
}

// Usage in handler
async fn generic_handler(
    BasicAuth(auth): BasicAuth,
    Path(resource): Path<Resource>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    if !authorize(&auth.user, &resource) {
        return Err((StatusCode::FORBIDDEN, "Not authorized".to_string()));
    }
    Ok("Authorized")
}

With these patterns, Basic Auth in Axum becomes a clear authentication boundary, while explicit authorization checks enforce identity- and role-based access per request, mitigating BOLA/IDOR and privilege escalation risks.

Frequently Asked Questions

Does middleBrick fix vulnerabilities found in Basic Auth setups in Axum?
No. middleBrick detects and reports findings with remediation guidance; it does not fix, patch, block, or remediate issues.
Can a middleBrick scan test authorization logic when only Basic Auth is used?
Yes. By authenticating with Basic Auth credentials, middleBrick can test unauthenticated and authenticated attack surfaces, including BOLA/IDOR and privilege escalation checks, against endpoints that rely on Basic Auth.