Api Rate Abuse in Actix with Saml
Api Rate Abuse in Actix with Saml — how this specific combination creates or exposes the vulnerability
Rate abuse in Actix when SAML is used for authentication can occur when unauthenticated or partially authenticated endpoints are exposed or when session validation is not enforced consistently across protected routes. In an Actix-web service, SAML assertions are typically validated once at an authentication layer, and a successful validation may establish a session or claims that are reused across multiple requests. If rate-limiting is applied only to unauthenticated paths or is scoped by IP address alone, an authenticated user obtained via SAML can trigger excessive requests that bypass IP-based protections. This situation commonly arises when SAML endpoints or assertion consumers do not enforce per-principal limits, allowing an attacker who compromises a single SAML identity to perform credential stuffing, brute-force authorization checks, or automated scraping at scale.
Another vector specific to the Actix + SAML combination is inconsistent enforcement of authentication state across middleware and route handlers. For example, if some routes rely on session cookies set after SAML login while others accept bearer tokens or API keys, the rate-limiting policy might not cover all entry points. Attackers can probe the uncovered routes to evade detection. Additionally, SAML responses may carry attributes such as roles or group memberships that are used for authorization but not considered in rate calculations. Without tying rate limits to the authenticated principal (e.g., a SAML NameID or session identifier), an attacker can rotate identities within the same tenant and exhaust backend resources or trigger denial-of-service conditions.
Operational impacts include inflated backend load, increased authentication processing, and potential lockouts for legitimate users due to noisy neighbors. Because SAML flows often involve redirects and artifact resolution, noisy clients can amplify traffic on the Assertion Consumer Service (ACS) endpoint. In an Actix service, this may manifest as high CPU or memory usage in the handlers that process SAML assertions or as errors in session stores. Effective mitigation requires binding rate limits to the SAML session or authenticated identity, ensuring that limits apply across all entry paths, and monitoring for anomalous patterns such as many requests with the same SAML subject across different endpoints.
Saml-Specific Remediation in Actix — concrete code fixes
To remediate rate abuse in Actix with SAML, tie rate limits to the authenticated principal rather than IP address, and ensure all entry points are covered. Use session identifiers or SAML NameID values as keys for rate-limiting logic. Below are concrete Actix-web patterns and SAML validation snippets that demonstrate a robust approach.
Example SAML validation middleware in Actix
use actix_web::{dev::ServiceRequest, Error, HttpResponse};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use std::rc::Rc;
use std::cell::RefCell;
// A simple in-memory rate limiter keyed by SAML NameID
struct RateLimiter {
limits: std::collections::HashMap,
max_requests: usize,
window: std::time::Duration,
}
impl RateLimiter {
fn new(max_requests: usize, window: std::time::Duration) -> Self {
Self {
limits: std::collections::HashMap::new(),
max_requests,
window,
}
}
fn allow(&mut self, key: &str) -> bool {
let now = std::time::Instant::now();
let entry = self.limits.entry(key.to_string()).or_insert((0, now));
if now.duration_since(entry.1) > self.window {
entry.0 = 1;
entry.1 = now;
true
} else if entry.0 < self.max_requests {
entry.0 += 1;
true
} else {
false
}
}
}
// SAML assertion validator stub (replace with a real SAML library)
fn validate_saml_assertion(assertion: &str) -> Option<(String, Vec<String>)> {
// In practice, parse and verify the SAML assertion, extract NameID and roles
// Return Some((nameid, roles)) if valid, None otherwise
if assertion.starts_with("VALID") {
Some(("saml_nameid_123".to_string(), vec!["role-user".to_string()]))
} else {
None
}
}
async fn saml_auth_middleware(
req: ServiceRequest,
srv: &actix_web::dev::Service<actix_web::dev::ServiceRequest>,
) -> Result<actix_web::dev::ServiceResponse, Error> {
// Extract SAML assertion from header or form parameter
let assertion = match req.headers().get("X-SAML-Assertion") {
Some(h) => h.to_str().unwrap_or(""),
None => return Ok(req.into_response(HttpResponse::Unauthorized().finish())),
};
let (nameid, _roles) = validate_saml_assertion(assertion).ok_or_else(|| {
actix_web::error::ErrorUnauthorized("Invalid SAML assertion")
})?;
// Attach principal to request extensions for later use
req.extensions_mut().insert(nameid);
srv.call(req).await
}
Rate limiter integration and SAML-aware route guards
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use std::sync::Mutex;
// Shared rate limiter protected by Mutex
struct AppState {
limiter: Mutex<RateLimiter>,
}
async fn protected_route(
data: web::Data<AppState>,
principal: String, // extracted from extensions by a custom guard
) -> impl Responder {
HttpResponse::Ok().body(format!("Access granted for {}", principal))
}
// Custom guard to extract principal from extensions
fn require_saml_principal(
req: &actix_web::HttpRequest,
) -> Result<String, actix_web::Error> {
req.extensions()
.get::()
.cloned()
.ok_or_else(|| actix_web::error::ErrorUnauthorized("Missing principal"))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let data = web::Data::new(AppState {
limiter: Mutex::new(RateLimiter::new(60, std::time::Duration::from_secs(60))),
});
HttpServer::new(move || {
App::new()
.app_data(data.clone())
.wrap_fn(|req, srv| {
let principal = match validate_saml_assertion(
req.headers().get("X-SAML-Assertion")
.and_then(|v| v.to_str().ok())
.unwrap_or(""),
) {
Some((nameid, _)) => nameid,
None => return Ok(req.into_response(HttpResponse::Unauthorized().finish())),
};
// Insert principal for guards and handlers
let mut req = req;
req.extensions_mut().insert(principal.clone());
let fut = srv.call(req);
async move { fut.await }
})
.service(web::resource("/api/endpoint").to(protected_route))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Key practices:
- Use SAML NameID or a stable session identifier as the rate-limit key to ensure per-user limits across all ACS and API endpoints.
- Apply rate limits at the edge (e.g., API gateway or reverse proxy) and enforce them in application middleware to prevent bypass via alternate paths.
- Monitor SAML assertion validation failures and anomalous patterns, such as many requests with the same NameID over short windows, to detect abuse or compromised identities.
- Combine rate limiting with attribute-based access controls that consider SAML roles and groups, and ensure all routes—whether they use session cookies, bearer tokens, or API keys—are covered by the same policy.