Mastra 集成 Supabase Auth:@mastra/auth-supabase 授权层实现与源码解析
2026/9/14 18:16:23 网站建设 项目流程

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 的说明,它做两件事:

  1. 验证 Supabase access token:将请求中携带的 Supabase 令牌交给@supabase/supabase-js校验,换取对应的 Supabase 用户;
  2. 暴露 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-jsUser类型,即认证/授权流程中传递的用户对象就是标准的 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_URLSupabase 项目的 URLSupabase 控制台项目设置
SUPABASE_ANON_KEYSupabase 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):将可选的authorizeUsermapUserToResourceIdprotectedpublic等选项注册到基类实例上(见 provider/index.ts),这是后文自定义授权能够生效的机制基础。

3.2 可选配置项(继承自MastraAuthProviderOptions

从 packages/_internals/auth/src/provider/index.ts 的MastraAuthProviderOptions定义看,MastraAuthSupabase的构造参数除了 Supabase 特有的url/anonKey外,还支持所有授权提供方通用的选项:

选项类型说明
namestring?组件名,默认'supabase'
authorizeUserAuthorizeUserFn<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 发起在线校验;
  • 成功:返回完整的 SupabaseUser对象(包含idemailuser_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; }

由此可以得出三条使用前提与行为约定:

  1. Supabase 项目中必须存在名为users的数据表,且该表主键列id与 Supabase Auth 的用户 ID 对应,并包含一个布尔列isAdmin
  2. 授权 = 是否管理员:请求路径是否放行,取决于该用户行上isAdmin的真值。非管理员(isAdmin: false)会被授权层拒绝;
  3. 查询失败一律拒绝(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,完全脱离了对usersisAdmin列的依赖。如果你的权限模型就是 Supabase Auth 的 metadata,这是最轻量的定制方式,无需继承类、无需额外数据库查询。

7. 在 Mastra 服务端中的位置

MastraAuthSupabase最终作为IMastraAuthProviderserver.auth接收。从 packages/core/src/server/auth.ts 看,MastraAuthProvider等类型是从内部 auth 包转出给 core 使用的公共 API;提供方需要实现的两个核心抽象方法是authenticateTokenauthorizeUser(见 provider/index.ts),MastraAuthSupabase恰好一一实现:

  1. 请求到达受保护路径 → Mastra 服务端提取 token;
  2. authenticateToken(token)用 Supabase Auth 校验令牌,得到Usernull
  3. 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_URLSUPABASE_ANON_KEY已设置(环境变量或构造参数二选一即可,缺一即启动报错);
  • 走默认授权时:Supabase 中存在users表,含idisAdmin列,且 anon key 可读;
  • 需要非"管理员"语义的权限模型时:已通过authorizeUser选项覆盖默认逻辑;
  • 需要按路径细分公开/保护端点时:已配置public/protected规则。

相关入口文件汇总:

文件说明
auth/supabase/README.md包说明(安装、用法、文档索引)
auth/supabase/src/index.tsMastraAuthSupabase完整实现
auth/supabase/src/index.test.ts构造函数/认证/授权/自定义覆盖的测试用例
auth/supabase/package.json依赖、Node 版本与导出格式
auth/supabase/CHANGELOG.md版本历史
packages/_internals/auth/src/provider/index.tsMastraAuthProvider基类与通用选项定义
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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询