Credential Stuffing in Axum with Firestore
Credential Stuffing in Axum with Firestore — how this specific combination creates or exposes the vulnerability
Credential stuffing is an automated attack where attackers use previously leaked username and password pairs to gain unauthorized access. When an Axum web service relies on Firestore for authentication state or user record storage without additional protections, the combination can expose or enable credential stuffing risks.
Axum is a Rust web framework that does not include built-in authentication mechanisms; developers typically integrate authentication via middleware and session management or by validating credentials against a database. If Axum endpoints that sign in or verify users directly query a Firestore collection using user-supplied identifiers without rate limiting or other protections, attackers can automate requests with stolen credentials.
Firestore, a NoSQL document database, stores user data such as email and password hashes. If Firestore rules are misconfigured—permitting read or write access based only on authentication tokens that can be bypassed—attackers may leverage credential stuffing to probe for valid accounts. For example, if an endpoint like /login accepts a JSON payload with email and password and performs a Firestore get on each request without throttling, attackers can iterate through credential lists efficiently.
Another risk arises from how Axum applications handle user enumeration. If an Axum route returns different responses for missing users versus incorrect passwords, attackers can use this behavior to map valid accounts. When those valid accounts are then targeted with credential stuffing campaigns using passwords sourced from prior breaches, the risk of unauthorized access increases.
Insecure Firestore security rules can further amplify the issue. Rules that allow broad read access to user documents based on a known UID derived from attacker-controlled input may enable enumeration or account confirmation during stuffing attempts. Even when Axum mediates access, weak rules may permit bypassing intended constraints under certain request patterns.
Because Firestore does not enforce native account lockout or rate limiting, Axum developers must implement these protections at the application or infrastructure layer. Without such controls, a Firestore-backed Axum service becomes a viable target for credential stuffing, especially if authentication endpoints do not enforce strict request throttling, token-based protections, or multi-factor authentication.
Firestore-Specific Remediation in Axum — concrete code fixes
To mitigate credential stuffing in an Axum application backed by Firestore, implement robust rate limiting, strong password storage, and secure Firestore rules. Below are concrete remediation steps and code examples.
1. Rate limiting on authentication endpoints
Use Axum middleware to enforce per-IP or per-account request limits on login routes. For example, with tower::limit::RateLimitLayer or a custom layer:
use axum::{routing::post, Router};
use tower_http::limit::RateLimitLayer;
use std::time::Duration;
let app = Router::new()
.route("/login", post(login_handler))
.layer(RateLimitLayer::new(10, Duration::from_secs(60))); // 10 requests per minuteThis reduces the feasibility of automated credential guessing by limiting request bursts.
2. Secure password storage and verification
Store passwords using a strong adaptive hashing algorithm such as Argon2. When creating or updating a user in Firestore, hash the password before persisting:
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier, rand_core::OsRng};
use axum::{Json, extract::State};
use firestore::*; // Assume a Firestore client binding
async fn create_user_handler(
State(db): State,
Json(payload): Json,
) -> Result<impl IntoResponse, (StatusCode, String)> {
let hashed = Argon2::default().hash_password(payload.password.as_bytes(), &OsRng)?;
let user = UserDoc {
email: payload.email,
password_hash: hashed.to_string(),
};
db.create(&user).await?;
Ok(Json("User created"))
} During login, verify using verify_password and ensure timing-safe comparison practices.
3. Parameterized Firestore queries to prevent injection and enumeration
Use Firestore SDK methods that parameterize queries rather than string-based lookups. For example, fetch a user by email using a direct document reference or query with a bound parameter:
use firestore::*;
async fn verify_user(email: &str, db: &FirestoreDb) -> Option<UserDoc> {
let user_ref = db.collection("users").doc(email_to_doc_id(email)); // deterministic mapping with caution
user_ref.get().await.ok()
}Ensure Firestore security rules restrict reads to authenticated contexts and avoid rules that allow broad listing or unrestricted document access based on user input.
4. Consistent error responses and account enumeration protection
Return the same generic error for invalid credentials regardless of whether the account exists. In Axum, standardize response bodies and status codes:
async fn login_handler(
Json(payload): Json<LoginPayload>,
State(db): State<FirestoreDb>,
) -> impl IntoResponse {
let user = verify_user(&payload.email, &db).await;
// Always perform a dummy hash to mitigate timing attacks
let _ = Argon2::default().hash_password(b"dummy", &OsRng).ok();
match user {
Some(u) if verify_password(&payload.password, &u.password_hash).is_ok() => login_success(),
_ => login_failure(), // Same status and message for all failures
}
}5. Complementary protections
- Enable multi-factor authentication to reduce reliance on passwords alone.
- Monitor and log authentication attempts for anomaly detection.
- Periodically review Firestore rules to ensure they do not inadvertently expose user documents.