rustcorshttp-status-code-405rust-towerrust-axum

Axum router rejecting CORS OPTIONS preflight with 405 even with CorsLayer


Before a fetch is sent, the browser will send a request method OPTIONS to confirm that the API will accept the request from a script with a particular origin.

Chrome shows that the my Axum server is rejecting my client's request with 405. My router looks something like this:

let app = Router::new()
    .layer(TraceLayer::new_for_http())
    .layer(CorsLayer::permissive())
    .route("/api", post(server));

Router::layer says All requests to the router will be processed by the layer’s corresponding middleware. but I'm not sure its doing its job.


Solution

  • The .layer() function is a builder so returns a new Router, with the inner routers wrapped. The route /api will be tested first and be rejected 405 because only request method POST is supported - not OPTIONS.

    In summary, you need your CorsLayer "outside" your route so it can answer OPTIONS requests.

    Note the example in the documentation:

    // All requests to `first_handler` and `second_handler` will be sent through
    // `ConcurrencyLimit`
    let app = Router::new().route("/", get(first_handler))
        .route("/foo", get(second_handler))
        .layer(ConcurrencyLimitLayer::new(64))
        // Request to `GET /bar` will go directly to `third_handler` and
        // wont be sent through `ConcurrencyLimit`
        .route("/bar", get(third_handler));
    

    By the way, your TraceLayer is not tracing your API calls for the same reason!

    Try this, and you'll see the OPTIONS request logged, and the POST should hit your server:

    let app = Router::new()
        .route("/api", post(server))
        .layer(CorsLayer::permissive())
        .layer(TraceLayer::new_for_http());