seanmonstar/warp · error · CorsForbidden

CORS request forbidden

Error message

CORS request forbidden

What it means

This is warp's CorsForbidden rejection, produced by the CORS filter (src/filters/cors.rs) when an incoming request violates the configured CORS policy. The filter validates the Origin, request method, and request headers against the allow-list configured via warp::cors(); any mismatch yields err, which is wrapped into a known rejection with kind = CorsForbidden. The server deliberately rejects the request instead of serving it, because cross-origin access was not granted.

Solutions

  1. Add the browser origin (scheme + host + port, exactly) to cors().allow_origin([...])
  2. Allow the failing method and headers via allow_methods() and allow_headers()
  3. If cookies/auth headers are sent, call allow_credentials(true)
  4. Ensure the cors filter is applied to the route handling the preflight request
  5. For strict validation, verify the regex/allow list matches the origin string exactly (no trailing slash)

Example fix

// before
let cors = warp::cors().allow_methods(vec!["GET"]);
let routes = warp::get().and(warp::path("api")).with(cors);
// after
let cors = warp::cors()
    .allow_origin("http://localhost:3000")
    .allow_methods(vec!["GET", "POST"])
    .allow_headers(vec!["content-type", "authorization"])
    .allow_credentials(true);
let routes = warp::get()
    .and(warp::path("api"))
    .with(cors);
Defensive patterns

Strategy: fallback

Validate before calling

// client-side preflight check before calling the API
const origin = window.location.origin;
const allowed = ["http://localhost:3000", "https://app.example.com"];
if (!allowed.includes(origin)) console.warn("Origin will be rejected by CORS:", origin);

Type guard

function isCorsRejection(rej: unknown): rej is { status: number } {
  return typeof rej === "object" && rej !== null && "status" in rej && (rej as any).status === 403;
}

Try / catch

try {
  const res = await fetch("https://api.example.com/data");
} catch (e) {
  // browsers mask CORS rejections as opaque network errors
  console.error("Request blocked by CORS policy; check server allow_origin()");
}

Prevention

When it happens

Trigger: A browser preflight (OPTIONS) or actual request carries an Origin header while the route is wrapped with cors(), and: the origin is not in allow_origin(), the method is not in allow_methods(), a request header is not in allow_headers(), credentials are used without allow_credentials(true), or a strict regex list is mis-typed.

Common situations: Frontend on localhost:3000 calling a backend on localhost:8080 without adding that origin to allow_origin; deploying behind a proxy that changes the Host/Origin; adding a new HTTP method or custom header in the client but forgetting to update allow_methods()/allow_headers(); forgetting to mount the cors filter on the OPTIONS preflight path.

Understand the failure class

Related errors


AI-assisted analysis of seanmonstar/warp@ff34d7213e (2026-09-09). Data as JSON: /api/errors/8c317e7d52bdcf66. Report an issue: GitHub.

Appendix: source

Thrown at src/filters/cors.rs:513

            match validated {
                Ok(Validated::Preflight(origin)) => {
                    let preflight = Preflight {
                        config: self.config.clone(),
                        origin,
                    };
                    future::Either::Left(future::ok((Either::A((preflight,)),)))
                }
                Ok(Validated::Simple(origin)) => future::Either::Right(WrappedFuture {
                    inner: self.inner.filter(Internal),
                    wrapped: Some((self.config.clone(), origin)),
                }),
                Ok(Validated::NotCors) => future::Either::Right(WrappedFuture {
                    inner: self.inner.filter(Internal),
                    wrapped: None,
                }),
                Err(err) => {
                    let rejection = crate::reject::known(CorsForbidden { kind: err });
                    future::Either::Left(future::err(rejection.into()))
                }
            }
        }
    }

    #[derive(Debug)]
    pub struct Preflight {
        config: Arc<Configured>,
        origin: header::HeaderValue,
    }

    impl crate::reply::Reply for Preflight {
        fn into_response(self) -> crate::reply::Response {
            let mut res = crate::reply::Response::default();
            self.config.append_preflight_headers(res.headers_mut());
            res.headers_mut()
                .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, self.origin);
            res

View on GitHub (pinned to ff34d7213e)