Cookbook / Middleware and state

Hardening an API

Security headers, CORS, rate limiting, compression and request limits in one setup.

Axeon is secure by default (size limits, timeouts, slow-client protection, no internal errors sent to clients). These built-in middleware cover the rest of a typical public API.

src/main.rs
use axeon::middleware::{Compression, Cors, RateLimiter, SecurityHeaders};
use axeon::{Json, Server};
use axeon::json;
use std::time::Duration;

fn main() {
    let mut app = Server::new();

    app.max_body_size(1024 * 1024)               // 413 above 1 MiB (default 2 MiB)
        .max_header_size(16 * 1024)              // 431 above 16 KiB
        .request_timeout(Duration::from_secs(10)) // headers, body and TLS handshake
        .write_timeout(Duration::from_secs(10));  // clients that stop reading

    app.middleware(SecurityHeaders::new()) // HSTS, nosniff, frame options, CSP...
        .middleware(Compression::new())    // gzip / deflate
        .middleware(
            Cors::new()
                .allow_origin("https://app.example.com")
                .allow_credentials(true),
        )
        // 120 requests a minute per client, with bursts of 20.
        // Add .trust_proxy(true) only behind a proxy that sets X-Forwarded-For.
        .middleware(RateLimiter::per_minute(120).burst(20));

    app.get("/api/ping", |_req| async { Json(json!({ "pong": true })) });

    // A tighter limit on one route.
    app.route("/api/avatar")
        .body_limit(64 * 1024)
        .post(|_req| async { (201, "stored") });

    app.listen("127.0.0.1:3000").expect("server failed to start");
}
Terminal
curl -i http://127.0.0.1:3000/api/ping -H 'origin: https://app.example.com'

Notes#

  • Order matters. Middleware added first runs first, so put things that must see every request (request IDs, logging, security headers) at the top.
  • When the rate limit is exceeded the client gets 429 with a Retry-After header.
  • Rate limiting is per process. Behind several instances, also limit at your load balancer.
  • The limit key defaults to the client IP. It can also be an API key or user.