Mastra 集成 Supabase Auth:@mastra/auth-supabase 授权层实现与源码解析
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
本文基于 Mastra 仓库中 auth/supabase/README.md 及其配套实现,讲解@mastra/auth-supabase包的用途、安装与配置方式,并结合 auth/supabase/src/index.ts 的源码与 auth/supabase/src/index.test.ts 的测试用例,剖析其令牌认证(authenticateToken)、默认授权策略(authorizeUser)以及自定义授权覆盖的完整机制,帮助你在 Supabase Auth 已管理应用用户的前提下,让同一套会话直接保护 Mastra 服务端点。
1. 包定位:让 Supabase 会话保护 Mastra 端点
@mastra/auth-supabase是 Mastra 授权层(authorization layer)的 Supabase 提供方(provider)。根据 auth/supabase/README.md 的说明,它做两件事:
- 验证 Supabase access token:将请求中携带的 Supabase 令牌交给
@supabase/supabase-js校验,换取对应的 Supabase 用户; - 暴露 Supabase 用户给 Mastra 授权层:认证得到的用户对象会进入 Mastra 服务端的授权流程,参与路径保护判断。
适用场景非常明确:你的应用已经使用 Supabase Auth 管理用户(登录、注册、会话刷新都由 Supabase 完成),此时不需要为 Mastra 再搭一套独立认证体系,直接复用同一套 Supabase 会话来保护 Mastra 暴露的 API 端点即可。
从源码结构看(auth/supabase/src/index.ts),该包只导出了一个类:
export class MastraAuthSupabase extends MastraAuthProvider<User> { protected supabase: SupabaseClient; // ... }它继承自MastraAuthProvider(基类定义见 packages/_internals/auth/src/provider/index.ts),泛型参数为@supabase/supabase-js的User类型,即认证/授权流程中传递的用户对象就是标准的 Supabase User。
2. 安装与前置条件
安装命令(来自 auth/supabase/README.md):
npm install @mastra/auth-supabase运行环境约束可从 auth/supabase/package.json 确认:
- 要求 Node.js
>= 22.13.0; - 运行时依赖为
@supabase/supabase-js@^2.110.9,令牌校验与数据库查询都通过该官方 SDK 完成; - 包采用双格式导出(ESM 的
dist/index.js与 CJS 的dist/index.cjs,见 package.json 的 exports 字段),可按项目模块类型导入。
前置条件:在启动 Mastra 之前,需要准备好两个 Supabase 凭据(README 中的 "SetSUPABASE_URLandSUPABASE_ANON_KEYbefore starting Mastra"):
| 环境变量 | 含义 | 获取位置 |
|---|---|---|
SUPABASE_URL | Supabase 项目的 URL | Supabase 控制台项目设置 |
SUPABASE_ANON_KEY | Supabase anon 密钥(客户端公开密钥) | Supabase 控制台项目设置 |
3. 基本用法:接入 Mastra 服务端 auth
README 给出的标准接入方式,是在new Mastra(...)的server.auth中实例化MastraAuthSupabase:
import { Mastra } from '@mastra/core/mastra'; import { MastraAuthSupabase } from '@mastra/auth-supabase'; export const mastra = new Mastra({ server: { auth: new MastraAuthSupabase({ url: process.env.SUPABASE_URL, anonKey: process.env.SUPABASE_ANON_KEY, }), }, });对应仓库内的参考文档还包括 docs/src/content/en/integrations/auth/supabase.mdx(集成指南)与 docs/src/content/en/reference/auth/supabase.mdx(Provider 参考)。
3.1 构造函数:凭据解析与失败即抛错
auth/supabase/src/index.ts 的构造函数逻辑:
constructor(options?: MastraAuthSupabaseOptions) { super({ name: options?.name ?? 'supabase' }); const supabaseUrl = options?.url ?? process.env.SUPABASE_URL; const supabaseAnonKey = options?.anonKey ?? process.env.SUPABASE_ANON_KEY; if (!supabaseUrl || !supabaseAnonKey) { throw new Error( 'Supabase URL and anon key are required, please provide them in the options or set the environment variables SUPABASE_URL and SUPABASE_ANON_KEY', ); } this.supabase = createClient(supabaseUrl, supabaseAnonKey); this.registerOptions(options); }要点:
- 双通道凭据解析:优先使用显式传入的
options.url/options.anonKey,缺省时回退到环境变量SUPABASE_URL/SUPABASE_ANON_KEY。因此new MastraAuthSupabase()无参构造是可行的,前提是环境变量已设置(测试 src/index.test.ts 专门验证了这条路径); - 启动期快速失败:两个凭据都缺失时直接抛出错误。这意味着问题会在应用启动阶段暴露,而不是在第一个请求到达时才失败;
name默认为'supabase':作为 Mastra 组件名用于日志与组件标识(基类构造时传给MastraBase,见 provider/index.ts);registerOptions(options):将可选的authorizeUser、mapUserToResourceId、protected、public等选项注册到基类实例上(见 provider/index.ts),这是后文自定义授权能够生效的机制基础。
3.2 可选配置项(继承自MastraAuthProviderOptions)
从 packages/_internals/auth/src/provider/index.ts 的MastraAuthProviderOptions定义看,MastraAuthSupabase的构造参数除了 Supabase 特有的url/anonKey外,还支持所有授权提供方通用的选项:
| 选项 | 类型 | 说明 |
|---|---|---|
name | string? | 组件名,默认'supabase' |
authorizeUser | AuthorizeUserFn<TUser>? | 自定义授权函数,覆盖默认的isAdmin查询逻辑 |
mapUserToResourceId | (user) => string \| null \| undefined | 将认证用户映射为 memory/thread 作用域用的资源 ID |
protected | (RegExp \| string \| [string, Methods \| Methods[]])[] | 需要保护的端点路径规则 |
public | 同上 | 公开端点路径规则 |
路径规则中Methods取值定义在 packages/_internals/auth/src/types/index.ts:'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'ALL'。
4. 认证流程:authenticateToken
async authenticateToken(token: string): Promise<User | null> { const { data, error } = await this.supabase.auth.getUser(token); if (error) { return null; } return data.user; }(来源:auth/supabase/src/index.ts)
- 入参
token是 Mastra 服务端从请求中提取的 access token,内部调用supabase.auth.getUser(token)向 Supabase Auth 发起在线校验; - 成功:返回完整的 Supabase
User对象(包含id、email、user_metadata等字段); - 失败(令牌无效、过期或网络错误):返回
null,而不抛异常——由上层 Mastra 授权流程将null解释为"未认证",请求将被拒绝。
测试 src/index.test.ts 对两条路径都有覆盖:有效 token 返回 mock 用户对象,且断言getUser确实以该 token 为参数被调用;无效 token 返回null。
需要注意的一点:该校验是每次请求实时查询 Supabase Auth,本包自身不做本地令牌缓存,也不解析 JWT 签名——令牌有效性完全委托给 Supabase 服务端。
5. 默认授权策略:查询users表的isAdmin字段
这是该包最有"行为约定"属性的部分。默认的authorizeUser实现(auth/supabase/src/index.ts):
async authorizeUser(user: User) { // Get user data from Supabase const { data, error } = await this.supabase .from('users') .select('isAdmin') .eq('id', user?.id) .single(); if (error) { return false; } const isAdmin = data?.isAdmin; // Check permissions based on role return isAdmin; }由此可以得出三条使用前提与行为约定:
- Supabase 项目中必须存在名为
users的数据表,且该表主键列id与 Supabase Auth 的用户 ID 对应,并包含一个布尔列isAdmin; - 授权 = 是否管理员:请求路径是否放行,取决于该用户行上
isAdmin的真值。非管理员(isAdmin: false)会被授权层拒绝; - 查询失败一律拒绝(fail-closed):数据库查询出现任何错误(表不存在、RLS 拒绝读取等)都返回
false,而不是放行。
测试用例 src/index.test.ts 精确验证了这个行为序列:
- 管理员用户:断言查询链
from('users')→select('isAdmin')→eq('id', user.id)被依次调用,且结果为true; - 非管理员用户(
isAdmin: false):返回false; - 数据库错误(
single返回 error):返回false。
另外注意,这里使用的是anon key 客户端查询业务表,因此users表必须允许该查询路径可读(例如针对 anon 角色的 RLS 策略);若 RLS 未放行,error分支会命中,所有用户都会被授权层拒绝——这是部署时最容易踩的坑,建议先手工验证 anon key 下select isAdmin from users where id = ...是否可读。
6. 覆盖授权逻辑:通过authorizeUser选项定制
默认的"按isAdmin列判断"在很多场景并不适用(例如权限来自user_metadata、角色存在其他表、或需要按路径细分权限)。基类MastraAuthProvider支持在构造时传入authorizeUser函数直接覆盖默认实现,registerOptions会将其绑定到实例(provider/index.ts)。
测试 src/index.test.ts 给出了一个完整的可运行示例:
const supabase = new MastraAuthSupabase({ async authorizeUser(user: User): Promise<boolean> { // 自定义授权逻辑:检查特定权限 return user?.permissions?.includes('admin') ?? false; }, }); // 有 admin 权限 -> true await supabase.authorizeUser({ sub: 'user123', permissions: ['admin'] }); // 只有 read 权限 -> false await supabase.authorizeUser({ sub: 'user456', permissions: ['read'] }); // 无 permissions 字段 -> false await supabase.authorizeUser({ sub: 'user789' });可见覆盖后的函数签名接收认证得到的User对象并返回Promise<boolean> | boolean,完全脱离了对users表isAdmin列的依赖。如果你的权限模型就是 Supabase Auth 的 metadata,这是最轻量的定制方式,无需继承类、无需额外数据库查询。
7. 在 Mastra 服务端中的位置
MastraAuthSupabase最终作为IMastraAuthProvider被server.auth接收。从 packages/core/src/server/auth.ts 看,MastraAuthProvider等类型是从内部 auth 包转出给 core 使用的公共 API;提供方需要实现的两个核心抽象方法是authenticateToken与authorizeUser(见 provider/index.ts),MastraAuthSupabase恰好一一实现:
- 请求到达受保护路径 → Mastra 服务端提取 token;
authenticateToken(token)用 Supabase Auth 校验令牌,得到User或null;authorizeUser(user)按默认isAdmin策略(或你自定义的策略)决定放行与否。
当你的 Mastra 实例同时组合多个授权提供方时(例如 Supabase + 另一个 SSO),它们会被CompositeAuth包装,按顺序尝试各提供方的authenticateToken,任一成功即视为已认证(见 provider/index.ts),此时mapUserToResourceId会被自动代理到真正完成认证的那个提供方。
8. 小结与核对清单
@mastra/auth-supabase的实现非常薄,职责边界清晰:认证委托给 Supabase Auth,授权默认读取users.isAdmin,其余策略全部开放定制。接入前建议按以下清单核对:
- 已安装
@mastra/auth-supabase,Node.js 版本满足>= 22.13.0; SUPABASE_URL与SUPABASE_ANON_KEY已设置(环境变量或构造参数二选一即可,缺一即启动报错);- 走默认授权时:Supabase 中存在
users表,含id与isAdmin列,且 anon key 可读; - 需要非"管理员"语义的权限模型时:已通过
authorizeUser选项覆盖默认逻辑; - 需要按路径细分公开/保护端点时:已配置
public/protected规则。
相关入口文件汇总:
| 文件 | 说明 |
|---|---|
| auth/supabase/README.md | 包说明(安装、用法、文档索引) |
| auth/supabase/src/index.ts | MastraAuthSupabase完整实现 |
| auth/supabase/src/index.test.ts | 构造函数/认证/授权/自定义覆盖的测试用例 |
| auth/supabase/package.json | 依赖、Node 版本与导出格式 |
| auth/supabase/CHANGELOG.md | 版本历史 |
| packages/_internals/auth/src/provider/index.ts | MastraAuthProvider基类与通用选项定义 |
| docs/src/content/en/integrations/auth/supabase.mdx | 仓库内集成指南文档 |
| docs/src/content/en/reference/auth/supabase.mdx | 仓库内 Provider API 参考文档 |
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考