Activepieces Managed Auth 深度解析:基于 JWT 的嵌入式认证与外部 Token 交换机制
【免费下载链接】activepiecesAI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces
Managed Auth(托管认证)是 Activepieces 面向 SaaS 厂商提供的嵌入式认证能力:厂商在自己的产品中嵌入 Activepieces 流程构建器,由厂商后端用 RSA 私钥签发短期 JWT,换取 Activepieces 侧完整的AuthenticationResponse(含访问令牌),实现"用户、项目、权限与并发限制全部由外部 Token 的 claims 自动供给"。本文以仓库中 managed-auth.md 为骨架,结合服务端、前端与共享包的源码实现,完整讲解其架构、Token 协议、交换流程与安全边界,读完即可在自己的 SaaS 产品中接入这一认证链路。
一、什么是 Managed Auth:把构建器嵌进你的产品
在 Activepieces 中,"Embedding" 指 SaaS 厂商(下称"厂商")将 Activepieces 的流程构建器嵌入自有产品页面,让终端用户无需注册 Activepieces 账号即可直接编排流程。难点在于:Activepieces 需要为每一位终端用户自动创建账号与项目,并严格控制其可见的 Pieces 范围与并发能力——这不能靠用户手动注册完成,必须由厂商后端在每次会话启动时"代为认证"。
Managed Auth 正是为此设计的服务端到服务端认证协议:
- 厂商后端持有一把 RSA私钥,用它签发一个短时 JWT(External Access Token);
- JWT 通过 Activepieces 的 Embed SDK 传递给服务端端点
POST /v1/managed-authn/external-token; - 服务端用存储在 Activepieces 侧的公钥验签,再从 claims 中提取
externalUserId、externalProjectId、角色、Piece 范围、并发池等信息; - 服务端自动完成"用户/项目/权限"的按需创建或复用,最终返回一个完整
AuthenticationResponse(含 Activepieces 访问令牌),前端 SDK 据此以该用户身份进入构建器。
值得注意的是,该功能受平台套餐platform.plan.embeddingEnabled门控,但门控位于 Signing Key 模块,而非 external-token 端点本身。从源码看,signing-key-module.ts 在注册/v1/signing-keys路由前挂载了platformMustHaveFeatureEnabled((platform) => platform.plan.embeddingEnabled)钩子;而 managed-authn-controller.ts 中的端点配置为securityAccess.public()——其安全性完全由 JWT 签名本身保证(详见"安全边界"一节)。
二、整体架构与一次完整交换的时序
从 app.ts 可见,managedAuthnModule与signingKeyModule在ApEdition.CLOUD与ApEdition.ENTERPRISE两个版本中均被注册(分别为 app.ts 与 app.ts),服务端模块注册路径为:
/v1/managed-authn— managed-authn-module.ts 注册控制器前缀;/v1/signing-keys— signing-key-module.ts 注册签名密钥 CRUD。
一次完整交换的时序如下:
厂商后端 Activepieces Server | | | 1. 用 RSA 私钥签发 JWT (short-lived) | |------------------------------------->| (不经过 AP,由厂商自行签发) | | | 2. Embed SDK 调用 POST /v1/managed-authn/external-token |------------------------------------->| externalTokenExtractor: | | - 按 header kid 找到 Signing Key | | - 用公钥 RS256 验签、解析 payload | | getOrCreateProject (按 externalProjectId) | | 可选:更新 displayName / upsert 并发池 | | applyProjectPieceAccess (无条件执行) | | getOrCreateUser (按 externalUserId + 哈希邮箱) | | upsert 项目成员 (默认 EDITOR) | | 签发 7 天 AP 访问令牌 |<-------------------------------------| 返回 AuthenticationResponse其中服务端的编排逻辑全部位于 managed-authn-service.ts 的externalToken方法,而 JWT 校验与 payload 解析位于 external-token-extractor.ts。
三、三个关键领域概念
3.1 Signing Key:RSA 密钥对
- 公钥存储在 Activepieces 平台侧(仅保存公钥,服务端无法伪造外部 Token);
- 私钥由厂商自己保管,用于签发 JWT;
- JWT 的 header 中
kid字段 = Signing Key ID,服务端据此从数据库解析对应的公钥。
从 signing-key-generator.ts 可以看到服务端生成密钥对的方式:使用 Node.jscrypto.generateKeyPair,算法rsa、模长 4096 位,公钥与私钥均使用pkcs1编码的 PEM 格式。而 signing-key-service.ts 的add方法在保存时只入库公钥,privateKey仅在创建响应中一次性返回给厂商——这意味着私钥一旦丢失无法从 Activepieces 侧找回,只能删除重建。
3.2 externalUserId:厂商用户 ID
厂商自己的用户标识,用于在 Activepieces 中确定性映射用户。它不会以原始形式入库,而是与platformId一起哈希为确定性的身份邮箱:
sha256("managed_<platformId>_<externalUserId>")对应源码为 managed-authn-service.ts 的generateEmailHash,哈希结果还会经过trim().toLowerCase()清洗以保证比较一致性。因此 Managed Auth 用户永远没有真实邮箱,其身份标识完全由externalId维系。
3.3 externalProjectId:厂商项目 ID
厂商的项目标识,通过项目的externalId字段映射到 Activepieces 项目。服务端按(platformId, externalProjectId)查找,不存在则创建一个TEAM 类型项目(owned by 平台 owner),见 managed-authn-service.ts。
四、前置准备:创建 Signing Key
在调用 external-token 端点之前,厂商必须先通过平台 API 创建一把签名密钥:
# 创建签名密钥(需要平台级管理员权限,受 embeddingEnabled 套餐门控) POST /v1/signing-keys Content-Type: application/json { "displayName": "my-saas-production" }响应中会包含privateKey(仅此一次):
{ "id": "sk_xxxx", "platformId": "platform_xxxx", "publicKey": "-----BEGIN RSA PUBLIC KEY-----...", "privateKey": "-----BEGIN RSA PRIVATE KEY-----...", "algorithm": "RSA", "displayName": "my-saas-production" }Signing Key 的完整 CRUD(增、查列表、按 id 查、删)由 signing-key-service.ts 提供,删除时按(platformId, id)双重限定,防止跨平台误删。
五、服务端签发 JWT:Token Payload 的四个版本
外部 Token 的 payload 采用 Zod schema 解析,由 external-token-extractor.ts 的externalTokenPayload()定义。z.union的排列顺序是v4 → v3 → v2(最具体优先),因为 v2 的 schema 会剥离未知键,若放在前面会"吞掉" v3/v4 的版本特定字段。
v1/v2(无version字段,legacy)
v2 在 v1 基础上扩展了role、嵌套pieces对象、并发池字段:
{ "externalUserId": "user_123", "externalProjectId": "project_456", "firstName": "Jane", "lastName": "Doe", "role": "EDITOR", "pieces": { "filterType": "ALLOWED", "tags": ["tag-a"] }, "concurrencyPoolKey": "pool-1", "concurrencyPoolLimit": 10 }role:可选的平台自定义项目角色名,缺省回退到DefaultProjectRole.EDITOR(见 external-token-extractor.ts 的getProjectRole);pieces.filterType与pieces.tags:legacy 的 Piece 范围表达;concurrencyPoolKey/concurrencyPoolLimit:可选并发池配置,二者必须同时出现才会生效(见服务端判空逻辑 managed-authn-service.ts)。
v3(version: "v3")
嵌套的pieces被拍平为顶层字段:
{ "version": "v3", "externalUserId": "user_123", "externalProjectId": "project_456", "firstName": "Jane", "lastName": "Doe", "piecesFilterType": "ALLOWED", "piecesTags": ["tag-a"] }v4(version: "v4")
pieceSet为必填,其值是 Piece Set 的key(即命名 Piece Set 的标识,不再是 tag 列表):
{ "version": "v4", "externalUserId": "user_123", "externalProjectId": "project_456", "firstName": "Jane", "lastName": "Doe", "pieceSet": "my-named-piece-set-key" }三个版本的解析分支见 external-token-extractor.ts 的extractPieces:v4 提取pieceSetKey;v3 提取piecesFilterType+piecesTags;v1/v2 从嵌套pieces提取;均无则回退到PiecesFilterType.NONE。
六、externalToken 交换流程:逐步骤源码解读
POST /v1/managed-authn/external-token的请求体仅含一个字段externalAccessToken,由共享包中的 managed-authn-requests.ts 定义(注释还提醒:改动该结构需同步更新 embed-sdk,因其不能有依赖)。服务端 managed-authn-service.ts 的externalToken方法按以下步骤执行:
步骤 1:校验外部 Token
externalTokenExtractor.extract()(见 external-token-extractor.ts):
- 先
jwtUtils.decode取出 header,若缺少kid直接抛出INVALID_BEARER_TOKEN(signing key id not found in the header); - 按
kid查库获取 Signing Key(含平台公钥); - 用
RS256(常量JwtSignAlgorithm.RS256,见 external-token-extractor.ts)验签并解析 payload,不校验 issuer(传入issuer: null); - 解析出
ExternalPrincipal,其中platformId取自Signing Key 所属平台,而不是 payload——这是防止跨平台冒用的关键设计。
验签失败的错误消息会原样透传回调用方,便于厂商排查。
步骤 2:Get or Create Project
按(platformId, externalProjectId)查找项目(managed-authn-service.ts):
- 命中则直接复用;
- 未命中则读取平台,创建一个
displayName = externalProjectId、owner 为平台 owner、type = ProjectType.TEAM且带externalId的项目。
步骤 3:可选地更新显示名与并发池
- 若 payload 含
projectDisplayName,则更新项目显示名(managed-authn-service.ts); - 若
concurrencyPoolKey与concurrencyPoolLimit同时非空,则 upsert 并发池并绑定到项目(managed-authn-service.ts)——这使厂商可以在外部 Token 中按项目指定并发上限。
步骤 4:应用 Piece 访问范围(无条件执行)
applyProjectPieceAccess在每次交换时都会无条件运行,此处没有managePiecesEnabled门控(managed-authn-service.ts)。其回退顺序为:
- 显式
pieceSetkey(v4)→ 按(platformId, key)查找命名 Piece Set,命中即assignProject; - 未命中 → 取legacy
piecesTags的第一个 tag(仅当filterType === ALLOWED,多 tag 场景未启用,每个 tag 对应一个 key = tagName 的命名 Set); - 仍无匹配(或 targetKey 为 undefined)→ 回退到平台的Default 默认 Piece Set,且当指定了 key 却查不到时会输出 warn 日志:
pieceSet key "<key>" not found — falling back to default。
值得注意的是,项目套餐(project plan)不会被写入——外部 Token 只影响 Piece 集合与并发池,不改变项目的计费套餐。
步骤 5:Get or Create User
getOrCreateUser(managed-authn-service.ts)按(platformId, externalId)查找用户:
- 存在则直接返回;
- 不存在则先
getOrCreateUserIdentity创建身份(邮箱 =sha256("managed_<platformId>_<externalUserId>")哈希值,密码为随机生成、provider: JWT、verified: true,见 managed-authn-service.ts),再创建platformRole: MEMBER的用户。
步骤 6:Upsert 成员关系并签发访问令牌
- 以
(projectId, userId, projectRoleName)upsert 项目成员,角色默认来自第 5 节解析的projectRole(缺省EDITOR); - 读取身份后调用
accessTokenManager.generateToken,签发7 天(7 * 24 * 60 * 60秒)的 Activepieces 访问令牌(managed-authn-service.ts); - 返回完整
AuthenticationResponse:包含id、platformRole、status、externalId、platformId、姓名、email(哈希邮箱)、trackEvents、newsLetter、verified、token与projectId(managed-authn-service.ts)。
控制器在返回前还会上报USER_SIGNED_UP事件,source: 'managed'(见 managed-authn-controller.ts),方便平台侧追踪嵌入式注册来源。
七、前端与 Embed SDK 侧:拿到 Token 之后
服务端返回的AuthenticationResponse由嵌入方前端接收。仓库中对应的 API 客户端是 managed-auth-api.ts 的managedAuthApi.generateApToken,它封装了对/v1/managed-authn/external-token的 POST 调用,并从@activepieces/shared引入ManagedAuthnRequestBody与AuthenticationResponse类型。
在嵌入路由中,该客户端被实际调用:见 embed/index.tsx(managedAuthApi.generateApToken({ ... }))。典型集成方式为:
- 厂商后端签发外部 JWT(见第五节 payload 结构);
- 厂商前端将 JWT 交给 Activepieces Embed SDK;
- SDK 调用
POST /v1/managed-authn/external-token换取 AP 访问令牌; - 携带该令牌以对应
projectId进入构建器,即完成了"免注册嵌入式登录"。
八、安全边界与已知注意事项(Gotchas)
- 端点是公开的:
POST /v1/managed-authn/external-token配置为securityAccess.public(),没有附加 API Key 或会话校验——JWT 签名本身就是全部安全。因此厂商必须确保私钥安全保管,并签发短时Token。 - 平台归属取自 Signing Key 而非 payload:
platformId由kid解析出的签名密钥决定,外部用户无法通过篡改 claims 进入其他平台。 - 外部用户没有真实邮箱:身份邮箱是
sha256("managed_<platformId>_<externalUserId>")的确定性哈希,不具备密码找回等邮件能力;UserIdentityProvider.JWT也表明其身份来源。 - 项目套餐不被写入:外部 Token 只控制 Piece Set 与并发池绑定,不修改项目 plan。
- v2 schema 会剥离未知键:
z.union必须保持 v4 → v3 → v2 的从前往后顺序,否则 v3/v4 的version特定字段会被静默丢弃,导致解析错误。 - 私钥只在创建时返回一次:
signingKeyService.add只持久化公钥,privateKey 一旦丢失需删除重建。
九、关键文件索引
| 层次 | 路径 | 职责 |
|---|---|---|
| 文档 | managed-auth.md | 本主题的核心知识笔记(路径已核实) |
| 服务端模块 | managed-authn-module.ts | 注册/v1/managed-authn前缀 |
| 服务端控制器 | managed-authn-controller.ts | 唯一的POST /external-token路由 |
| 服务端编排 | managed-authn-service.ts | 项目/用户/成员/Piece 范围/令牌签发 |
| JWT 校验 | external-token-extractor.ts | RS256 验签、v1/v2/v3/v4 payload 解析 |
| 签名密钥 | signing-key-module.ts | embeddingEnabled套餐门控 |
| 密钥生成 | signing-key-generator.ts | 4096 位 RSA、pkcs1 PEM |
| 共享契约 | managed-authn-requests.ts | 请求体结构(externalAccessToken) |
| 前端客户端 | managed-auth-api.ts | generateApTokenAPI 封装 |
| 注册入口 | app.ts | CLOUD / ENTERPRISE 版本挂载模块 |
十、总结
Managed Auth 是 Activepieces 嵌入式能力的认证基石:以 RSA 签名 JWT 作为信任根,通过kid解析平台归属,用externalUserId/externalProjectId实现用户与项目的确定性幂等映射,再以 v1–v4 多版本 payload 逐步演进 Piece 范围控制(从嵌套pieces到命名 Piece Set 的pieceSetkey),最后签发 7 天访问令牌供 Embed SDK 使用。理解其 Token 版本、回退语义与"端点公开、签名即安全"的边界,是正确、安全地将其接入自有 SaaS 产品的关键。
【免费下载链接】activepiecesAI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents项目地址: https://gitcode.com/GitHub_Trending/ac/activepieces
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考