CORS and Security in Hyperlane
Project Code:https://github.com/hyperlane-dev/hyperlane
Introduction
Cross-Origin Resource Sharing (CORS) is a fundamental security mechanism in modern web browsers. It controls which web applications can access resources from different origins, playing a critical role in preventing unauthorized cross-origin requests. When building APIs and web services with hyperlane, understanding and properly configuring CORS is essential for both functionality and security.
This article covers how to implement CORS in hyperlane applications, along with other security best practices that help protect your web services.
Understanding CORS
CORS is a browser-enforced mechanism that restricts web pages from making requests to a different domain than the one that served the page. Without proper CORS headers, browsers block cross-origin requests to protect users from potential security threats.
A "cross-origin" request occurs when the protocol, domain, or port of the request target differs from the current page. For example:
-
https://api.example.com→https://api.example.com/v1(same origin) -
https://example.com→https://api.example.com(cross-origin — different subdomain) -
http://example.com→https://example.com(cross-origin — different protocol) -
https://example.com:80→https://example.com:8080(cross-origin — different port)
Basic CORS Configuration in Hyperlane
Setting CORS Headers
Hyperlane makes it straightforward to configure CORS headers on your responses:
ctx.get_mut_response()
.set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
.set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
.set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);
This configuration:
-
ACCESS_CONTROL_ALLOW_ORIGIN— Specifies which origins are allowed.WILDCARD_ANY(*) allows all origins. -
ACCESS_CONTROL_ALLOW_METHODS— Lists the HTTP methods that are permitted.ALL_METHODSallows all standard methods. -
ACCESS_CONTROL_ALLOW_HEADERS— Specifies which request headers are allowed.WILDCARD_ANYpermits all headers.
CORS as Middleware
For consistent CORS configuration across all routes, implement it as middleware:
struct CorsMiddleware;
impl ServerHook for CorsMiddleware {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
ctx.get_mut_response()
.set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
.set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
.set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);
Status::Continue
}
}
server.request_middleware::<CorsMiddleware>();
By registering this middleware, every response from your server will include the appropriate CORS headers.
CORS Configuration Options
Restricting Allowed Origins
While WILDCARD_ANY is convenient for development and public APIs, production applications should restrict allowed origins:
struct RestrictedCorsMiddleware;
impl ServerHook for RestrictedCorsMiddleware {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let origin = ctx.get_request()
.try_get_header_back(ORIGIN)
.unwrap_or_default();
// Only allow specific origins
if origin == "https://example.com" || origin == "https://app.example.com" {
ctx.get_mut_response()
.set_header(ACCESS_CONTROL_ALLOW_ORIGIN, &origin);
}
ctx.get_mut_response()
.set_header(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE")
.set_header(ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Authorization");
Status::Continue
}
}
This approach validates the Origin header against a whitelist and only echoes back approved origins.
Restricting Allowed Methods
You can limit which HTTP methods are allowed for cross-origin requests:
ctx.get_mut_response()
.set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
.set_header(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST")
.set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);
This restricts cross-origin requests to only GET and POST methods.
Restricting Allowed Headers
Similarly, you can control which request headers are permitted:
ctx.get_mut_response()
.set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
.set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
.set_header(ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Authorization, X-Requested-With");
This explicitly lists the headers that the client is allowed to send in cross-origin requests.
Preflight Requests
Understanding Preflight
When a cross-origin request includes custom headers or uses methods other than GET, POST, or HEAD with certain content types, the browser sends a "preflight" OPTIONS request before the actual request. This preflight request asks the server for permission.
Handling Preflight Requests
You can handle preflight requests in your route handlers by checking for the OPTIONS method:
impl ServerHook for ApiHandler {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
// Handle preflight requests
if ctx.get_request().get_method() == &RequestMethod::Options {
ctx.get_mut_response()
.set_status_code(204)
.set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
.set_header(ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS")
.set_header(ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type, Authorization")
.set_header(ACCESS_CONTROL_MAX_AGE, "86400");
let data = ctx.get_mut_response().build();
stream.try_send(data).await;
return Status::Continue;
}
// Handle actual API request...
Status::Continue
}
}
The Access-Control-Max-Age header tells the browser how long (in seconds) the preflight response can be cached, reducing the number of preflight requests.
Security Best Practices
Authentication with CORS
When combining authentication with CORS, ensure that CORS headers are set even on authentication failure responses. This allows the client's browser to properly read the error:
struct SecureCorsAuthMiddleware;
impl ServerHook for SecureCorsAuthMiddleware {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
// Always set CORS headers first
ctx.get_mut_response()
.set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
.set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
.set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);
// Then perform authentication
let auth_str = ctx.get_request()
.try_get_header_back(AUTHORIZATION)
.unwrap_or_default();
if auth_str.is_empty() {
let data = ctx.get_mut_response()
.set_status_code(401)
.set_body("Unauthorized")
.build();
if stream.try_send(data).await.is_err() {
stream.set_closed(true);
}
return Status::Reject;
}
Status::Continue
}
}
Connection Management for Security
Proper connection management is an important security consideration. Always close connections when they are no longer needed:
stream.set_closed(true);
And respect the keep-alive settings from the client:
let keep_alive = stream.is_keep_alive(ctx.get_request().is_enable_keep_alive());
while stream.try_get_http_request().await.is_ok() {
if !ctx.get_request().is_enable_keep_alive() {
stream.set_closed(true);
break;
}
}
Input Validation
Always validate and sanitize incoming request data. Hyperlane provides safe methods for accessing request data:
let method = ctx.get_request().get_method();
let path = ctx.get_request().get_path();
let host = ctx.get_request().get_host();
let headers = ctx.get_request().get_headers();
let body = ctx.get_request().get_body_string();
let json_body: T = ctx.get_request().get_body_json::<T>();
let query = ctx.get_request().try_get_query("key");
Using try_get_* methods instead of get_* methods helps prevent panics from missing data.
Request Size Limits
Configure request size limits through RequestConfig to prevent denial-of-service attacks:
let request_config_json = r#"{
"buffer_size": 8192,
"max_path_size": 8192,
"max_header_count": 100,
"max_header_key_size": 8192,
"max_header_value_size": 8192,
"max_body_size": 2097152,
"read_timeout_ms": 6000
}"#;
let request_config = RequestConfig::from_json(request_config_json).unwrap();
let mut server: Server = Server::from(request_config);
Key security-related settings:
-
max_body_size— Limits the maximum request body size (2MB in this example). -
max_header_count— Restricts the number of headers per request. -
max_path_size— Limits the URL path length. -
read_timeout_ms— Sets a timeout for reading requests, preventing slow-loris attacks.
Timeout Protection
Implement timeout middleware to prevent resource exhaustion from long-running requests:
spawn(async move {
timeout(Duration::from_millis(100), async move {
new_ctx.get_mut_response().set_status_code(504).set_body("timeout");
}).await.unwrap();
});
CORS and Route Filters
Hyperlane's route filtering system can be used to apply different CORS policies to different routes:
#[host("api.example.com")]
struct ApiRoute;
#[reject_host("blocked.example.com")]
struct SafeRoute;
These filters run before the request reaches your handler, providing an additional layer of security.
Complete Secure Server Example
Here is a complete example showing a secure server configuration with CORS, authentication, and proper error handling:
#[tokio::main]
async fn main() {
let mut server: Server = Server::default();
// Register CORS middleware
server.request_middleware::<CorsMiddleware>();
// Register authentication middleware
server.request_middleware::<AuthMiddleware>();
// Register routes
server.route::<ApiHandler>("/api/data");
let server_control_hook = server.run().await.unwrap_or_default();
server_control_hook.wait().await;
}
Conclusion
CORS and security are essential considerations when building web applications with hyperlane. By properly configuring CORS headers, implementing authentication middleware, validating input, setting request size limits, and managing connections carefully, you can build secure and functional web services.
Remember that CORS is a browser-side security mechanism — it does not prevent server-side access to your API. Always implement proper authentication and authorization on the server, regardless of your CORS configuration. The combination of CORS, authentication middleware, input validation, and connection management provides a comprehensive security posture for your hyperlane applications.
Project Code:https://github.com/hyperlane-dev/hyperlane
Top comments (0)