npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@authhero/proxy

v0.7.3

Published

A multi-tenant HTTP reverse proxy library for [Hono](https://hono.dev). Resolves an incoming request's `Host` header to a tenant, matches against a JSON-configured list of routes (path / method / host / headers / query), and runs each matching route throu

Readme

@authhero/proxy

A multi-tenant HTTP reverse proxy library for Hono. Resolves an incoming request's Host header to a tenant, matches against a JSON-configured list of routes (path / method / host / headers / query), and runs each matching route through an ordered chain of handlers — middleware-style transformations followed by a terminal handler that produces the response.

Designed to run on Cloudflare Workers, Node.js, Bun, or any Hono-compatible runtime. This package is data-plane only — proxy route CRUD lives in the authhero core (/api/v2/proxy-routes), and the underlying schema is shipped by each database adapter (@authhero/kysely-adapter, @authhero/drizzle, @authhero/aws-adapter).

Install

pnpm add @authhero/proxy hono @hono/zod-openapi

hono and @hono/zod-openapi are peer dependencies.

Concepts

A route has two parts:

  • match{ hosts?, methods?, path, headers?, query? }. Path uses Hono's pattern syntax (/api/*, /users/:id). hosts accepts exact or *.example.com wildcards. headers and query are name → regex.
  • handlers — an ordered array of handler configs. Each handler is { type, options }. The last handler is the terminal (it produces the response); earlier handlers wrap it like Hono middleware ((c, next) => …).

Built-in handlers:

| Type | Role | What it does | | --- | --- | --- | | cors | middleware | Replies to OPTIONS preflight; adds CORS headers to the response. | | basic_auth | middleware | Requires a username/password in the Authorization: Basic header. | | headers | middleware | Adds/removes request and response headers. | | cache | middleware | Injects Cache-Control: public, max-age=N if not already set. | | forwarded_headers | middleware | Sets X-Forwarded-Host, X-Forwarded-Proto, X-Real-IP, X-Original-URL from CF-Connecting-IP. | | rewrite_cookies | middleware | Rewrites upstream Set-Cookie: …; Domain= to the request host. | | rewrite_location | middleware | Rewrites the Location header on 3xx responses from upstream origin to request origin. | | http | terminal | Forwards to an HTTP upstream via fetch. | | service_binding | terminal | Forwards to a Cloudflare service binding (env.MY_API.fetch). | | dispatch_namespace | terminal | Dispatches to a Cloudflare Workers for Platforms namespace (env.DISPATCHER.get(scriptName).fetch). script_name accepts {tenant_id}, {custom_domain_id}, {domain}, {host} placeholders. | | redirect | terminal | Returns a 301/302/307/308 redirect. | | static | terminal | Returns a static body (great for healthchecks). |

Register custom handlers via a HandlerRegistry.

Quick start (static config)

import { createProxyApp, createStaticProxyAdapter, httpRoute } from "@authhero/proxy";

const data = createStaticProxyAdapter({
  hosts: {
    "acme.example.com": {
      tenant_id: "acme",
      routes: [
        httpRoute("https://acme.vercel.app"),                     // sugar for { match: {path:"/*"}, handlers: [{type:"http",...}] }
        {
          match: { path: "/healthz" },
          handlers: [
            { type: "static", options: { status: 200, json: { ok: true } } },
          ],
        },
        {
          match: { path: "/api/*" },
          handlers: [
            { type: "cors", options: { origins: ["https://app.example.com"] } },
            { type: "http", options: { upstream_url: "https://api.acme.vercel.app" } },
          ],
        },
      ],
    },
  },
});

const app = createProxyApp({ data });
export default app;

Cloudflare service bindings

To dispatch to a service binding (e.g. an internal API Worker), declare it in your Worker entry and pass it to createProxyApp:

export default {
  fetch(req: Request, env: Env, ctx: ExecutionContext) {
    const app = createProxyApp({
      data: ...,
      bindings: { API2: env.API2 },
    });
    return app.fetch(req);
  },
};

Then a route can use:

{ match: { hosts: ["api.example.com"] }, handlers: [
    { type: "forwarded_headers", options: {} },
    { type: "service_binding", options: { binding: "API2" } },
] }

Dispatching to Workers for Platforms

Route to a per-tenant worker deployed into a Cloudflare dispatch namespace. The script name supports placeholders that are substituted at request time from the resolved host:

# wrangler.toml
[[dispatch_namespaces]]
binding = "DISPATCHER"
namespace = "authhero-tenants"
// One catch-all route per host dispatches to that tenant's worker.
{ match: { path: "/*" }, handlers: [
    { type: "dispatch_namespace", options: {
        binding: "DISPATCHER",
        script_name: "tenant-{tenant_id}-auth",  // e.g. "tenant-acme-auth"
        // Optional: cpu_ms, subrequests — forwarded to dispatcher.get(...)
    }},
] }

Available placeholders: {tenant_id}, {custom_domain_id}, {domain}, {host}. The first three come from the resolved host record; {host} is the request Host header.

Replacing the upstream-origin in cookies and redirects

The legacy Sesamy proxy rewrote Set-Cookie: Domain=upstream.example.com and 3xx Location: https://upstream.example.com/… headers so they pointed at the public custom domain. Equivalent with v2:

{
  match: { path: "/*" },
  handlers: [
    { type: "forwarded_headers", options: {} },
    { type: "rewrite_cookies", options: {} },   // infers upstream host from the http/service_binding handler
    { type: "rewrite_location", options: {} },  // ditto
    { type: "http", options: { upstream_url: "https://upstream.example.com" } },
  ],
}

(You can pin upstream_host / upstream_origin explicitly if you don't want the auto-inference.)

Dynamic routes (database-backed)

For production, store routes in a database and resolve them per request. AuthHero adapter packages ship a createProxyDataAdapter(db) helper that returns a full ProxyDataAdapter (CRUD + resolveHost) wired to their schema:

import { Kysely } from "kysely";
import { createProxyApp } from "@authhero/proxy";
import { createProxyDataAdapter } from "@authhero/kysely-adapter";

const db = new Kysely({ /* dialect */ });

const app = createProxyApp({
  data: createProxyDataAdapter(db),
});

The proxy_routes table comes with the standard AuthHero migrations — running them via migrateToLatest(db) (or the equivalent drizzle/aws step) is enough.

Remote control plane (HTTP adapter)

If the proxy runs separately from the AuthHero control plane (different worker, different VPC, different region), use createHttpProxyAdapter to read route config over HTTP:

import {
  createProxyApp,
  createHttpProxyAdapter,
  createCacheAdapterHostCache,
  createInMemoryHostCache,
} from "@authhero/proxy";
import { createCloudflareCache } from "@authhero/cloudflare-adapter";

const httpAdapter = createHttpProxyAdapter({
  baseUrl: "https://auth.example.com",
  clientId: env.PROXY_CLIENT_ID,
  clientSecret: env.PROXY_CLIENT_SECRET,
});

// Three-tier cache: in-memory (per-isolate) → Cloudflare Cache API (per-colo) → control plane.
const inMemory = createInMemoryHostCache(httpAdapter, { freshTtlMs: 60_000, staleTtlMs: 600_000 });
const resolver = createCacheAdapterHostCache({
  upstream: inMemory,
  cache: createCloudflareCache({ cacheName: "authhero-proxy-hosts" }),
  freshTtlMs: 60 * 60_000,           // 1 hour fresh
  staleTtlMs: 23 * 60 * 60_000,      // SWR for 23 more hours (24h total)
  waitUntil: (p) => ctx.waitUntil(p),
});

const app = createProxyApp({
  data: httpAdapter,
  resolver,                                  // share across requests
  bindings: { API2: env.API2 },
});

The HTTP adapter exchanges client_id + client_secret for an access token at ${baseUrl}/oauth/token, then calls GET ${baseUrl}/api/v2/proxy/control-plane/hosts/:host. The control plane must expose this endpoint with a system-scoped credential (separate from tenant tokens).

Control-plane endpoint (server side)

In your AuthHero server, opt in by wiring the resolver and pointing at the JWKS that signs control-plane bearer tokens. Authentication is built in — authhero verifies the bearer JWT (RS/ES algs, iss === env.ISSUER, scope proxy:resolve_host):

import { init } from "authhero";
import { createProxyDataAdapter } from "@authhero/kysely-adapter";

const proxyData = createProxyDataAdapter(db);

const { app } = init({
  dataAdapter,
  proxyControlPlane: {
    resolveHost: proxyData.resolveHost,
    jwksUrl: `${env.ISSUER}/.well-known/jwks.json`,
    // On Workers, route through a service binding to keep the request
    // off the public network:
    // jwksFetch: (url) => env.JWKS_SERVICE.fetch(url),
  },
});

Host cache

resolveHost is called on every request. Wrap it with the built-in stale-while-revalidate cache:

const app = createProxyApp({
  data,
  cache: {
    freshTtlMs: 5 * 60_000,
    staleTtlMs: 60 * 60_000,
    negativeTtlMs: 30_000,
    waitUntil: (p) => ctx.waitUntil(p),
  },
});

On Cloudflare Workers, thread ExecutionContext.waitUntil through with AsyncLocalStorage so background refreshes survive the response.

Custom handlers

Define your own handler type:

import { z } from "@hono/zod-openapi";
import { HandlerRegistry, defineHandler, registerBuiltinHandlers, createProxyApp } from "@authhero/proxy";

const myHandler = defineHandler<{ secret: string }>({
  type: "hmac_verify",
  optionsSchema: z.object({ secret: z.string() }),
  build(options) {
    return async (c, next) => {
      const sig = c.req.header("x-signature");
      if (!sig || !await verify(sig, options.secret, c.req.raw)) {
        return new Response("Bad signature", { status: 401 });
      }
      await next();
    };
  },
});

const registry = new HandlerRegistry();
registerBuiltinHandlers(registry);
registry.add(myHandler);

const app = createProxyApp({ data, registry });

Route schema (zod)

All schemas are re-exported as Zod schemas (proxyRouteSchema, matchSchema, handlerConfigSchema) for validation in your own management UIs.

Exports

  • createProxyApp(options) — main Hono app factory
  • createProxyDataPlaneRouter / createProxyDataPlaneHandler — data plane plumbing
  • createStaticProxyAdapter / httpRoute — static config helpers
  • createHttpProxyAdapter — HTTP control-plane adapter
  • createInMemoryHostCache / createCacheAdapterHostCache — cache layers (createCacheApiHostCache is also exported but deprecated in favor of the adapter-based wrapper)
  • HandlerRegistry / defineHandler / registerBuiltinHandlers + each builtin handler
  • compileHostApp, sortRoutes, matchesHost, matchesAnyHost, buildMatchFilter

Database-backed adapters live in the AuthHero adapter packages: @authhero/kysely-adapter, @authhero/drizzle, @authhero/aws-adapter.

License

MIT