Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Trusted forwarded addresses

This example demonstrates where an application-owned forwarding-header policy can live. It assumes a trusted Nginx proxy removes client-provided forwarding headers and writes the value received by the application.

cargo run --example axum_x_forwarded_for --features axum,memory

The server listens on http://127.0.0.1:3001.

Complete source

use std::{
    error::Error,
    net::{IpAddr, SocketAddr},
    time::Duration,
};

use axum::{Router, routing::get};
use http::Request;
use tower_rate_limiter::{IpKeyExtractor, KeyExtractor, MemoryStore, RateLimitError, RateLimitLayer};

/// The deployment's trusted Nginx proxy strips client-provided forwarding headers and writes
/// this value. The crate deliberately does not provide this trust policy itself.
#[derive(Clone, Copy)]
struct TrustedForwardedAddress;

impl KeyExtractor for TrustedForwardedAddress {
    type Key = IpAddr;

    fn extract<B>(&self, request: &Request<B>) -> Result<Self::Key, RateLimitError> {
        if let Some(header) = request.headers().get("x-forwarded-for")
            && let Ok(value) = header.to_str()
        {
            for candidate in value.split(',') {
                if let Ok(address) = candidate.trim().parse::<IpAddr>() {
                    return Ok(address);
                }
            }
        }

        IpKeyExtractor::new().extract(request)
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let key_extractor = TrustedForwardedAddress;
    let limiter = RateLimitLayer::builder(key_extractor)
        .policy_name("forwarded-limit")
        .limit(100)
        .window(Duration::from_secs(60))
        .with_store(MemoryStore::new())
        .build()?;
    let app = Router::new().route("/health", get(|| async { "ok" })).layer(limiter);

    let address: SocketAddr = "127.0.0.1:3001".parse()?;
    let listener = tokio::net::TcpListener::bind(address).await?;
    println!("listening on http://{address}");
    axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await?;
    Ok(())
}

Do not copy this parser unless the deployment enforces the trust assumption described above. On an internet-facing server that accepts arbitrary X-Forwarded-For, a client could select or rotate its own rate-limit key.