@authhero/proxy

中文文档
0.11.13发布于 2026/11/05AGPL-3.0-onlyTypeScriptESM + CJS构建来源已验证近 30 天下载 1912

多租户 HTTP 逆向代理库 浩诺解决收到的请求 Host 给租户的页眉, 匹配 JSON 配置的路由列表( path / method / hosters / headers / 查询) , 并运行每个匹配的路由 treou

中文使用文档
文档目录16 节
  1. 安装
  2. 概念
  3. 客户端 IP 转发
  4. 快速启动( 静态配置)
  5. Cloudflare 服务绑定
  6. 向工人派遣平台
  7. 取代由上游来源的饼干和再定向
  8. 动态路线(数据库支持)
  9. 遥控平面( HTTP 适配器)
  10. 控制平面端点(服务器侧)
  11. KV 出版的读取复制品( 在光亮的 HTTP 适配器上推荐)
  12. 主机缓存
  13. 自定义处理器
  14. 路线计划( Zod)
  15. 出口
  16. 许可证

@authhero/ 代理服务器

多租户 HTTP 逆向代理库 浩诺解决收到的请求 Host 头到租户,与JSON配置的路由列表相匹配(路径/方法/主机/头头/查询),并通过命令的处理器链来运行每个匹配路由——中间软件风格的转换,然后是产生响应的终端处理器.

设计运行于Cloudflare Workers,Node.js, Bun,或任何Hono相容的运行时间. 这个软件包仅是数据平面 – 代理路由 CRUD lives in the authhero 核心资源(/api/v2/proxy-routes),而基础计划由每个数据库适配器运出(@authhero/kysely-adapter, (中文(简体) ), @authhero/drizzle, (中文(简体) ), @authhero/aws-adapter) (中文(简体) ).

安装

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

hono 财务报告和审定财务报表 @hono/zod-openapi 是同伴的依赖关系.

概念

A 类 路径 有两个部分:

  • match —— 说 { hosts?, methods?, path, headers?, query? }。路径使用Hono的模式语法(/api/*, (中文(简体) ), /users/:id) (中文(简体) ). hosts 接受准确或 *.example.com 通通证. headers 财务报告和审定财务报表 query 名称 \ regex.
  • handlers ——命令的处理器配置阵列. 每个操作员是 { type, options }编辑 最后一个 处理器是终端(它产生响应); 早期的处理器像Hono中间软件((c, next) => …) (中文(简体) ).

内置处理器 :

类型作用它会做什么
cors中间软件对选择方案预先飞行的答复;在答复中增加CORS标题.
basic_auth中间软件需要在 Authorization: Basic 头曰.
headers中间软件添加/删除请求和回复标题.
cache中间软件喷出 Cache-Control: public, max-age=N 如果还没有设置.
forwarded_headers中间软件设定 X-Forwarded-For, (中文(简体) ), X-Forwarded-Host, (中文(简体) ), X-Forwarded-Proto, (中文(简体) ), X-Real-IP, (中文(简体) ), X-Original-URL 从CF-Concting-IP。 自动准备到任何遗漏的链条.
rewrite_cookies中间软件重写上游 Set-Cookie: …; Domain= 到请求主机.
rewrite_location中间软件重写 Location 关于3xxx从上游来源对请求来源的答复的标题.
http终端转发到上游的 HTTP 通过 fetch。 。 。 .
service_binding终端转发到 Cloudflare 服务绑定(env.MY_API.fetch) (中文(简体) ).
dispatch_namespace终端向云旗工人派出平台命名空间(env.DISPATCHER.get(scriptName).fetch) (中文(简体) ). script_name 接受 {tenant_id}, (中文(简体) ), {custom_domain_id}, (中文(简体) ), {domain}, (中文(简体) ), {host} 占位符 .
redirect终端返回 a 301/302/307/308重新定向.
static终端返回一个静态身体(健康检查效果良好).

客户端 IP 转发

forwarded_headers 保证:代理机将它预先加到每个路由链上(和 defaultHandlers),这还没有宣布,所以存储在KV或控制平面中的路由表不能默默地放下访客IP. 处理器列表中任何位置的链条都完全按照配置.

处理器忽略一个 CF-Connecting-IP 属于Cloudflare自己的出版范围——当代理到达工人与工人之间时,信头会随身携带,盖上它就可以覆盖真正的客户端IP,一个已经录入的更早的跳出 X-Forwarded-For。在这种情况下,入境链保持不变;入境 X-Real-IP 永远不会被信任为一个倒置的背面,因为它是可客户端的。 选择退出 { type: "forwarded_headers", options: { skip_cloudflare_client_ip: false } }。 。 。 .

通过 a 注册自定义处理器 HandlerRegistry。 。 。 .

快速启动( 静态配置)

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 服务绑定

向服务机构(如内部API工人)发送信息,在工人登记簿上声明,并传递给 createProxyApp数字 :

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

然后一个路线可以使用:

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

向工人派遣平台

前往Cloudflare发送点名空间的普通工人的路线 脚本名称支持根据请求从已解决的主机替换的占位符 :

# 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(...)
    }},
] }

可用的占位符 : {tenant_id}, (中文(简体) ), {custom_domain_id}, (中文(简体) ), {domain}, (中文(简体) ), {host}。前三个来自已解决的东道主记录; {host} 是请求 Host 头曰.

取代由上游来源的饼干和再定向

遗产 Sesamy 代理重写 Set-Cookie: Domain=upstream.example.com 和 3xx 数据 Location: https://upstream.example.com/… 信头因此指向公共自定义域. 等同于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" } },
  ],
}

(你可以针) upstream_host 编号 : upstream_origin 很明显,如果你不想自动推论。 )

动态路线(数据库支持)

生产时,将路线储存在数据库中并按要求解决. AuthHero 适配器包装船 a createProxyDataAdapter(db) 返回满分的助手 ProxyDataAdapter (中文(简体) ) resolveHost与他们的计划接通:

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),
});

这个 proxy_routes 表格带有标准的 AuthHero 迁移——通过 migrateToLatest(db) (或等同的细雨/锯子阶)已足.

遥控平面( HTTP 适配器)

如果代理运行与AuthHero控制平面(不同的工人,不同的VPC,不同的区域)分开,则使用 createHttpProxyAdapter 以读取 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 },
});

HTTP 适配器交换 client_id + 键 client_secret 用于在 ${baseUrl}/oauth/token,然后打电话 GET ${baseUrl}/api/v2/proxy/control-plane/hosts/:host. 控制平面必须使用系统范围证书(与租户代币分开)来暴露这一端点.

控制平面端点(服务器侧)

在您的 AuthHero 服务器中,选择通过线接解析器并指向 JWKS, 标志控制飞机无记名标志。 认证在 认证持证人JWT(RS/ES algs, iss === env.ISSUER范围 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),
  },
});

HTTP 适配器通过双跳认证调用读取路由(token mint +) 解决),每一次缓存出错。 为了把跳跃从热道上移出,有 控制平面 发布 每个解析出的主机块到 Cloudflare KV 命名空间, 并拥有代理 读取 与一个单一的, 未经认证 KV.get编辑 control-plane DB 保留了真理的来源;KV是已出版的已读复制品.

代理侧 —— 说 createKvProxyAdapter,将HTTP适配器保留为 迁移中错失/误差倒计时 :

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

const kv = createKvProxyAdapter({ kv: env.PROXY_HOSTS, timeoutMs: 1000 });
const http = createHttpProxyAdapter({ baseUrl, clientId, clientSecret });

const upstream: ProxyDataAdapter = {
  proxyRoutes: kv.proxyRoutes, // read-only
  async resolveHost(host) {
    try {
      const hit = await kv.resolveHost(host);
      if (hit) return hit;
    } catch {
      /* KV slow/unavailable — fall through */
    }
    return http.resolveHost(host); // miss / error fallback
  },
};

const resolver = createCacheAdapterHostCache({
  upstream: createInMemoryHostCache(upstream, { freshTtlMs: 60_000 }),
  cache: createCloudflareCache({ cacheName: "authhero-proxy-hosts" }),
  freshTtlMs: 60 * 60_000,
  negativeTtlMs: 60_000,
  waitUntil: (p) => ctx.waitUntil(p),
});

createProxyApp({ data: upstream, resolver });

控制平面 ——将适配器打包一次 wrapProxyAdaptersWithKvPublish (从) authhero将包裹的一对传给 管理API 和 createApplySyncEvents,然后种子现有主机 与 backfillProxyHostsToKv。 KV 命名空间必须生活在 不变 云浮账户 作为代理工人。 全文见出版物 * 种子 * 使用指南 文档中: 代理 ~ 形状 3b。 。 。 .

主机缓存

resolveHost 每一个请求都要求。 用内置的 stale- while- revalidate 缓存包起来 :

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

云花岗工人,线条 ExecutionContext.waitUntil 与 AsyncLocalStorage 所以背景更新 幸存的反应.

自定义处理器

定义您的处理器类型 :

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 });

路线计划( Zod)

所有计划作为Zod计划再出口(proxyRouteSchema, (中文(简体) ), matchSchema, (中文(简体) ), handlerConfigSchema用于在您自己的管理 UI 中验证.

出口

  • createProxyApp(options) - 主Hono应用工厂
  • createProxyDataPlaneRouter 编号 : createProxyDataPlaneHandler - 数据平面管道
  • createStaticProxyAdapter 编号 : httpRoute ——静态配置助手
  • createHttpProxyAdapter - HTTP控制平面适配器
  • createInMemoryHostCache 编号 : createCacheAdapterHostCache ——缓存层(createCacheApiHostCache 也可以导出但为适应器包装而贬值)
  • HandlerRegistry 编号 : defineHandler 编号 : registerBuiltinHandlers + 每个内置处理器
  • compileHostApp, (中文(简体) ), sortRoutes, (中文(简体) ), matchesHost, (中文(简体) ), matchesAnyHost, (中文(简体) ), buildMatchFilter

数据库支持的适配器生活在 AuthHero 适配器包中: @authhero/kysely-adapter, (中文(简体) ), @authhero/drizzle, (中文(简体) ), @authhero/aws-adapter。 。 。 .

许可证

麻省理工学院

版本与发布记录

@authhero/proxy 共发布 63 个版本,首次发布于 2026/05/27;过去 12 个月发布了 63 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.11.13latest2026/11/052 天前
0.11.122026/11/043 天前
0.11.112026/10/309 天前
0.11.102026/10/2910 天前
0.11.92026/10/2612 天前
0.11.82026/10/2315 天前

关于 @authhero/proxy 的常见问题

@authhero/proxy 是什么?

多租户 HTTP 逆向代理库 浩诺解决收到的请求 Host 给租户的页眉, 匹配 JSON 配置的路由列表( path / method / hosters / headers / 查询) , 并运行每个匹配的路由 treou

如何安装 @authhero/proxy?

在项目目录运行 npm install @authhero/proxy,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:hono(^4.6.8)、@hono/zod-openapi(^1.4.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@authhero/proxy 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@authhero/proxy 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@authhero/proxy 可以商用吗?

@authhero/proxy 使用 AGPL-3.0-only 许可。最严格的 Copyleft:除分发外,通过网络向用户提供基于它的服务也需要开源对应源码。闭源 SaaS 使用前务必评估。以上为概括,不构成法律意见,请以许可原文为准。

@authhero/proxy 的下载量有多少?

根据 npm 官方下载统计,@authhero/proxy 在 2026/10/07 – 2026/11/05 这 30 天内被下载 1,912 次,日均约 64 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@authhero/proxy 还在维护吗?

最新版本 0.11.13 发布于 2026/11/05(2 天前);至今共发布 63 个版本,其中过去 12 个月发布了 63 个;首次发布于 2026/05/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。