Cloudflare - v1.3.0
    Preparing search index...

    Class SecurityMiddleware

    The security middleware stack applied to the Hono app: CORS, an origin allowlist, client-IP capture, and API-key authentication.

    Which routes it protects, which stay public, and which origins may reach it from a browser all come from the configuration layer, so src/app.ts composes the app without restating any of it. Override in config/SecurityConfig.ts.

    Authentication fails closed: a missing server-side key yields 500, a mismatched client key 401, and neither reaches the route. Because the rejection short-circuits, anything that must observe rejected requests — request logging, telemetry — has to be registered ahead of SecurityMiddleware.apply.

    The blanket check is default-deny within its pattern: a route added later is protected until someone exempts it. Endpoints that cannot be addressed by a key — a third-party webhook, or a browser endpoint where an embedded key would be public anyway — are named in excludedRoutes and authenticate themselves instead. An empty urlPattern disables the blanket check altogether, leaving CORS and the origin allowlist as all this registers.

    Not to be confused with an application's config/SecurityConfig, which is the override that feeds this rather than a second implementation of it.

    Bayu Dwiyan Satria

    1.0.0

    1.0.0

    Index
    • Constructs a SecurityMiddleware instance.

      Parameters

      • Optionalsettings: SecuritySettings

        Settings to use. Omit to take them from the configuration surface, which is what callers normally do.

      • metadata: RequestMetadata<Request<unknown, CfProperties<unknown>>> = ...

        Reader for the runtime's account of a request. Defaults to the Cloudflare one, which is the only thing here that knows a platform — and the only argument that would change to run this middleware elsewhere.

      Returns SecurityMiddleware

      Settings are looked up on first use rather than here. An application composes its app at module scope — const security = new SecurityMiddleware() beside the routes — which runs while imports are still being hoisted, before configure() can. Resolving in this constructor would make importing the app throw.

    metadata: RequestMetadata<Request<unknown, CfProperties<unknown>>>

    Reader for the facts the runtime attaches to a request.

    settings: () => SecuritySettings

    This middleware's settings, resolved on first read.

    • Validates the API key, comparing the configured header against the expected value from the environment.

      Parameters

      • ctx: any

        The Hono context.

      • next: any

        Passes control to the next middleware.

      Returns Promise<any>

      A 500 response when the Worker has no key configured, a 401 when the client's key does not match, and nothing when the request is allowed through.

      The comparison goes through timingSafeEqual rather than !==, so the time taken does not reveal how many leading bytes of the key were correct. See that function for why the distinction matters.

    • Registers CORS, the origin allowlist, the public routes, and the authenticated pattern on an app.

      Parameters

      • app: Hono

        The Hono app to apply the middleware to.

      Returns void

      Order matters: public routes are registered before the authentication middleware, which is what keeps them reachable without a key.

      An empty urlPattern skips the authenticated pattern altogether.

    • Captures the client's IP address onto the context as clientIp.

      Parameters

      • ctx: any

        The Hono context.

      • next: any

        Passes control to the next middleware.

      Returns Promise<void>

      Which header may be believed is a platform question, so this does not answer it — CloudflareRequestMetadata does, and documents why CF-Connecting-IP can be trusted where X-Forwarded-For cannot. This middleware only puts the answer somewhere handlers can reach it.

      The address is put on the context rather than only logged, so a handler that needs it does not have to re-derive it and risk picking the wrong header:

      const ip = ctx.get('clientIp')
      

      It is not logged here. Every request already carries its IP in the request.completed line logToAnalytics emits, and a second line saying the same thing at debug was one more record to sample, pay for, and read past — for a value the next line already holds.

    • Whether a path is exempt from the API-key check.

      A trailing /* exempts a subtree, anything else exempts that one path — the same convention urlPattern and publicRoutes already use. Exact is the default because the two are genuinely different intents: a health check mounted at the pattern's own prefix must stay open without opening everything beneath it.

      Parameters

      • path: string

        The request path.

      Returns boolean

      True when the blanket check must not run.

    • Rejects a browser request from an origin this Worker does not serve.

      Parameters

      • ctx: any

        The Hono context.

      • next: any

        Passes control to the next middleware.

      Returns Promise<any>

      A 403 when the origin is not allowed, nothing otherwise.

      Runs ahead of everything else and, unlike CORS, actually refuses the request. CORS is enforced by the browser after the response is produced, so on its own it would let another site's page reach an endpoint and spend this Worker's upstream quota while merely being unable to read what came back.

      A missing Origin is allowed through: server-to-server callers send none, and they are the ones the API key exists for.