Insecure Direct Object Reference in Axum with Api Keys
Insecure Direct Object Reference in Axum with Api Keys — how this specific combination creates or exposes the vulnerability
Insecure Direct Object Reference (BOLA/IDOR) occurs when an API exposes internal object identifiers—such as numeric IDs or UUIDs—and allows a caller to manipulate those identifiers to access or modify data they should not see. In Axum, this commonly arises when route parameters or query values are used directly as database keys without confirming that the requesting caller is authorized for that specific resource. Introducing API keys into this mix changes the threat surface: keys identify the client application or service, but they do not automatically imply authorization for a particular resource. A key may be valid, yet the application may still fail to enforce per-key access scopes, allowing an attacker who knows or guesses another resource identifier to pivot across users or tenants.
Consider an Axum handler that retrieves a document by ID taken directly from the URL, validated only by type and presence of an API key in a header. The API key may map to an application with read access, but the handler does not check whether the document belongs to the scope permitted by that key. This mismatch between application-level identification (API key) and data-level authorization (resource ownership or tenant) is a classic BOLA/IDOR setup. Attackers can enumerate predictable IDs or leverage information leakage to iterate through resources, escalating the impact through vertical or horizontal privilege abuse.
In practice, this can intersect with other checks run by middleBrick’s 12 security checks, such as Property Authorization and Authentication. MiddleBrick may flag that an endpoint accepts an API key yet does not bind key permissions to the object being accessed. The scanner does not assume internal architecture, but it can correlate runtime behavior—such as successful retrieval with an unauthorized ID and a valid key—with specification expectations defined in your OpenAPI/Swagger 2.0, 3.0, or 3.1 documents. Cross-referencing spec definitions with runtime findings helps highlight where authorization checks are missing relative to declared scopes or security schemes. This is especially important when API keys are used as bearer tokens without additional context like tenant IDs or user roles.
Real-world examples include endpoints like /documents/{doc_id} where doc_id is user-controlled and the API key only identifies the client application. If the handler does not verify that the document’s tenant or owner aligns with the permissions encoded by the key, the endpoint is vulnerable. Similarly, numeric or UUID-based identifiers that skip validation or normalization can allow IDOR when combined with key-based authentication, because keys alone do not encode object-level permissions. MiddleBrick’s checks for BOLA/IDOR, alongside Authentication and Property Authorization, are designed to surface these gaps by testing unauthenticated attack surfaces and inspecting how responses differ across manipulated identifiers even when keys are present.
Api Keys-Specific Remediation in Axum — concrete code fixes
Remediation centers on ensuring that every access to a resource validates both the API key and the relationship between the key and the requested object. In Axum, this means enriching your handlers with explicit authorization checks, leveraging application state to map keys to permissions, and avoiding direct use of user-supplied identifiers as database keys without scoping.
One approach is to maintain a mapping from API key to allowed scopes or tenant IDs, then enforce that scope in each handler. Below is a simplified but realistic Axum example that demonstrates this pattern. The handler extracts the key, looks up permissions, and confirms the target document belongs to an authorized scope before proceeding.
use axum::{routing::get, Router, extract::State, http::Request, response::IntoResponse};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
// Map from API key to allowed tenant IDs
key_to_tenants: Arc>>,
// Simulated document store: document ID -> tenant ID
documents: Arc>,
}
async fn get_document(
State(state): State>,
key: Option>,
axum::extract::Path(doc_id): axum::extract::Path,
) -> impl IntoResponse {
let key = match key {
Some(k) => k.token(),
None => return (axum::http::StatusCode::UNAUTHORIZED, "Missing API key").into_response(),
};
let tenants = match state.key_to_tenants.get(key) {
Some(t) => t,
None => return (axum::http::StatusCode::FORBIDDEN, "Key not recognized").into_response(),
};
let doc_tenant = match state.documents.get(&doc_id) {
Some(t) => *t,
None => return (axum::http::StatusCode::NOT_FOUND, "Document not found").into_response(),
};
if !tenants.contains(&doc_tenant) {
return (axum::http::StatusCode::FORBIDDEN, "Access denied to this document").into_response();
}
// Safe to return the document
(axum::http::StatusCode::OK, format!("Document {} data", doc_id)).into_response()
}
#[tokio::main]
async fn main() {
let mut key_to_tenants = HashMap::new();
key_to_tenants.insert("valid_key_123".to_string(), vec![1, 2]);
key_to_tenants.insert("other_key_456".to_string(), vec![3]);
let documents: HashMap = [(1, 1), (2, 1), (3, 3)].into_iter().collect();
let state = Arc::new(AppState {
key_to_tenants: Arc::new(key_to_tenants),
documents: Arc::new(documents),
});
let app = Router::new()
.route("/documents/:doc_id", get(get_document))
.with_state(state);
// axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
}
In this example, the API key is not used alone; it is paired with tenant scoping. The key_to_tenants mapping ensures that even if a document ID is known, access is only granted when the key’s allowed tenants include the document’s tenant. This directly mitigates BOLA/IDOR by enforcing data-level authorization tied to the key’s identity. In production, the mapping would be backed by a secure store and dynamically refreshed, and keys would be treated as opaque identifiers rather than trust boundaries.
Alternatively, if your domain uses UUID-based resources with tenant context, you can encode tenant into the resource reference and validate it at runtime. For user-specific endpoints, prefer binding identifiers to user or tenant IDs already present in the key’s claims, rather than trusting the client-supplied reference alone. MiddleBrick’s scans can help verify that your runtime behavior aligns with these patterns by checking for missing authorization steps even when keys are present.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |