DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Authentication-Middleware

Authentication Middleware in Hyperlane

Project Code:https://github.com/hyperlane-dev/hyperlane

Introduction

Authentication is a critical security concern for web applications. Whether you are building a REST API, a web application, or a microservice, controlling who can access your resources is paramount. In the hyperlane framework, authentication is typically implemented as middleware — reusable components that intercept requests before they reach your route handlers.

This article explores various authentication patterns you can implement in hyperlane, from simple header-based checks to more sophisticated token validation schemes.

Understanding Middleware in Hyperlane

Before diving into authentication, it helps to understand how middleware works in hyperlane. Middleware components implement the ServerHook trait, which provides two key methods:

  • new() — Called when a new connection is established. Used for initialization.
  • handle() — Called for each request on the connection. This is where authentication logic lives.

The handle() method returns a Status enum value:

  • Status::Continue — Allow the request to proceed to the next handler.
  • Status::Reject — Reject the request and stop further processing.

Basic Authentication Middleware

Header-Based Authentication

The simplest form of authentication checks for the presence of an Authorization header. Here is the foundational authentication middleware from hyperlane's documentation:

impl ServerHook for AuthMiddleware {
    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        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

This middleware:

  1. Reads the Authorization header from the incoming request.
  2. If the header is missing or empty, it responds with a 401 Unauthorized status and rejects the request.
  3. If the header exists, it allows the request to continue.

Using try_get_header_back

The try_get_header_back method is used here because the Authorization header is typically added by the client (the "back" direction from the server's perspective). This method returns an Option<String>, and unwrap_or_default() converts it to an empty string if the header is absent.

Error Handling in Authentication

Notice the careful error handling when sending the rejection response:

if stream.try_send(data).await.is_err() {
    stream.set_closed(true);
}
Enter fullscreen mode Exit fullscreen mode

If the send fails (e.g., because the client has already disconnected), the stream is marked as closed to prevent further attempts to write to it.

Bearer Token Authentication

Validating Bearer Tokens

A common authentication pattern is Bearer token authentication, where the Authorization header contains a token prefixed with "Bearer":

struct BearerAuthMiddleware;

impl ServerHook for BearerAuthMiddleware {
    async fn new(_: &mut Stream, _: &mut Context) -> Self {
        Self
    }

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        let auth_str = ctx.get_request()
            .try_get_header_back(AUTHORIZATION)
            .unwrap_or_default();

        if !auth_str.starts_with("Bearer ") {
            let data = ctx.get_mut_response()
                .set_status_code(401)
                .set_body("Unauthorized: Invalid token format")
                .build();

            if stream.try_send(data).await.is_err() {
                stream.set_closed(true);
            }

            return Status::Reject;
        }

        let token = &auth_str[7..]; // Extract token after "Bearer "

        if token.is_empty() {
            let data = ctx.get_mut_response()
                .set_status_code(401)
                .set_body("Unauthorized: Empty token")
                .build();

            if stream.try_send(data).await.is_err() {
                stream.set_closed(true);
            }

            return Status::Reject;
        }

        // Store the token in context attributes for downstream handlers
        ctx.set_attribute("auth_token", token);

        Status::Continue
    }
}

server.request_middleware::<BearerAuthMiddleware>();
Enter fullscreen mode Exit fullscreen mode

This enhanced middleware:

  1. Checks that the Authorization header starts with "Bearer ".
  2. Extracts the actual token value.
  3. Stores the token in the context attributes so downstream handlers can access it.
  4. Returns appropriate error messages for different failure scenarios.

Method-Based Authentication

Sometimes you only want to allow specific HTTP methods. Hyperlane's attribute macros make this straightforward:

#[methods("GET", "POST")]
struct MethodRestrictedRoute;

impl ServerHook for MethodRestrictedRoute {
    async fn new(_: &mut Stream, _: &mut Context) -> Self {
        Self
    }

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        // Only GET and POST requests reach here
        // Process the request...
        Status::Continue
    }
}
Enter fullscreen mode Exit fullscreen mode

The #[methods] attribute automatically rejects requests that don't match the specified HTTP methods, before the handle() method is even called.

Route-Level Authentication

Using Route Filters

Hyperlane's routing system supports attribute-based filtering, which can be used for authentication and access control:

#[host("api.example.com")]
struct ApiRoute;

#[reject_host("blocked.example.com")]
struct SafeRoute;

#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
struct GetOnlyRoute;

#[reject(ctx.get_request().get_path().len() > 1000)]
struct SafePathRoute;
Enter fullscreen mode Exit fullscreen mode

These filters provide different levels of access control:

  • #[host] — Only allow requests to a specific host.
  • #[reject_host] — Block requests from specific hosts.
  • #[filter] — Apply a custom filter expression.
  • #[reject] — Reject requests matching a condition.

Referer-Based Filtering

You can also filter based on the Referer header:

#[referer("https://example.com")]
#[reject_referer("https://malicious.com")]
struct RefererFilteredRoute;
Enter fullscreen mode Exit fullscreen mode

This is useful for preventing hotlinking or blocking requests from known malicious domains.

Combining Authentication with Other Middleware

Authentication and CORS

When building APIs that serve cross-origin requests, authentication middleware must work alongside CORS middleware. The CORS headers should be set regardless of whether authentication succeeds:

struct CorsAuthMiddleware;

impl ServerHook for CorsAuthMiddleware {
    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

Multiple Middleware with Priority

Hyperlane allows you to register multiple middleware with different priorities using the attribute macro system:

#[request_middleware(1)]
struct RequestMiddleware1;

#[request_middleware(2)]
struct RequestMiddleware2;
Enter fullscreen mode Exit fullscreen mode

Lower numbers execute first. This lets you control the order in which authentication and other middleware run.

Authentication Using Context Attributes

Hyperlane's context attribute system allows authentication data to flow between middleware and route handlers:

impl ServerHook for AuthMiddleware {
    async fn new(_: &mut Stream, ctx: &mut Context) -> Self {
        Self
    }

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        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;
        }

        ctx.set_attribute("authenticated", "true");
        ctx.set_attribute("auth_token", &auth_str);

        Status::Continue
    }
}
Enter fullscreen mode Exit fullscreen mode

Then in your route handler:

impl ServerHook for ProtectedRoute {
    async fn new(_: &mut Stream, ctx: &mut Context) -> Self {
        Self
    }

    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        let is_authenticated: Option<String> = ctx.try_get_attribute("authenticated");

        if is_authenticated.is_some() {
            // User is authenticated — serve the protected resource
            ctx.get_mut_response()
                .set_status_code(200)
                .set_body("Protected content");
        } else {
            ctx.get_mut_response()
                .set_status_code(403)
                .set_body("Forbidden");
        }

        let data = ctx.get_mut_response().build();
        stream.try_send(data).await;

        Status::Continue
    }
}
Enter fullscreen mode Exit fullscreen mode

Registering Authentication Middleware

Authentication middleware is registered with the server using the request_middleware method:

server.request_middleware::<AuthMiddleware>();
Enter fullscreen mode Exit fullscreen mode

For attribute macro-based middleware:

#[request_middleware(1)]
struct AuthMiddleware;
Enter fullscreen mode Exit fullscreen mode

The middleware runs for every request that reaches it, making it an ideal place to enforce authentication policies.

Best Practices for Authentication Middleware

  1. Fail fast. Reject unauthenticated requests as early as possible in the middleware chain to avoid unnecessary processing.

  2. Provide clear error messages. Tell the client why their request was rejected (e.g., "Missing token", "Invalid token format", "Token expired").

  3. Use context attributes for token propagation. Store validated token data in context attributes so downstream handlers don't need to re-parse the Authorization header.

  4. Set CORS headers before authentication. For APIs, ensure CORS headers are present even on error responses so the client's browser can read the error.

  5. Handle stream errors gracefully. Always check the result of try_send and close the stream if it fails.

  6. Layer your middleware. Use priority numbers to control the order of middleware execution. CORS middleware should typically run before authentication middleware.

  7. Don't store sensitive data in cookies for API authentication. Use the Authorization header for APIs and reserve cookies for browser-based sessions.

Conclusion

Hyperlane provides a flexible and powerful middleware system for implementing authentication in your web applications. From simple header checks to complex token validation, the ServerHook trait gives you full control over the authentication process. By combining authentication middleware with context attributes, route filters, and other middleware components, you can build secure and maintainable access control systems.

The key is to leverage hyperlane's middleware pipeline — registering authentication middleware with appropriate priority levels, using context attributes to propagate authentication state, and handling errors gracefully throughout the process.


Project Code:https://github.com/hyperlane-dev/hyperlane

Top comments (0)