GuardAPI

Fix BOLA (Broken Object Level Authorization) in Actix Web

BOLA (Broken Object Level Authorization), previously known as IDOR, is the most exploited vulnerability in modern APIs. In the context of Actix Web, it occurs when a developer assumes that because a user is authenticated, they are authorized to access any resource ID they pass in the request path or body. If you are fetching a record from your database using only the ID provided by the client, you are leaking data.

The Vulnerable Pattern

#[get("/api/invoice/{invoice_id}")]
async fn get_invoice(path: web::Path, db: web::Data) -> impl Responder {
    let id = path.into_inner();
// VULNERABLE: The handler trusts the invoice_id from the URL.
// Any authenticated user can iterate the ID and steal invoices.
let invoice = sqlx::query!("SELECT * FROM invoices WHERE id = $1", id)
    .fetch_one(db.get_ref())
    .await;

match invoice {
    Ok(data) => HttpResponse::Ok().json(data),
    Err(_) => HttpResponse::NotFound().finish(),
}

}

The Secure Implementation

To remediate BOLA in Actix Web: 1. Stop relying on client-side IDs for access control. 2. Implement a custom 'FromRequest' extractor to securely identify the requester via JWT or session. 3. Enforce authorization at the database level by always including the user's unique identifier in the 'WHERE' clause. 4. Use 'fetch_optional' instead of 'fetch_one' to distinguish between a missing resource and an unauthorized access attempt, returning a 403 Forbidden or 404 Not Found to prevent resource enumeration.

#[get("/api/invoice/{invoice_id}")]
async fn get_invoice_secure(
    path: web::Path,
    db: web::Data,
    auth: Claims // Custom extractor that validates JWT/Session and returns user context
) -> impl Responder {
    let invoice_id = path.into_inner();
    let user_id = auth.sub; // Trusted identity from the secure token
// SECURE: Ownership check is hardcoded into the query logic.
// The query will only return the record if it belongs to the requester.
let invoice = sqlx::query!(
    "SELECT * FROM invoices WHERE id = $1 AND owner_id = $2",
    invoice_id,
    user_id
)
.fetch_optional(db.get_ref())
.await;

match invoice {
    Ok(Some(data)) => HttpResponse::Ok().json(data),
    Ok(None) => HttpResponse::Forbidden().body("Unauthorized access to resource"),
    Err(_) => HttpResponse::InternalServerError().finish(),
}

}

About this page

Framework notes in /guides are generated sketches kept for URL stability. They are not human pentest reports and they are not GuardAPI scan output. The product is a GET-only BOLA merge gate. Maintained by GuardAPI. Questions: support@guard-api.com