Using tower-http Middleware - CORS & Compression

1. Welcome and Icebreaker:

Have you ever run into a CORS error in your browser? What did you do to fix it? Or, have you ever noticed a website loading faster than expected? Sometimes, that’s thanks to response compression!

2. Outline the Session:

Agenda:

  1. Concept Explanation: What are CORS and compression? Why are they important?
  2. Live Coding: Add CORS and compression middleware to your Axum app.
  3. Tasks: Practice by customizing CORS and compression settings.

Learning Outcomes:

  • Understand and implement CORS and compression middleware in Axum.
  • Configure CORS policies and enable response compression.
  • Recognize security and performance implications of middleware choices.

Expectations: Try changing the CORS policy and see how it affects requests from different origins! Experiment with compression and observe the difference in response sizes.

3. Concept Explanation:

CORS (Cross-Origin Resource Sharing) is a browser-enforced policy controlling which cross-origin pages may read responses from your server. Without proper CORS configuration, browsers can block frontend applications hosted on a different origin from reading your API responses. CORS does not stop non-browser clients or replace authentication.

Compression, on the other hand, is a performance optimization technique. By compressing HTTP responses (using algorithms like gzip or brotli), you can significantly reduce the amount of data sent over the network, resulting in faster load times and reduced bandwidth usage. This is especially important for APIs that return large payloads or serve many users.

The tower-http crate provides middleware layers for both CORS and compression:

  • CorsLayer allows you to configure which origins, methods, and headers are permitted.
  • CompressionLayer automatically compresses responses if the client supports it.

Why use middleware? Middleware in Axum (and other web frameworks) lets you add cross-cutting concerns—like security, logging, or compression—without cluttering your handler logic. Middleware is applied to all or part of your app, making it easy to enforce policies consistently.

4. Live Coding Demonstration:

Let’s see how to add CORS and compression middleware to an Axum app. Add tower-http = { version = "0.6", features = ["cors", "compression-gzip"] } alongside Axum 0.8 and Tokio 1. We’ll start with permissive settings, then show how to restrict CORS for browser clients.

use axum::{Router, routing::get};
use tower_http::{cors::CorsLayer, compression::CompressionLayer};

async fn hello() -> &'static str {
    "Hello, Axum!"
}

#[tokio::main]
async fn main() {
    // Permissive CORS (not recommended for production)
    let cors = CorsLayer::permissive();

    // Restrictive CORS: only allow requests from https://example.com
    // let cors = CorsLayer::new().allow_origin("https://example.com".parse::<axum::http::HeaderValue>().unwrap());

    // Custom CORS: allow only GET and POST, allow credentials, and custom headers
    // let cors = CorsLayer::new()
    //     .allow_origin("https://example.com".parse::<axum::http::HeaderValue>().unwrap())
    //     .allow_methods([axum::http::Method::GET, axum::http::Method::POST])
    //     .allow_headers([axum::http::header::AUTHORIZATION, axum::http::header::CONTENT_TYPE])
    //     .allow_credentials(true);

    let app = Router::new()
        .route("/", get(hello))
        .layer(cors)
        .layer(CompressionLayer::new());

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

More about CORS Implementation in Axum

  • Permissive CORS (CorsLayer::permissive()):
    • Allows any origin, method, and header. Useful for development, but insecure for production.
  • Restricting Origins: Use .allow_origin(header_value) for one origin or .allow_origin([origin1, origin2]) for multiple origins.
  • Allowed Methods: Use .allow_methods([Method::GET, Method::POST]) to restrict which HTTP methods are accepted from cross-origin requests.
  • Allowed Headers: Use .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE]) to specify which headers can be sent by the client.
  • Credentials: Use .allow_credentials(true) if your frontend needs to send cookies or HTTP authentication. Note: when credentials are allowed, you cannot use * as the allowed origin.
  • Preflight Requests: Browsers send an OPTIONS request before certain cross-origin requests. CORS middleware automatically handles these preflight requests.
  • Multiple Origins: Pass an array of parsed HeaderValues to .allow_origin([...]).

Example: Allowing Multiple Origins

let cors = CorsLayer::new()
    .allow_origin([
        "https://example.com".parse::<axum::http::HeaderValue>().unwrap(),
        "https://another.com".parse::<axum::http::HeaderValue>().unwrap(),
    ]));

Example: Allowing Credentials

let cors = CorsLayer::new()
    .allow_origin("https://example.com".parse::<axum::http::HeaderValue>().unwrap())
    .allow_credentials(true);

Example: Allowing Custom Headers

let cors = CorsLayer::new()
    .allow_origin("https://example.com".parse::<axum::http::HeaderValue>().unwrap())
    .allow_headers([axum::http::header::AUTHORIZATION, axum::http::header::CONTENT_TYPE]);

Tip: Always test your CORS settings with your actual frontend and browser, as CORS errors can be subtle and browser-specific.

Best Practices:

  • Set CORS policies as strict as possible for security. Only allow trusted domains.
  • Enable compression for large or frequent responses, but be aware of CPU usage on very high-traffic servers.
  • Always test CORS with real browsers and frontend apps.

Common Pitfalls:

  • CORS only restricts browser access; it is not authentication or protection against non-browser clients.
  • Forgetting to allow credentials or custom headers if your frontend needs them.
  • Not testing CORS with real browsers (browser behavior can differ from tools like curl/Postman).
  • Not verifying that compression is actually happening (check response headers like content-encoding).

5. Tasks

  • Restrict CORS: Change the CORS configuration to only allow requests from https://example.com. Try making requests from other origins and observe the result.
  • Test Compression: Send a large response (e.g., a long string or JSON array) and compare the response size with and without CompressionLayer enabled. Use browser dev tools or curl --compressed to inspect the content-encoding header and response size.
  • Bonus: Try allowing only specific HTTP methods (e.g., only GET and POST) in your CORS policy. What happens if you send a PUT request?