CORS
Cross-origin resource sharing (CORS) lets a browser call your API from a page served on another origin. Corvis enables it with app.enable_cors(...) and follows the semantics of Express's cors package: same options, same headers and same handling of preflight requests.
Enabling it
let mut app = CorvisFactory::create::<AppModule>().await?;
app.enable_cors(CorsOptions::default());CorsOptions::default() (same as CorsOptions::new()) allows any origin (Access-Control-Allow-Origin: *) with the methods GET, HEAD, PUT, PATCH, POST and DELETE.
You can also pass it when creating the application, in CorvisApplicationOptions:
use corvis::CorvisApplicationOptions;
let app = CorvisFactory::create_with_options::<AppModule>(CorvisApplicationOptions {
cors: Some(CorsOptions::new().origin("https://app.example.com")),
..Default::default()
})
.await?;Typical configuration
app.enable_cors(
CorsOptions::new()
.origins(["https://app.example.com", "http://localhost:5173"])
.methods(["GET", "HEAD", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"])
.allowed_headers(["Content-Type", "Authorization"])
.exposed_headers(["Content-Disposition"])
.credentials(true)
.max_age(86_400),
);| Method | Equivalent cors option | Effect |
|---|---|---|
.origin("https://a.com") | origin: 'https://a.com' | A fixed origin. Calling it twice builds a list; "*" is any origin |
.origins([...]) | origin: [...] | List of exact origins |
.origin_regex(Regex::new(...)?) | origin: /re/ | Adds a regular expression to the list (use your own dependency on the regex crate) |
.reflect_origin() | origin: true | Reflects the request's origin |
.origin_fn(|origin| ...) | origin: (origin, cb) => ... | Decides per request by returning a CorsOrigin |
.origin_value(CorsOrigin::Disabled) | origin: false | Does not send CORS headers |
.methods([...]) | methods | Access-Control-Allow-Methods |
.allowed_headers([...]) | allowedHeaders | Without a value, the requested headers are reflected |
.exposed_headers([...]) | exposedHeaders | Access-Control-Expose-Headers |
.credentials(true) | credentials | Access-Control-Allow-Credentials: true |
.max_age(seconds) | maxAge | Access-Control-Max-Age in the preflight |
.preflight_continue(true) | preflightContinue | Passes the OPTIONS to the application instead of answering it |
.options_success_status(200) | optionsSuccessStatus | Preflight status (204 by default) |
Dynamic origins
origin_fn receives the request's origin (if any) and returns a CorsOrigin:
use corvis::CorsOrigin;
app.enable_cors(
CorsOptions::new()
.origin_fn(|origin| match origin {
Some(o) if o.ends_with(".example.com") => CorsOrigin::Reflect,
_ => CorsOrigin::Disabled,
})
.credentials(true),
);The CorsOrigin variants are Any, Reflect, Disabled, Fixed(String), List(...) and Dynamic(...). Disabled omits the CORS headers for that request only.
Behavior
- Any
OPTIONSis treated as a preflight, even if it carries noAccess-Control-Request-Methodand even if the route does not exist:204is returned withContent-Length: 0and theAccess-Control-*headers. This happens before middleware and guards. The exceptions arepreflight_continue(true)and anorigin_fnthat returnsCorsOrigin::Disabledfor that request: in those cases theOPTIONScontinues to the application. Vary: Originis always sent when the configured origin is not*, also when the request's origin is not allowed (in that caseAccess-Control-Allow-Originis missing and the browser blocks the response).- CORS headers are also added to error responses, including those from the body parser.
- Headers set by the handler take priority over those from CORS.
credentials and open origins
With credentials(true) the browser sends cookies. Do not combine it with reflect_origin() or an origin_fn that accepts any origin: any site could make authenticated requests on behalf of the user. Use an explicit list.
WebSockets
enable_cors does not affect gateways. To limit the origins that can open a WebSocket use GatewayOptions::allowed_origins (responds 403 on the handshake). See Gateways.