关于 supabase-server
@supabase/server 是用于服务端请求处理器的 Supabase 工具包,面向构建 Edge Functions 和其他服务端应用的开发者。它将凭据验证、CORS、Supabase 客户端创建和上下文注入组合起来,并支持多种认证模式。
主要功能
- 01包装请求处理器并验证凭据
- 02支持四种认证模式
- 03创建带上下文的 Supabase 客户端
- 04提供可组合的认证核心原语
- 05通过中间件桥接接入多个框架
- 06提供 MCP OAuth 2.1 资源发现工具
适用场景
- 01为 Edge Function 验证用户 JWT
- 02使用密钥保护服务间请求
- 03为自定义认证流程构建上下文
- 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/server | npm:@supabase/server@1 | withSupabase、createSupabaseContext、类型、错误 |
@supabase/server/core | npm:@supabase/server@1/core | verifyAuth、verifyCredentials、extractCredentials、resolveEnv、createContextClient、createAdminClient |
@supabase/server/adapters/hono | npm:@supabase/server@1/adapters/hono | 已弃用。将于 2026 年 12 月 1 日移除。 withSupabase(Hono 中间件变体)。新的 Hono 代码使用下方 Hono 快速入门中的桥接方案。 |
@supabase/server/oauth-protected-resource | npm:@supabase/server@1/oauth-protected-resource | withOAuthProtectedResource、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/server1.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
对于 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 |