Fine-Grained Authorization (FGA)
细粒度授权属于 Mastra 企业版(Enterprise Edition)功能。生产部署需要有效的 EE 许可证。更多信息请联系销售。
细粒度授权(FGA)为你的 Mastra 应用增加资源级权限检查。RBAC 回答的是“这个角色能否执行此操作?”,而 FGA 回答的是**“这个用户能否对某个特定资源执行此操作?”**
何时使用 FGA何时使用 FGA的直接链接
FGA 面向权限具有上下文相关性的多租户 B2B 产品:
- 一个用户可能是团队 A 的 admin,但只是团队 B 的 member
- 线程访问应限制在用户所属组织内
- 工作流执行应限定在特定团队或项目
- 工具访问取决于用户与资源的关系
配置配置的直接链接
在 Mastra 服务端配置中,与认证和 RBAC 一起配置 FGA:
import { Mastra } from '@mastra/core/mastra'
import { MastraFGAPermissions } from '@mastra/core/auth/ee'
import { MastraAuthWorkos, MastraFGAWorkos } from '@mastra/auth-workos'
const mastra = new Mastra({
server: {
auth: new MastraAuthWorkos({
/* ... */
fetchMemberships: true,
mapUserToResourceId: user => user.teamId,
}),
fga: new MastraFGAWorkos({
resourceMapping: {
agent: { fgaResourceType: 'team', deriveId: ctx => ctx.user.teamId },
workflow: { fgaResourceType: 'team', deriveId: ctx => ctx.user.teamId },
thread: { fgaResourceType: 'workspace-thread', deriveId: ({ resourceId }) => resourceId },
},
permissionMapping: {
[MastraFGAPermissions.AGENTS_EXECUTE]: 'manage-workflows',
[MastraFGAPermissions.WORKFLOWS_EXECUTE]: 'manage-workflows',
[MastraFGAPermissions.MEMORY_READ]: 'read',
[MastraFGAPermissions.MEMORY_WRITE]: 'update',
},
}),
storedResources: {
scope: true,
},
},
})
使用 MastraFGAWorkos 时,请在 MastraAuthWorkos 上设置 fetchMemberships: true。WorkOS FGA 检查需要用户的组织成员关系,以解析出正确的成员 ID 进行授权。
在内存授权中,请使用 thread 作为资源映射的键。MastraFGAWorkos 仍然接受旧别名 memory,但新配置应优先使用 thread。
配置 server.fga 后,Mastra 会对受保护操作强制执行 FGA。如果某个受保护操作没有已认证用户,Mastra 会拒绝该操作。如果未配置 server.fga,这些 FGA 检查会被跳过,Mastra 保持之前的行为。
资源映射资源映射的直接链接
resourceMapping 告诉 Mastra 如何从请求上下文中解析 FGA 资源类型和 ID。键是 Mastra 资源类型,值定义 FGA 资源类型以及如何派生 ID:
resourceMapping: {
// When checking "can user execute agent X?", resolve the FGA resource
// as the user's team (type: 'team', id: user.teamId)
agent: {
fgaResourceType: 'team',
deriveId: (ctx) => ctx.user.teamId,
},
}
deriveId() 接收:
user:已认证用户resourceId:可用的所属 Mastra 资源 ID(例如线程的resourceId)requestContext:当前请求上下文,用于高级租户解析
从 deriveId() 返回 undefined,会回退到原始 Mastra 资源 ID。
对于线程和内存检查,Mastra 仍会将原始 threadId 作为被检查的资源传入,但也会将线程所属的 resourceId 转发给 deriveId()。这样你就可以将线程权限映射到复合租户 ID,例如 userId-teamId-orgId。
权限映射权限映射的直接链接
permissionMapping 将 Mastra 内部权限字符串转换为 FGA 提供方的权限 slug:
import { MastraFGAPermissions } from '@mastra/core/auth/ee';
permissionMapping: {
[MastraFGAPermissions.AGENTS_EXECUTE]: 'manage-workflows', // Mastra permission -> WorkOS permission slug
[MastraFGAPermissions.MEMORY_READ]: 'read',
}
如果某个权限没有映射,则原样传递原始字符串。
使用 validatePermissions() 在启动时校验 Mastra 可能发出的全部权限。当提供方要求每个 Mastra 权限都必须有显式的提供方权限 slug 时,请使用它。
存储资源作用域存储资源作用域的直接链接
FGA 负责对资源的访问授权,但它不会自动过滤共享存储中的已存记录。当在多租户应用中使用内置存储资源 API 时,请启用存储资源作用域。
const mastra = new Mastra({
server: {
auth: new MastraAuthWorkos({
/* ... */
mapUserToResourceId: user => user.teamId,
}),
storedResources: {
scope: true,
},
},
})
启用 scope: true 后,Mastra 会从请求上下文中读取 MASTRA_RESOURCE_ID_KEY。mapUserToResourceId() 会在认证后设置该值。存储资源处理器会将作用域持久化到记录元数据中,并按该作用域过滤列表、读取、更新、发布和删除操作。
当作用域需要自定义请求逻辑时,使用对象形式:
storedResources: {
scope: {
metadataKey: 'teamId',
resolve: ({ user }) => user.teamId,
requireScope: true,
},
},
如果 requireScope 为 true 或省略,当无法解析作用域时,带作用域的存储资源路由会失败。
路由策略覆盖路由策略覆盖的直接链接
Mastra 为内置资源路由提供了路由级 FGA 元数据,包括 agents、workflows、tools、MCP tools、memory threads、responses、conversations 和存储资源。存储资源路由覆盖 /stored/agents、/stored/mcp-clients、/stored/prompt-blocks、/stored/scorers、/stored/skills 和 /stored/workspaces。当路由带有路由级 fga 元数据、Mastra 能为该路由派生内置元数据,或提供方通过 resolveRouteFGA() 提供元数据时,该路由会被检查。
要拒绝无法解析 FGA 元数据的受保护路由,请在 FGA 提供方上配置路由策略覆盖:
const fga = new MastraFGAWorkos({
resourceMapping: {
project: { fgaResourceType: 'project' },
},
permissionMapping: {
'projects:read': 'read',
},
requireForProtectedRoutes: true,
auditProtectedRoutes: 'warn',
validatePermissions: async permissions => {
// Throw if a Mastra permission is missing from permissionMapping.
},
})
将 auditProtectedRoutes 设为 'error',可在受保护路由缺少内置 FGA 元数据时让启动失败。如果启用了 requireForProtectedRoutes,Mastra 默认会将该审计记录为警告。
对于自定义路由,优先使用路由级 fga 元数据。这样授权策略就与路由放在一起:
import { createRoute } from '@mastra/server/server-adapter'
export const getProjectRoute = createRoute({
method: 'GET',
path: '/projects/:projectId',
responseType: 'json',
requiresAuth: true,
fga: {
resourceType: 'project',
resourceIdParam: 'projectId',
permission: 'projects:read',
},
handler: async () => {
return { project: null }
},
})
仅当路由元数据必须基于路由、参数或请求上下文集中派生时,才使用 resolveRouteFGA()。路由映射比字符串前缀检查更具扩展性:
import type { FGARouteConfig, FGARouteResolver } from '@mastra/core/auth/ee'
const routeFGA = {
'GET /billing/:accountId': {
resourceType: 'account',
resourceIdParam: 'accountId',
permission: 'billing:read',
},
} satisfies Record<string, FGARouteConfig>
const resolveRouteFGA: FGARouteResolver = ({ route }) => routeFGA[`${route.method} ${route.path}`]
const fga = new MastraFGAWorkos({
/* ... */
resolveRouteFGA,
})
强制执行点强制执行点的直接链接
配置 FGA 提供方后,Mastra 会在以下生命周期点自动检查授权:
| 生命周期点 | 检查的权限 | 资源类型 | 资源 ID |
|---|---|---|---|
Agent 执行(generate、stream) | agents:execute | agent | agentId |
内置工作流 HTTP 执行路由和 Workflow.execute() | workflows:execute | workflow | workflowId |
| 独立工具执行 | tools:execute | tool | toolName |
| Agent 工具执行 | tools:execute | tool | ${agentId}:${toolName} |
| MCP 工具执行 | tools:execute | 默认是 tool,或使用服务端级 fga.resourceMapping 覆盖 | 默认是 JSON.stringify([serverName, toolName]),或使用服务端派生的 ID |
| 线程和内存访问 | memory:read、memory:write、memory:delete | thread | threadId |
| 存储资源路由 | 路由操作对应的存储资源权限 | 存储资源类型 | 路由记录 ID,或集合路由使用的存储资源作用域 |
| HTTP 资源路由 | 按路由配置 | 按路由配置 | 按路由配置 |
对于受 OAuth 保护的 MCP 服务器,HTTP MCP 传输会将认证数据作为 extra.authInfo 传入。如果某个 MCPServer 注册在启用了 FGA 的 Mastra 实例上,请配置 mapAuthInfoToUser,以便 Mastra 在检查 tools/list 和 tools/call 之前设置 requestContext.get('user')。当 MCP 工具检查需要与内部 agent 和 workflow 工具检查不同的资源或权限映射时,请使用服务端级 fga 选项。参见 MCPServer 认证上下文。
直接通过 SDK 调用 createRun().start()、resume() 或 restart(),在当前版本中不会由核心 FGA 独立检查。请从受保护路由发起这些调用,或在应用代码中对其进行保护。直接调用受保护入口点时,请传入带有已认证用户的 requestContext。
核心的 agent、内部 workflow、tool 和 memory 检查还会将 requestContext 和操作元数据传递给 FGA 提供方。路由检查传递 requestContext。线程检查会在可用时传递所属的 resourceId。
自定义 FGA 提供方自定义 FGA 提供方的直接链接
实现 IFGAProvider 即可使用任意 FGA 后端:
import { FGADeniedError } from '@mastra/core/auth/ee'
import type { FGACheckParams, IFGAProvider, MastraFGAPermissionInput } from '@mastra/core/auth/ee'
class MyFGAProvider implements IFGAProvider {
async check(user: any, params: FGACheckParams): Promise<boolean> {
// Your authorization logic
return true
}
async require(user: any, params: FGACheckParams): Promise<void> {
const allowed = await this.check(user, params)
if (!allowed) {
throw new FGADeniedError(user, params.resource, params.permission)
}
}
async filterAccessible<T extends { id: string }>(
user: any,
resources: T[],
resourceType: string,
permission: MastraFGAPermissionInput,
): Promise<T[]> {
// Filter resources the user can access
return resources
}
}
关于每个方法、参数以及 ActorSignal 类型,请参阅 IFGAProvider 参考。
系统参与者系统参与者的直接链接
自主运行和定时运行的 agent 在没有最终用户的情况下运行。用 actor 信号标记这些调用,以便 FGA 将它们与用户请求区分开:
true或{ actorKind: 'system' }标识一个匿名系统参与者。- 对象形式还可以携带
agentId、permissions和scope,用于标识并约束执行操作的 agent。
默认情况下,受信任的参与者在经过租户作用域检查后会跳过以用户为中心的 require() 检查。若要对每个 agent 实施最小权限,请在提供方上实现可选的 requireActor 方法。该方法接收参与者以及和 require 相同的 FGACheckParams,并通过抛出 FGADeniedError 拒绝访问。未实现 requireActor 的提供方会保留受信任参与者绕过逻辑,因此该新增能力向后兼容。
import { FGADeniedError } from '@mastra/core/auth/ee'
import type { ActorSignal, FGACheckParams, IFGAProvider } from '@mastra/core/auth/ee'
class MyFGAProvider implements IFGAProvider {
// ...check, require, filterAccessible...
async requireActor(actor: ActorSignal, params: FGACheckParams): Promise<void> {
const agentId = actor === true ? undefined : actor.agentId
// Resolve the agent's real grants from a trusted source keyed by agentId.
const granted = await this.grantsForAgent(agentId)
const required = Array.isArray(params.permission) ? params.permission : [params.permission]
if (!required.some(permission => granted.includes(permission))) {
throw new FGADeniedError(null, params.resource, params.permission)
}
}
}
将参与者传播到工作流步骤将参与者传播到工作流步骤的直接链接
工作流运行的 actor 会传递到每个步骤的执行上下文,但不会被传递给该步骤发起的 agent 和 tool 调用。调用 agent 时未提供 actor 的步骤会回退到用户成员关系解析,对于没有用户的系统运行会失败。
在 actor 上设置 propagate: true,框架就会在运行中为声明式 .then(agent) 和 .then(tool) 步骤所发起的 agent 和 tool 调用转发该 actor:
await run.start({
inputData: { city: 'London' },
actor: { actorKind: 'system', sourceWorkflow: 'nightly-report', propagate: true },
})
传播是选择启用的,并且刻意保持范围狭窄:
true简写永远不会传播。请使用对象形式来选择启用。- 自定义步骤的
execute函数永远不会被覆盖。请从步骤上下文读取actor并显式传递,这样代表用户执行的步骤就不会静默继承系统信任。 - 在创建步骤时传入
actor,可覆盖该步骤的运行级 actor,包括传入actor: undefined以在运行中途回退到用户授权。
// Runs as the system actor even though it loads user-supplied data.
workflow.then(createStep(reportAgent))
// Explicitly runs as the user instead.
workflow.then(createStep(summaryAgent, { actor: undefined }))
信任要求信任要求的直接链接
actor 信号是受信任的输入,因此必须在服务端构造:
- 将
actor视为每次调用的信号。持久化工作流在运行启动时会转发它,但在恢复时不会还原最初的 actor。每次受信任的恢复都应显式传入它,或通过 agent 的defaultOptions解析新的 actor。没有当前 actor 时,将应用用户授权;缺少用户时会安全失败(fail closed)。 - Mastra 会从其内置 agent HTTP 路由处理的执行选项中剥离
actor。请在服务端代码中设置它,例如定时任务或工作流中,绝不要来自客户端输入。 - 在服务端确定租户作用域。内置 agent HTTP 路由会忽略请求上下文中客户端提供的
organizationId,而受信任参与者路径要求设置organizationId。 - 持久化恢复保留其现有的请求上下文恢复和合并行为。这不会让持久化的 actor 在后续工作流片段中被信任。
- 租户作用域检查确认存在受信任的
organizationId,但不会验证actor.agentId是否属于该组织。当这种关系很重要时,请在requireActor中使用权威提供方数据进行验证。 - 将
actor.permissions视为未经证实的声明。要实施最小权限,请从受信任来源解析 agent 的权限,例如以agentId为键的清单或你的 FGA 后端。不要信任内联值。 - 一旦提供方实现了
requireActor,该方法的错误会终止执行。Mastra 不会回退到仅组织级别的授权。