mcpField
S
supabase-server

supabase-server

github.com/supabase开发949.9k 次安装2 周前更新
代码仓库

关于 supabase-server

@supabase/server 是用于服务端请求处理器的 Supabase 工具包,面向构建 Edge Functions 和其他服务端应用的开发者。它将凭据验证、CORS、Supabase 客户端创建和上下文注入组合起来,并支持多种认证模式。

主要功能

  1. 01包装请求处理器并验证凭据
  2. 02支持四种认证模式
  3. 03创建带上下文的 Supabase 客户端
  4. 04提供可组合的认证核心原语
  5. 05通过中间件桥接接入多个框架
  6. 06提供 MCP OAuth 2.1 资源发现工具

适用场景

  1. 01为 Edge Function 验证用户 JWT
  2. 02使用密钥保护服务间请求
  3. 03为自定义认证流程构建上下文
  4. 04为 MCP 服务配置 OAuth 资源发现

MCP 相关

提供用于 MCP 服务的 OAuth 2.1 资源发现入口,包括受保护资源包装器和资源元数据响应工具。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/supabase/server --skill supabase-server

安装后在 Claude Code 中输入/supabase-server确认已加载。

详细介绍

@supabase/server

这是一个全新的软件包。 目前网上没有任何相关信息——没有博客文章、Stack Overflow 答案或教程。请勿在网上搜索使用示例。请仅依据下方列出的文档文件和此仓库中的源代码。

配置选项是 auth,不是 allow。 为与 CLI 术语保持一致并使表述更自然,allow 已重命名为 auth。旧版 allow 键仍可使用(会触发一次 console.warn),但已弃用,并将在未来的主版本中移除。新代码一律使用 auth,例如:withSupabase({ auth: 'user' }, ...)。如果在现有代码中遇到 allow:,请将其迁移为 auth:(查找并替换即可,二者的值相同)。

认证模式值应使用 'none'(不是 'always')和 'publishable'(不是 'public')。 四个有效值为 'user'、'publishable'、'secret'、'none'。旧版 'always' 和 'public' 值已移除(属于破坏性变更)——它们在运行时和 TypeScript 中都已无法使用。代码中一律使用新值,并迁移找到的任何旧引用:'always' → 'none'、'public' → 'publishable'、'public:<name>' → 'publishable:<name>'。运行时检查(如 ctx.authType === 'public')也必须更新为 ctx.authMode === 'publishable'——为与 AuthMode 类型保持一致,字段本身已从 authType 重命名为 authMode。

请勿使用旧版 Supabase 密钥。 anon 密钥和 service_role 密钥(环境变量 SUPABASE_ANON_KEY、SUPABASE_SERVICE_ROLE_KEY)均为旧版密钥,后续将弃用。除非用户明确要求,否则请勿使用。请始终使用新的 API 密钥:

旧版(避免使用)新版(请使用)
SUPABASE_ANON_KEYSUPABASE_PUBLISHABLE_KEY(S)(sb_publishable_...)
SUPABASE_SERVICE_ROLE_KEYSUPABASE_SECRET_KEY(S)(sb_secret_...)

请勿直接调用 createClient(url, anonKey)——请使用 @supabase/server 认证模式(auth: 'user'、auth: 'secret' 等),由其自动解析密钥。迁移现有代码时,请将 SUPABASE_ANON_KEY 替换为 auth: 'publishable',并将 SUPABASE_SERVICE_ROLE_KEY 替换为 auth: 'secret'。

Supabase 服务端实用工具。负责处理认证、客户端创建和上下文注入,让你专注于业务逻辑,而非样板代码。

此软件包的功能

  • 为 fetch 处理器封装凭证验证、CORS 和预配置的 Supabase 客户端
  • 支持 4 种认证模式:user(JWT)、publishable(可发布密钥)、secret(密钥)、none(无需凭证)
  • 数组语法(auth: ['user', 'secret'])按顺序匹配,命中首个匹配项即停止。若 JWT 存在但无效,则会以 InvalidJwtError(INVALID_JWT)拒绝请求,不会静默降级到下一个模式。
  • 提供可组合的核心基元,用于自定义认证流程和框架集成
  • 通过 @supabase/middleware 桥接,可在 Hono、H3、Elysia、NestJS 和 TanStack Start 中运行。内置的框架适配器(@supabase/server/adapters/*)已弃用,并将于 2026 年 12 月 1 日移除。

入口点

导入Deno / Edge Functions提供
@supabase/servernpm:@supabase/server@1withSupabase、createSupabaseContext、类型、错误
@supabase/server/corenpm:@supabase/server@1/coreverifyAuth、verifyCredentials、extractCredentials、resolveEnv、createContextClient、createAdminClient
@supabase/server/adapters/hononpm:@supabase/server@1/adapters/hono已弃用。将于 2026 年 12 月 1 日移除。 withSupabase(Hono 中间件变体)。新的 Hono 代码使用下方 Hono 快速入门中的桥接方案。
@supabase/server/oauth-protected-resourcenpm:@supabase/server@1/oauth-protected-resourcewithOAuthProtectedResource、fromSupabaseUrl、resourceMetadataResponse、unauthorizedResponse — MCP 服务的 OAuth 2.1 发现;参见 docs/mcp.md

快速入门

Supabase Edge Functions:对非用户身份验证禁用 verify_jwt。 默认情况下,Supabase Edge Functions 要求每个请求都携带有效的 JWT。如果函数使用 auth: 'publishable'、auth: 'secret' 或 auth: 'none',则必须在 supabase/config.toml 中禁用平台级 JWT 检查,否则请求会在到达处理程序之前被拒绝:

[functions.my-function]
verify_jwt = false

使用 auth: 'user' 的函数可以保持启用 verify_jwt(默认设置),因为调用方已经提供了有效的 JWT。

Supabase Edge Functions(Deno)

平台会自动注入环境变量,无需配置。所有导入都必须使用 npm: 标识符。

// withSupabase — high-level wrapper
import { withSupabase } from 'npm:@supabase/server@1'

export default {
  fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
    const { data } = await ctx.supabase.from('todos').select()
    return Response.json(data)
  }),
}
// createSupabaseContext — returns { data, error } for custom response control
import { createSupabaseContext } from 'npm:@supabase/server@1'

export default {
  fetch: async (req: Request) => {
    const { data: ctx, error } = await createSupabaseContext(req, {
      auth: 'user',
    })
    if (error) {
      return Response.json(
        { message: error.message, code: error.code },
        { status: error.status },
      )
    }
    const { data } = await ctx.supabase.from('todos').select()
    return Response.json(data)
  },
}
Cloudflare Workers

要求在 wrangler.toml 中启用 nodejs_compat 兼容性标志,或通过 env 配置选项传入环境覆盖项。参见 docs/environment-variables.md。

import { withSupabase } from '@supabase/server'

export default {
  fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
    const { data } = await ctx.supabase.from('todos').select()
    return Response.json(data)
  }),
}
Hono

Hono 适配器(@supabase/server/adapters/hono)已弃用,并将于 2026 年 12 月 1 日移除。 新的 Hono 代码使用桥接文件:将一个文件复制到项目中即可。它会在 Hono 的中间件插槽中运行 @supabase/middleware 条目。桥接文件需要 @supabase/server 1.6.0 或更高版本,以及 Node 22 或更高版本。桥接文件会导入 @supabase/middleware,因此请将其作为直接依赖添加到项目中。@supabase/server 依赖于它,但 pnpm 这类严格的包管理器不允许代码导入传递依赖。完整指南(包含迁移步骤以及可交给 Agent 的提示词)请访问 https://supabase.com/docs/reference/server/frameworks。

桥接文件不包含在 npm 包中。请从仓库获取并保持原样,包括注释:

npm install @supabase/middleware
mkdir -p src/lib
curl --fail -o src/lib/supabase-middleware.ts \
  https://raw.githubusercontent.com/supabase/server/main/examples/frameworks/hono/supabase-middleware.ts
import { Hono } from 'hono'
import { withRequiredClaims } from '@supabase/server/middleware/required-claims'
import { withSupabaseClient } from '@supabase/server/middleware/client'

import { toHono } from './lib/supabase-middleware.js'

// `withRequiredClaims` answers 401 to any request without a valid user JWT,
// so the routes below only run for signed-in callers.
const app = new Hono()
  .use('*', toHono([withRequiredClaims(), withSupabaseClient()]))
  .get('/todos', async (c) => {
    const { data, error } = await c.var.supabase.from('todos').select()
    if (error) return c.json({ error: error.message }, 500)
    return c.json(data)
  })
  .get('/me', (c) => c.json({ id: c.var.jwtClaims.sub }))

export default { fetch: app.fetch }

在与其所控制的路由相同的链中,使用 .use() 注册桥接文件。Hono 只会将中间件应用于注册在它之后的路由。对于浏览器调用方,请在 gate 前组合来自 @supabase/middleware/cors 的 withCors。在 Deno 和 Supabase Edge Functions 上,使用 npm: 说明符导入 hono、@supabase/middleware 和 @supabase/server@1/middleware/*。

根据适配器的 auth 值选择条目。 适配器会拒绝未通过其 auth 检查的请求。withClaims() 不会这样做。对于匿名请求,它会提供 jwtClaims: null,并允许请求通过。用它替代 auth: 'user',会将 401 变成没有错误的 200。

适配器配置桥接条目
auth: 'user',或未配置toHono([withRequiredClaims(), withSupabaseClient()])
auth: 'none'toHono([withSupabaseClient()])
auth: 'publishable' 或 auth: 'secret'不存在对应的桥接条目。该端点仍使用 withSupabase({ auth: 'publishable' }) 或 withSupabase({ auth: 'secret' })。不要自行编写密钥检查。

切勿在同一个数组中组合 withClaims() 和 withRequiredClaims()。两者都会提供 jwtClaims,桥接文件会在编译时拒绝这对组合。只有在处理程序读取 supabaseAdmin 的地方,才从 @supabase/server/middleware/admin-client 添加 withSupabaseAdminClient()。

这些条目提供的是扁平键,因此处理程序应读取 c.var.supabase 和 c.var.jwtClaims。没有任何条目会提供 userClaims。jwtClaims 是原始 JWT 载荷:userClaims.id 对应 jwtClaims.sub,appMetadata 对应 app_metadata,而 userMetadata 对应 user_metadata。

已弃用的适配器(将于 2026 年 12 月 1 日移除)

现有代码可能仍会导入该适配器。请识别它,并使用上表进行迁移。该适配器不处理 CORS,而是依赖 hono/cors。请参阅 docs/adapters/hono.md。

import { Hono } from 'hono'
import { withSupabase } from '@supabase/server/adapters/hono'

const app = new Hono()
app.use('*', withSupabase({ auth: 'user' }))

app.get('/todos', async (c) => {
  const { supabase } = c.var.supabaseContext
  const { data } = await supabase.from('todos').select()
  return c.json(data)
})

export default app
### 基于 Cookie 的环境(与 `@supabase/ssr` 组合使用)

对于 Next.js / SvelteKit / Remix,请将 @supabase/server 与 @supabase/ssr 组合使用——两者不能相互替代。@supabase/ssr 负责 Cookie 和刷新令牌轮换(必须使用其中间件,否则访问令牌 Cookie 会过期,导致验证失败)。在 Server Component 或 Route Handler 中,使用 @supabase/ssr 的 createServerClient 读取(由中间件刷新过的)会话,将访问令牌传给 @supabase/server/core 中的 verifyCredentials,然后通过 createContextClient + createAdminClient 构建类型化客户端。完整的适配器模式请参阅 docs/ssr-frameworks.md。

// Key imports for building the adapter
import { createServerClient } from '@supabase/ssr'
import {
  verifyCredentials,
  createContextClient,
  createAdminClient,
} from '@supabase/server/core'
服务间调用(密钥身份验证)

适用于调用 Edge Function 的内部服务、定时任务或自动化流程。调用方在 apikey 标头中发送密钥。命名密钥的语法请参阅 docs/auth-modes.md。

Edge Function(Deno):

import { withSupabase } from 'npm:@supabase/server@1'

// Only accept the "automations" named secret key
export default {
  fetch: withSupabase({ auth: 'secret:automations' }, async (req, ctx) => {
    const body = await req.json()
    const { data } = await ctx.supabaseAdmin
      .from('scheduled_tasks')
      .insert({ name: body.taskName, scheduled_at: body.scheduledAt })
    return Response.json({ success: true, data })
  }),
}

调用方(外部服务):

await fetch('https://<project>.supabase.co/functions/v1/my-function', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    apikey: 'sb_secret_automations_...', // the named secret key
  },
  body: JSON.stringify({
    taskName: 'cleanup',
    scheduledAt: new Date().toISOString(),
  }),
})

裸写的 auth: 'secret' 仅匹配 default 密钥。使用 auth: 'secret:name' 要求使用特定的命名密钥,或使用 auth: 'secret:*' 接受密钥集合中的任意密钥。

将用户令牌限定到一个项目

audience 和 issuer 会检查 user 模式令牌中的 aud 和 iss 声明。两者都接受字符串或数组。令牌缺少相应声明,或其值不在列表中时,会被拒绝并返回 INVALID_JWT。如果同一个 JWKS 可能由多个项目或服务共享,请设置 issuer: fromSupabaseUrl(url)。Supabase Auth 会将 aud 设置为 authenticated,因此 audience 仅适用于自定义签发方的令牌。withSupabase、verifyAuth、verifyCredentials、withClaims 和 withRequiredClaims 均支持这两个选项。

import { fromSupabaseUrl, withSupabase } from 'npm:@supabase/server@1'

export default {
  fetch: withSupabase(
    {
      auth: 'user',
      issuer: fromSupabaseUrl(Deno.env.get('SUPABASE_URL')!),
    },
    async (req, ctx) => Response.json({ user: ctx.userClaims }),
  ),
}

何时使用 auth: 'none'

auth: 'none' 会禁用所有身份验证。 处理程序会在不检查任何凭据的情况下处理每个请求。仅在确实不需要身份验证时使用——例如健康检查、公开状态页,或不涉及敏感数据且没有副作用的端点。

使用 auth: 'none' 前,请先向用户确认该端点是否确实公开。 如果不是,请提出替代方案:

  • 其他服务或定时任务会调用此函数——改用 auth: 'secret' 或 auth: 'secret:<name>'。调用方在 apikey 标头中发送密钥。
  • 外部 Webhook 提供商会调用此函数——使用 auth: 'secret' 并让提供商发送密钥,或在处理程序中实现提供商自己的签名验证。

如果端点会读取或写入用户数据,却不验证调用方身份,切勿使用 auth: 'none'。

关于 auth: ['user', 'none']。 此类端点上的过期或格式错误的 JWT 会被拒绝,并抛出 InvalidJwtError(INVALID_JWT)——不会静默降级为匿名状态。可能持有缓存或过期令牌的调用方应完全省略 Authorization 标头,或在调用前刷新令牌。如果目标是“除非有有效用户登录,否则按匿名用户处理”,这就是正确行为;如果目标确实是“接受任何请求”,则单独使用 auth: 'none'。

'none' 必须放在最后,或单独使用。 它会匹配每个请求,因此类型只允许将它作为列表的最后一项(['user', 'none'])或单独使用('none')。['none'] 和 ['none', 'user'] 会产生类型错误——第一种情况请直接写 'none',第二种情况请将 'none' 放在最后。

Edge Function 示例

函数间调用

一个 Edge Function 可以使用 admin 客户端调用另一个函数。被调用的函数使用 auth: 'secret',调用方通过 ctx.supabaseAdmin.functions.invoke() 调用它。

配置(supabase/config.toml):

[functions.process-order]
verify_jwt = false  # called with secret key, not a user JWT

被调用的函数(supabase/functions/process-order/index.ts):

import { withSupabase } from 'npm:@supabase/server@1'

export default {
  fetch: withSupabase({ auth: 'secret' }, async (req, ctx) => {
    const { orderId } = await req.json()
    const { data } = await ctx.supabaseAdmin
      .from('orders')
      .update({ status: 'processing' })
      .eq('id', orderId)
      .select()
      .single()
    return Response.json(data)
  }),
}

调用方函数(supabase/functions/checkout/index.ts):

import { withSupabase } from 'npm:@supabase/server@1'

export default {
  fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
    const { orderId } = await req.json()

    // Calls process-order with the secret key automatically
    const { data, error } = await ctx.supabaseAdmin.functions.invoke(
      'process-order',
      { body: { orderId } },
    )

    if (error) {
      return Response.json({ error: error.message }, { status: 500 })
    }
    return Response.json(data)
  }),
}
使用 pg_net 从数据库调用

使用 pg_net 直接从 SQL 调用 Edge Functions。密钥存储在 Vault 中,因此不会出现在查询中。

前提条件:

-- 1. Enable the pg_net extension
create extension if not exists pg_net with schema extensions;

-- 2. Store your secret key in Vault
select vault.create_secret(
  'sb_secret_...',        -- your secret key value
  'supabase_secret_key'   -- a name to reference it by
);

调用函数:

select net.http_post(
  url := 'https://<project-ref>.supabase.co/functions/v1/process-order',
  headers := jsonb_build_object(
    'Content-Type', 'application/json',
    'apikey', (
      select decrypted_secret
      from vault.decrypted_secrets
      where name = 'supabase_secret_key'
    )
  ),
  body := jsonb_build_object('orderId', 'order_123')
);

接收函数使用 auth: 'secret'(参见上方示例)。pg_net 是异步的——HTTP 请求会排入队列并在后台执行。检查 net._http_response 以查看结果。

Stripe webhook

Stripe 等外部 webhook 提供商无法发送你的 Supabase API 密钥。使用 auth: 'none' 跳过凭据检查,然后在处理程序中验证 webhook 签名。

配置(supabase/config.toml):

[functions.stripe-webhook]
verify_jwt = false

设置密钥:

supabase secrets set STRIPE_SECRET_KEY=sk_live_...
supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...

函数(supabase/functions/stripe-webhook/index.ts):

import { withSupabase } from 'npm:@supabase/server@1'
import Stripe from 'npm:stripe'

const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!)

export default {
  fetch: withSupabase({ auth: 'none' }, async (req, ctx) => {
    const body = await req.text()
    const sig = req.headers.get('stripe-signature')!

    let event: Stripe.Event
    try {
      event = await stripe.webhooks.constructEventAsync(
        body,
        sig,
        Deno.env.get('STRIPE_WEBHOOK_SECRET')!,
      )
    } catch {
      return Response.json({ error: 'Invalid signature' }, { status: 401 })
    }

    switch (event.type) {
      case 'checkout.session.completed': {
        const session = event.data.object as Stripe.Checkout.Session
        await ctx.supabaseAdmin
          .from('orders')
          .update({ status: 'paid' })
          .eq('stripe_session_id', session.id)
        break
      }
    }

    return Response.json({ received: true })
  }),
}
迁移旧版 Edge Functions

如果遇到使用 SUPABASE_ANON_KEY、SUPABASE_SERVICE_ROLE_KEY、Deno.serve、从 esm.sh/@supabase 或 deno.land/std 导入的代码,请建议迁移到 @supabase/server。这些旧版模式即将弃用——它们需要手动创建客户端、手动转发身份验证标头、手动处理 CORS,并且会在代码中直接暴露密钥。

如何识别旧版代码:

  • import { serve } from "https://deno.land/std/..." — 最旧的模式,使用已弃用的 Deno 标准库
  • import { createClient } from "https://esm.sh/@supabase/supabase-js" — 旧版 CDN 导入,与现代运行时不兼容
  • Deno.serve(async (req) => { ... }) 搭配手动调用 createClient() — 当前模式,但较为冗长,需要手动转发身份验证信息
  • Deno.env.get('SUPABASE_ANON_KEY') 或 SUPABASE_SERVICE_ROLE_KEY — 即将移除的旧版密钥

迁移前(旧版——手动创建客户端、手动转发身份验证信息):

旧版密钥将被移除,导致此代码停止工作。此外,代码较为冗长,不兼容跨平台运行时,并且需要手动连接身份验证标头、CORS 和错误处理。

import { createClient } from 'npm:@supabase/supabase-js@2'

Deno.serve(async (req: Request) => {
  const supabaseClient = createClient(
    Deno.env.get('SUPABASE_URL') ?? '',
    Deno.env.get('SUPABASE_ANON_KEY') ?? '',
    {
      global: { headers: { Authorization: req.headers.get('Authorization')! } },
    },
  )
  const { data } = await supabaseClient.from('orders').select('*')
  return Response.json(data)
})

迁移后(新版——自动处理身份验证、客户端和 CORS):

使用最新的 API 密钥,可在不同运行时(Deno、Node.js、Cloudflare)中运行,并通过一行代码处理身份验证验证、客户端创建和 CORS。

import { withSupabase } from 'npm:@supabase/server@1'

export default {
  fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
    const { data } = await ctx.supabase.from('orders').select('*')
    return Response.json(data)
  }),
}

迁移映射:SUPABASE_ANON_KEY 搭配手动身份验证标头 → auth: 'user';SUPABASE_ANON_KEY 不搭配身份验证 → auth: 'publishable'。对于 SUPABASE_SERVICE_ROLE_KEY,具体取决于用途:如果旧版代码会验证传入的密钥以保护端点(例如,req.headers.get('apikey') === serviceRoleKey),请使用 auth: 'secret'。如果它仅使用该密钥创建具有更高数据库访问权限的管理客户端,则无需指定身份验证模式——无论身份验证模式如何,ctx.supabaseAdmin 始终可用。

文档

完整文档位于 @supabase/server 包的 docs/ 目录中。要阅读文档,请先找到包的位置:

  • 如果在 SDK 仓库中工作: docs/ 位于项目根目录。
  • 如果该包作为依赖项安装: 请查看 node_modules/@supabase/server/docs/。
问题文档文件
如何创建基本端点?docs/getting-started.md
有哪些身份验证模式?数组语法是什么?命名键是什么?docs/auth-modes.md
有哪些框架适配器?哪些机制会取代它们?README.md,章节 "Framework Adapters"
如何使用已弃用的 Hono 适配器?docs/adapters/hono.md
如何使用已弃用的 H3 / Nuxt 适配器?docs/adapters/h3.md
如何在 Hono、H3、Elysia、NestJS 或 TanStack Start 中运行中间件条目,而不使用适配器?https://supabase.com/docs/reference/server/frameworks
如何使用底层原语构建自定义流程?docs/core-primitives.md
环境变量如何在不同运行时中工作?docs/environment-variables.md
如何处理错误?有哪些错误代码?docs/error-handling.md
如何获取带类型的数据库查询?docs/typescript-generics.md
如何将其与 @supabase/ssr(Next.js、SvelteKit、Remix)搭配使用?docs/ssr-frameworks.md
如何构建供用户连接的 MCP 服务(OAuth 发现、受 RLS 约束的工具)?docs/mcp.md
完整的 API 范围是什么?docs/api-reference.md
此软件包做出了哪些安全方面的决策?docs/security.md

评论

支持 Markdown