DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

CORS-and-Security

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.comhttps://api.example.com/v1 (same origin)
  • https://example.comhttps://api.example.com (cross-origin — different subdomain)
  • http://example.comhttps://example.com (cross-origin — different protocol)
  • https://example.com:80https://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);
Enter fullscreen mode Exit fullscreen mode

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_METHODS allows all standard methods.
  • ACCESS_CONTROL_ALLOW_HEADERS — Specifies which request headers are allowed. WILDCARD_ANY permits 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>();
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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)