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
- Add the browser origin (scheme + host + port, exactly) to cors().allow_origin([...])
- Allow the failing method and headers via allow_methods() and allow_headers()
- If cookies/auth headers are sent, call allow_credentials(true)
- Ensure the cors filter is applied to the route handling the preflight request
- 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
- List every frontend origin (scheme+host+port) in allow_origin during setup
- Enable allow_credentials(true) whenever cookies or Authorization headers cross origins
- Test OPTIONS preflight with curl before wiring the frontend
- Keep the cors filter applied to both actual and preflight routes
- Never rely on wildcard origins in production
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
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);
resView on GitHub (pinned to ff34d7213e)