HIGH auth bypassaxumpostgresql

Auth Bypass in Axum with Postgresql

Auth Bypass in Axum with Postgresql — how this specific combination creates or exposes the vulnerability

Auth bypass occurs when an attacker accesses protected endpoints without valid authentication. In an Axum application using Postgresql as the identity store, the risk typically arises from improper session handling, missing authorization checks, or unsafe deserialization of user-supplied tokens. Even when routes are annotated with guards, developers may inadvertently allow access when database queries return unexpected rows or when error paths leak information that can be exploited.

Consider an Axum handler that retrieves a user by email from Postgresql and creates a session token without verifying role or revocation status:

// Example of a vulnerable Axum handler with Postgresql
async fn login(
    pool: web::Data,
    form: web::Form,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let row = sqlx::query(
        "SELECT id, password_hash, is_active FROM users WHERE email = $1"
    )
    .bind(&form.email)
    .fetch_optional(&pool)
    .await
    .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "db error".to_string()))?;

    let Some(row) = row else {
        return Err((StatusCode::UNAUTHORIZED, "invalid credentials".to_string()));
    };

    // Vulnerable: no check for is_active or role; weak token generation
    let token = encode(&Claims::new(row.get(0)));
    Ok(Json(LoginResponse { token }))
}

If the is_active column is not enforced at the query or application layer, an attacker who compromises an inactive or suspended account may still authenticate. Additionally, if the token is accepted without validating scope or session revocation state in Postgresql, an attacker can reuse tokens after password reset or account disablement. This becomes an auth bypass when protected routes rely solely on token presence instead of verifying the latest state from Postgresql.

Another common pattern is deserializing a JWT and trusting claims without rechecking user status in Postgresql. For example:

// JWT validated but user state not rechecked in Postgresql
async fn protected_route(
    token: String,
    pool: web::Data<Pool>
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let claims = decode_verify(&token).map_err(|_| ...)?;
    // Missing: verify that the user still exists and is_active = true in Postgresql
    let user_id: i32 = claims.sub.parse().unwrap();
    // Proceed with user_id assuming authentication is sufficient
    ...
}

In this scenario, an attacker who obtained a token before deactivation can bypass controls because the handler does not revalidate the user’s active status against Postgresql on every request. The combination of Axum’s flexible guard system and Postgresql’s role as the source of truth becomes a vulnerability when the application skips revalidation or does not enforce row-level policies in queries.

BOLA/IDOR can also emerge when object-level references (e.g., user_id) are taken from the token and used directly in Postgresql queries without confirming that the requesting subject owns the resource. If the authorization check is omitted, an attacker can change the ID in the request and access other users’ data.

Postgresql-Specific Remediation in Axum — concrete code fixes

Remediation focuses on strict revalidation against Postgresql, enforcing row ownership, and using parameterized queries to prevent injection and data leaks. Always recheck critical attributes (active, roles, consent) on each request or use short-lived tokens combined with state stored in Postgresql.

1) Enforce active status and roles in the login query:

// Secure login with Postgresql state validation
async fn login(
    pool: web::Data<Pool>,
    form: web::Form<LoginInput>,
) -> Result<impl IntoResponse, (StatusCode, String)> {
    let row = sqlx::query(
        "SELECT id, password_hash, role FROM users WHERE email = $1 AND is_active = TRUE"
    )
    .bind(&form.email)
    .fetch_optional(&pool)
    .await
    .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "db error".to_string()))?;

    let Some(row) = row else {
        return Err((StatusCode::UNAUTHORIZED, "invalid credentials".to_string()));
    };

    // Verify password, then issue token with minimal claims
    if !verify_password(&form.password, &row.get::<_, String>("password_hash")) {
        return Err((StatusCode::UNAUTHORIZED, "invalid credentials".to_string()));
    }

    let token = encode(&Claims::new_with_role(row.get(0), row.get(1)));
    Ok(Json(LoginResponse { token }))
}

2) Re-validate user state on each protected request and enforce ownership:

// Example of Postgresql revalidation and BOLA prevention in Axum
async fn get_user_data(
    user_id: i32,
    token_claims: Claims,
    pool: web::Data<Pool>
) -> Result<Json<UserData>, (StatusCode, String)> {
    // Ensure the requesting user matches the resource owner
    let row = sqlx::query(
        "SELECT id, email, name FROM users WHERE id = $1 AND id = $2 AND is_active = TRUE"
    )
    .bind(token_claims.sub.parse().unwrap_or(0))
    .bind(user_id)
    .fetch_optional(&pool)
    .await
    .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "db error".to_string()))?;

    let row = row.ok_or((StatusCode::FORBIDDEN, "access denied".to_string()))?;
    Ok(Json(UserData {
        id: row.get(0),
        email: row.get(1),
        name: row.get(2),
    }))
}

3) Use parameterized queries and avoid dynamic SQL to prevent injection and ensure consistent execution plans:

// Safe Postgresql usage in Axum
async fn search_profiles(
    query: web::Query<SearchParams>,
    pool: web::Data<Pool>
) -> Result<Json<Vec<Profile>>, (StatusCode, String)> {
    let rows = sqlx::query(
        "SELECT id, display_name FROM profiles WHERE user_id = $1 AND status = $2"
    )
    .bind(query.user_id)
    .bind(&query.status)
    .fetch_all(&pool)
    .await
    .map_err(|e| {
        // Log without exposing details
        (StatusCode::INTERNAL_SERVER_ERROR, "data unavailable".to_string())
    })?;

    Ok(Json(
        rows.iter().map(|r| Profile {
            id: r.get(0),
            display_name: r.get(1),
        }).collect()
    ))
}

4) Store revocation state in Postgresql and check it on token use:

// Example revocation check
async fn verify_token_with_revocation(
    token: &str,
    pool: web::Data<Pool>
) -> Result<Claims, (StatusCode, String)> {
    let (header, claims, signature) = decode_secret(token).map_err(|_| ...)?;
    let jti: String = claims.get_jti().ok_or(...)?;

    let revoked: bool = sqlx::query_scalar(
        "SELECT revoked FROM auth_revocation WHERE jti = $1"
    )
    .bind(jti)
    .fetch_one(&pool)
    .await
    .unwrap_or(false);

    if revoked {
        return Err((StatusCode::UNAUTHORIZED, "token revoked".to_string()));
    }
    Ok(claims)
}

These patterns ensure that Axum routes place explicit trust in Postgresql for identity and authorization, reducing the chance of auth bypass through stale or incorrect application state.

Related CWEs: authentication

CWE IDNameSeverity
CWE-287Improper Authentication CRITICAL
CWE-306Missing Authentication for Critical Function CRITICAL
CWE-307Brute Force HIGH
CWE-308Single-Factor Authentication MEDIUM
CWE-309Use of Password System for Primary Authentication MEDIUM
CWE-347Improper Verification of Cryptographic Signature HIGH
CWE-384Session Fixation HIGH
CWE-521Weak Password Requirements MEDIUM
CWE-613Insufficient Session Expiration MEDIUM
CWE-640Weak Password Recovery HIGH

Frequently Asked Questions

What is BOLA/IDOR and how does it relate to Postgresql queries in Axum?
BOLA (Broken Object Level Authorization) / IDOR occurs when an API exposes object references (like user_id) and allows actions without confirming that the requesting subject owns that object. In Axum with Postgresql, this happens if you use a user_id from a token to query Postgresql without verifying that the token’s subject matches the requested resource. Always bind both the subject claim and the target ID in parameterized queries and enforce ownership checks.
How does middleBrick handle API security scanning for Axum + Postgresql setups?
middleBrick scans the unauthenticated attack surface of your API endpoints and checks 12 security controls in parallel, including Authentication, BOLA/IDOR, and Data Exposure. It supports OpenAPI/Swagger spec analysis with full $ref resolution and cross-references spec definitions with runtime findings. If you use the GitHub Action, you can add API security checks to your CI/CD pipeline and fail builds if the security score drops below your threshold.