关于 metabase-embedding-sso-implementation
metabase-embedding-sso-implementation 是一项分步骤技能,用于在现有应用中为 Metabase 嵌入添加 JWT 单点登录。它要求通过应用现有认证保护令牌签发、由后端代理请求,并依据对应版本文档验证实现。
主要功能
- 01支持三种 SSO 嵌入类型
- 02将已认证用户映射为 JWT 声明
- 03添加服务端 SSO 端点
- 04配置前端认证
- 05验证安全性和实现变更
适用场景
- 01为现有 Metabase 嵌入添加 JWT SSO
- 02将宿主应用用户关联到 Metabase 用户组
- 03审查嵌入改动中的认证与密钥处理
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ metabase/ agent-skills --skill metabase-embedding-sso-implementation安装后在 Claude Code 中输入/metabase-embedding-sso-implementation确认已加载。
详细介绍
执行约定
按顺序遵循工作流步骤,不要跳过任何步骤。先创建检查清单,然后执行每个步骤,并明确标记为已完成,同时提供证据。每个步骤的输出都会用于下一步,因此跳过步骤会导致实现不正确。
如果因信息缺失或工具故障而无法完成某个步骤,你必须:
- 将该步骤记录为 ❌ blocked,
- 明确说明缺少了什么/发生了什么故障,
- 停止(不要继续后续步骤)。
每个工作流步骤必须以 Status: ✅ complete 或 Status: ❌ blocked 结束。步骤必须按顺序进行:只有前一步完成后,才能开始下一步。每个步骤都必须包含证据(检测到的代码模式、文件路径、应用的差异、通过/失败结果)。
架构一致性
遵循应用现有的架构、模板引擎、布局/局部模板系统、代码风格和路由模式。不要切换范式(例如,将模板改为内联 HTML,反之亦然)。如果应用使用中间件提供共享模板变量,应优先使用,而不是在路由处理程序中重复定义。
JWT SSO 端点必须集成应用现有的身份验证系统。该端点只能为已通过宿主应用身份验证的用户签发 Metabase JWT。绝不能创建无需先验证用户会话就签发令牌的端点。
发往 Metabase 实例的 SSO 请求必须通过应用后端代理(FE → BE → Metabase /auth/sso)。这样可以避免将 Metabase 实例 URL 和 JWT 令牌暴露给客户端、避免 CORS 问题,并确保始终在服务端验证身份验证。JWT 共享密钥绝不能暴露给前端。
重要性能说明
- 在每个步骤中尽量实现并行处理。在同一条消息中尽可能并行调用 Grep/Glob/Read。
- 扫描项目时不要使用子代理——结果必须保留在主上下文中,以便在后续步骤中交叉核对。
- 不要解析仓库分支、提交、PR 或议题。
范围
此技能为 Metabase 嵌入实现 JWT SSO 身份验证。它支持所有三种使用 SSO 的嵌入类型:
| 嵌入类型 | 交付机制 | 前端身份验证配置 |
|---|---|---|
| 模块化嵌入(embed.js) | Web 组件(<metabase-dashboard> 等) | window.metabaseConfig — 可用的身份验证字段请参阅获取的文档 |
模块化嵌入 SDK(@metabase/embedding-sdk-react) | React 组件(<InteractiveDashboard> 等) | defineMetabaseAuthConfig() — 可用的身份验证字段请参阅获取的文档 |
| 完整应用嵌入 | 带完整 Metabase UI 的 iframe | iframe src 指向 SSO 端点 |
SSO 端点的响应格式(JSON、重定向、代理到 Metabase /auth/sso 等)因嵌入类型和 Metabase 版本而异——请查阅获取的文档以确定正确的行为。
使用者的应用后端可能采用任意语言(Node.js、Python、Ruby、PHP、Java、Go、.NET 等)。除非在步骤 1 中检测到特定语言,否则说明应与语言无关。
此技能的处理范围
- 创建 JWT 签名端点,将应用中通过身份验证的用户映射到 Metabase JWT 字段(
email、first_name、last_name、groups、exp) - 根据检测到的嵌入类型配置前端身份验证层
- 添加
METABASE_JWT_SHARED_SECRET环境变量 - 如果尚未安装 JWT 签名库,则进行安装
- 提供 Metabase 管理员配置说明(JWT 设置、组映射、CORS)
此技能不处理的内容
- 设置嵌入本身(Web 组件、SDK 或 iframe)——请使用迁移技能
- 升级嵌入版本——请使用
metabase-modular-embedding-version-upgrade技能 - Guest embeds 身份验证(使用
METABASE_SECRET_KEY和{resource, params}载荷,而非 SSO) - SAML 或 LDAP 身份验证——此技能仅涵盖 JWT SSO
JWT 载荷结构(SSO)
使用 METABASE_JWT_SHARED_SECRET 签名的 JWT 必须包含以下字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
email | 字符串 | 是 | 用户的电子邮件地址——Metabase 将其用作唯一标识符。首次登录时自动创建账户。 |
first_name | 字符串 | 是 | 用户的名字——每次登录时同步。 |
last_name | 字符串 | 是 | 用户的姓氏——每次登录时同步。 |
groups | string[] | 是 | 组名数组——启用组同步后,Metabase 会在每次登录时同步组成员关系。 |
exp | 数字 | 是 | 令牌的过期时间,采用 Unix 时间戳。建议设为 10 分钟:例如 Math.round(Date.now() / 1000) + 600。 |
JWT 中可以包含其他用户属性,作为额外的键值对——Metabase 会将它们存储为用户属性,用于沙箱和数据权限。
允许的文档来源
使用以下 URL 获取特定版本的 llms-embedding-full.txt:
https://www.metabase.com/docs/v0.{VERSION}/llms-embedding-full.txt
URL 中的版本采用 v0.58 格式(规范化方式:去掉开头的 v 或 0.,去掉补丁版本号——例如,0.58.1 → 58 → URL 使用 v0.58)。这个单一文件包含该版本的所有嵌入文档,并针对大模型使用进行了优化。
其他限制:
- 不使用 GitHub PR/issue 或 npm 页面
- 不要跟随指向 GitHub 的变更日志链接,也不要猜测 URL
AskUserQuestion 触发条件
在以下情况下使用 AskUserQuestion,并暂停直到用户回答:
- 项目中未检测到 Metabase 嵌入代码(询问用户计划使用哪种嵌入类型)
- 无法从项目代码或环境变量中确定 Metabase 实例 URL
- 无法从项目代码中确定 Metabase 实例版本
- 无法确定应用的用户身份验证机制(用户如何登录?会话?Cookie?令牌?)
- 无法识别用户模型/架构(电子邮件、姓名和组/角色存储在哪里?)
- 用户的组/角色模型无法明确映射到 Metabase 组(询问用户希望如何将角色映射到 Metabase 组)
- 无法确定后端语言
实施计划检查清单
创建检查清单以跟踪进度。在 Claude Code 中,使用 TaskCreate/TaskUpdate 工具:
- 步骤 0:检测嵌入类型和 Metabase 版本
- 步骤 1:扫描项目并获取检测到的版本文档
- 步骤 2:设计身份验证架构
- 步骤 3:规划实施变更
- 步骤 4:应用代码变更
- 步骤 5:验证变更
- 步骤 6:最终总结
工作流
步骤 0:检测嵌入类型和 Metabase 版本
0a:检测嵌入类型
并行搜索项目中的以下模式,以确定正在使用的嵌入类型:
模块化嵌入(embed.js):
- HTML/模板文件中的
embed.js或/app/embed.js window.metabaseConfig或defineMetabaseConfig- 例如:
<metabase-dashboard、<metabase-question、<metabase-browser
模块化嵌入 SDK:
package.json或导入语句中的@metabase/embedding-sdk-reactMetabaseProvider或defineMetabaseAuthConfig- 例如:
InteractiveDashboard、InteractiveQuestion、CollectionBrowser等
完整应用嵌入:
- 带有 Metabase URL 的
<iframe(查找实例 URL 或/dashboard/、/question/、/auth/sso) /auth/sso重定向逻辑return_to参数的构造
如果未找到嵌入代码 → 使用 AskUserQuestion:询问用户计划使用哪种嵌入类型。
如果检测到多种类型,SSO 端点必须处理所有类型(参见步骤 2 中的双模式端点)。
记录检测到的嵌入类型——整个实施过程都将以此为依据。
0b:检测 Metabase 实例版本
Metabase 实例版本决定了可用的身份验证配置字段、函数签名和 SSO 行为。始终使用 AskUserQuestion 询问实例版本——即使 Docker 标签、环境变量或 package.json 中显示了版本,也要向用户确认。获取文档时,以实例版本为准。
SDK/实例版本不匹配检查:如果项目使用 @metabase/embedding-sdk-react(npm 包),请比较 SDK 包版本与实例版本。SDK 包版本必须与实例版本匹配——例如,SDK v0.52.x 要求实例为 v0.52.x。如果两者不匹配,请停止操作,并告知用户先统一版本,再继续配置 SSO。建议使用 metabase-modular-embedding-version-upgrade 技能升级。
步骤 1:扫描项目并获取文档
并行执行项目扫描和文档获取——两者相互独立。只要没有依赖关系,就在同一条消息中并行调用工具。
1a:获取 Metabase 实例版本对应的文档
获取 Step 0b 中确认的实例版本对应的 llms-embedding-full.txt(URL 格式见“允许使用的文档来源”)。在其中搜索 fetchRequestToken、authProviderUri、jwtProviderUri、defineMetabaseAuthConfig、JWT 和 SSO,查找相关的身份验证文档。
这些文档是关于检测到的版本的身份验证配置选项、函数签名、已弃用字段和端点响应格式的权威来源。设计身份验证架构时,在步骤 2 中使用这些文档。
与下面的项目扫描步骤并行执行。
1b:识别后端语言和框架
- 检查依赖/构建文件(
package.json、requirements.txt、Gemfile、pom.xml、go.mod、composer.json等)。 - 识别 Web 框架(Express、Fastify、Flask、Django、Rails、Spring、Gin、Laravel、ASP.NET 等)。
- 记录语言、框架和任何中间件模式。
1c:查找现有身份验证代码
这至关重要——SSO 端点必须与应用现有的身份验证集成。搜索以下内容:
- 会话中间件(
express-session、cookie-session、flask-login、devise、Passport、next-auth等) - 身份验证中间件模式(
req.user、req.session、current_user、@login_required、[Authorize]等) - 用户模型/架构定义(查找字段:
email、name、first_name、last_name、role、group等) - 登录路由(
/login、/auth、/signin、/api/auth等) - 基于令牌/ Cookie 的身份验证(JWT 验证中间件、Cookie 解析器等)
对于每个匹配的文件,请读取完整内容。
1d:查找现有 Metabase 嵌入代码
读取步骤 0a 中找到的所有文件。提取:
- Metabase 实例 URL(环境变量或硬编码值)
- 当前身份验证配置(如果有——可能已配置了部分 SSO)
- 配置 embed.js 或 metabaseConfig 的布局/头部文件
- Metabase 是否已有 SSO/JWT 端点
- 与 Metabase 相关的环境变量(
METABASE_、MB_)
1e:查找用户模型和群组/角色结构
搜索用户模型,了解可用字段:
- 包含用户字段的数据库架构文件、ORM 模型或类型定义
- 群组/角色定义(枚举、数据库表、常量)
- 当前用户如何附加到请求(例如,
req.user、会话存储)
输出:结构化项目清单
Backend: {language}, {framework}
Auth system: {mechanism} (e.g., express-session + Passport, next-auth, Django sessions)
User model: {file}:{line} — fields: {email, first_name, last_name, role, ...}
User on request: {how to access} (e.g., req.user, request.user, session[:user])
Groups/roles: {source} — values: {list of group/role names}
Embedding type: {modular | sdk | full-app | multiple}
Metabase URL: {env var or value}
Metabase version: {version}
Existing SSO endpoint: {file}:{line} or "none"
Existing auth config: {description} or "none"
步骤 2:设计身份验证架构(仅在步骤 1 ✅ 完成后)
根据项目清单,设计 SSO 实现方案。此步骤仅产出设计——暂不修改代码。
2a:JWT 端点设计
设计 SSO 端点路由:
-
路由:选择符合应用现有模式的路由。常见选项:
/sso/metabase、/api/auth/metabase、/auth/metabase-sso。遵循应用现有的路由命名约定。 -
HTTP 方法:GET(Metabase 的 SDK/embed.js 会向 JWT 提供方 URI 发送 GET 请求)
-
身份验证保护:端点必须由应用现有的身份验证中间件保护。只有通过身份验证的用户才能获得 Metabase JWT。根据步骤 1c 的发现,指定要使用的中间件/装饰器。
-
用户映射:定义如何从请求中提取用户字段:
email ← {source} (e.g., req.user.email) first_name ← {source} (e.g., req.user.firstName or req.user.name.split(' ')[0]) last_name ← {source} (e.g., req.user.lastName or req.user.name.split(' ')[1]) groups ← {source} (e.g., [req.user.role] or req.user.groups) -
组映射策略:如果应用的角色/组与所需的 Metabase 组不直接匹配,请定义映射:
App role "admin" → Metabase group "Administrators" App role "analyst" → Metabase group "Analysts" App role "viewer" → Metabase group "Viewers"如果映射不明确 → 使用 AskUserQuestion 询问。
-
令牌过期时间:10 分钟(例如
Math.round(Date.now() / 1000) + 600),除非应用有应当匹配的特定会话超时时间。
2b:端点响应行为
查阅获取的文档,确定对于检测到的嵌入类型和 Metabase 版本,SSO 端点应返回什么。响应格式(包含已签名 JWT 的 JSON、重定向到 Metabase /auth/sso、服务器端代理等)有所不同——不要假设存在固定模式。文档会说明,对于检测到的版本,SDK/embed.js/iframe 预期如何接收身份验证令牌。
如果项目使用多种嵌入类型,端点可能需要支持多种响应模式(例如,通过查询参数区分)。查阅文档,了解每种嵌入类型如何调用 JWT 提供方。
2c:前端身份验证配置
根据嵌入类型决定前端身份验证方案。查阅步骤 1a 获取的文档,确定检测到的版本支持哪些身份验证配置字段——不同版本的字段名称、签名和支持的选项各不相同。具体代码模式将在步骤 3d 中说明。
2d:JWT 库选择
如果项目尚未使用 JWT 库:
| 语言 | 库 | 安装命令 |
|---|---|---|
| Node.js | jsonwebtoken | npm install jsonwebtoken |
| Python | PyJWT | pip install PyJWT |
| Ruby | jwt | gem install jwt |
| PHP | firebase/php-jwt | composer require firebase/php-jwt |
| Java | io.jsonwebtoken:jjwt | 添加到 Maven/Gradle |
| Go | github.com/golang-jwt/jwt/v5 | go get github.com/golang-jwt/jwt/v5 |
| .NET | System.IdentityModel.Tokens.Jwt | dotnet add package System.IdentityModel.Tokens.Jwt |
如果项目中已存在 JWT 库,请使用该库。
步骤 3:规划实现变更(仅在步骤 2 ✅ 完成后)
创建完整的逐文件变更计划。每项变更都应注明目标文件、旧代码(如需修改)以及新代码。
3a:环境变量
- 将
METABASE_JWT_SHARED_SECRET添加到项目的环境配置中(.env、.env.example、docker-compose.yml,或定义其他 Metabase 环境变量的位置) - 值:占位字符串,并添加注释说明其必须与 Metabase 管理员中生成的密钥一致
3b:JWT 库安装
- 如果项目中不存在 JWT 库:注明安装命令
- 如果已存在:跳过此步骤
3c:SSO 端点实现
明确新端点的具体代码:
- 文件:添加路由的位置(现有路由文件;如果应用按功能拆分路由,则新建文件)
- 应用于该路由的身份验证中间件
- 用户字段提取(根据步骤 2a 中的映射)
- 使用
METABASE_JWT_SHARED_SECRET签署 JWT - 步骤 2b 确定的响应格式(依据获取的文档)
该端点必须:
- 从环境变量读取
METABASE_JWT_SHARED_SECRET - 从请求中提取经过身份验证的用户(使用应用现有的身份验证机制)
- 构建包含
email、first_name、last_name、groups、exp的 JWT 负载 - 使用 HS256 算法签署令牌(Metabase 的默认算法)
- 返回适用于嵌入类型的响应
该端点不得:
- 接受请求正文或查询参数中的用户身份(这是安全漏洞,用户可能伪造身份)
- 在未检查身份验证的情况下签发令牌
- 将用户信息硬编码
- 使用其他签名算法,除非 Metabase 管理员中进行了专门配置
3d:前端身份验证配置
以步骤 1a 获取的文档作为目标 Metabase 版本的身份验证配置字段、函数签名和已弃用选项的权威来源。配置字段、参数签名和已弃用选项会随版本变化;未经文档确认,不要假设某个特定字段存在。
模块化嵌入(embed.js):
- 检查文档,确认检测到的版本中
window.metabaseConfig支持哪些与身份验证相关的字段(例如,jwtProviderUri可能可用,也可能不可用)。 - 如果文档中未列出前端身份验证配置字段,则只能在 Metabase 管理员设置中配置 JWT 身份提供程序 URI。
模块化嵌入 SDK:
- 检查文档中的
defineMetabaseAuthConfig选项和fetchRequestToken函数签名,尤其要确认该函数是否接收参数。 - 除非文档另有说明,否则应在
fetchRequestToken函数体中硬编码端点 URL。 - 移除现有代码使用的所有已弃用身份验证配置字段(例如,较新版本已移除
authProviderUri)。检测到的版本对应的文档是判断有效字段的唯一依据。
完整应用嵌入:
- 更新 iframe
src,使其通过 SSO 端点或 Metabase 的/auth/sso路径,并使用return_to参数,其中包含经过 URL 编码的目标路径(例如,%2Fdashboard%2F1)。
3e:移除仅用于开发和已弃用的身份验证配置(如存在)
移除检测到的版本文档中未列出的所有身份验证配置字段。常见示例:
apiKey—— API 密钥仅用于本地开发,不适用于生产环境中的 SSOuseExistingUserSession—— 使用管理员的浏览器会话,不适用于生产环境- 现有代码中存在但检测到的版本文档中未列出的其他字段(这些字段可能已弃用或移除)
3f:Metabase 管理员配置说明(手动步骤)
将以下内容列入计划中,以便纳入最终摘要:
- 启用 JWT 身份验证:Admin > Settings > Authentication > JWT > enable
- 设置 JWT 签名密钥:粘贴与
METABASE_JWT_SHARED_SECRET相同的值 - 设置 JWT 身份提供程序 URI:填写 SSO 端点的完整 URL(例如,
http://localhost:9090/sso/metabase)——检查获取的文档,确认在检测到的版本中此项是必需还是可选(这取决于前端配置是否支持 JWT 提供程序字段)。 - 配置群组同步:
- 启用 "Synchronize Group Memberships"
- 在 Metabase 中创建匹配的群组,或者在名称不同的情况下设置群组映射
- 配置 CORS(仅限模块化嵌入):Admin > Embedding > Modular embedding > 添加宿主应用的域名
- SameSite Cookie 设置(用于跨域部署):Admin > Embedding > 设置为 "None"(需要 HTTPS)
步骤 4:应用代码更改(仅在完成步骤 3 后 ✅)
按以下顺序应用步骤 3 中的所有更改:
- 添加环境变量(步骤 3a)
- 如有需要,安装 JWT 库(步骤 3b)
- 创建 SSO 端点(步骤 3c)——这是核心更改
- 配置前端身份验证(步骤 3d)
- 如存在,仅移除开发环境专用身份验证(步骤 3e)
约束:
- 对现有文件进行每项更改时,使用 Edit 工具并精确指定
old_string/new_string - 仅对新文件使用 Write 工具
- 不要更改应用现有的身份验证系统——只在其基础上添加 Metabase SSO 层
- 不要更改项目中已存在的环境变量名称
- 如果某个文件需要多处编辑,请从上到下依次应用,以避免偏移问题
步骤 5:验证更改(仅在完成步骤 4 后 ✅)
执行以下所有检查。每项检查都应明确给出通过或失败的结果。
5a:SSO 端点存在且受身份验证保护
读取 SSO 端点文件。验证:
- 路由存在,且路径与计划一致
- 已应用身份验证中间件(端点受到保护)
- JWT 负载包含所有必需字段:
email、first_name、last_name、groups、exp - 使用来自环境变量的
METABASE_JWT_SHARED_SECRET签署令牌 - 响应格式符合获取到的文档对该嵌入类型的说明
通过标准:端点完整且受身份验证保护。
5b:已配置前端身份验证
验证前端身份验证配置仅使用检测到的版本文档中列出的字段。不得保留任何已弃用或已移除的字段。
- embed.js:
window.metabaseConfig身份验证字段符合文档支持的内容 - SDK:
defineMetabaseAuthConfig选项和fetchRequestToken签名符合文档 - 完整应用:iframe src 通过 SSO 路由
通过标准:前端身份验证符合嵌入类型和检测到的版本文档要求。
5c:不存在开发环境专用或已弃用的身份验证
使用 Grep 在整个项目文件中搜索 useExistingUserSession(排除 node_modules、.git)。还要在 metabaseConfig 或 defineMetabaseAuthConfig 附近搜索 apiKey——单独搜索 apiKey 范围太广,会匹配到无关代码。还要验证不存在已弃用的配置字段(与检测到的版本文档进行对照)。
通过标准:不存在 Metabase 专用的开发环境专用或已弃用身份验证方法。
5d:JWT 库可用
检查项目依赖项中是否包含 JWT 库(例如,package.json、requirements.txt)。
通过标准:依赖项中已列出该库,或该库已存在。
5e:抽查端点代码
验证端点不会:
- 从请求正文或查询参数中接受用户身份信息
- 在没有身份验证中间件的情况下签发令牌
- 将用户信息硬编码
- 使用
METABASE_JWT_SHARED_SECRET以外的签名密钥
通过标准:所有安全检查均通过。
如果任何检查失败:
- 立即修复问题
- 重新运行相关检查
- 如果尝试 3 次后仍无法修复,请将步骤 5 标记为 ❌ 受阻,并报告失败的检查及原因
第 6 步:输出摘要
将最终输出整理为以下几个部分:
- 已应用的更改:列出所有已修改/创建的文件,并用一句话描述每项更改
- SSO 端点:路由路径、HTTP 方法、响应行为,以及保护该端点的身份验证中间件
- JWT 负载映射:用表格展示应用用户字段如何映射到 Metabase JWT 字段:
| Metabase field | Source | Example value | |---|---|---| | email | req.user.email | "[email protected]" | | first_name | req.user.firstName | "Jane" | | last_name | req.user.lastName | "Doe" | | groups | [req.user.role] | ["Analyst"] | | exp | Date.now()/1000 + 600 | 1700000600 | - 组映射:说明应用角色/组如何映射到 Metabase 组
- 所需的手动步骤(第 3f 步中的 Metabase 管理员配置):
- 启用 JWT 身份验证并设置签名密钥
- 设置 JWT Identity Provider URI
- 配置组同步和映射
- 配置 CORS(如果使用模块化嵌入)
- 设置 SameSite cookie(如果跨域)
- 安全说明:
- 该端点由
{middleware}保护——只有经过身份验证的用户才能获取 Metabase JWT METABASE_JWT_SHARED_SECRET必须保密,并且必须与 Metabase 管理员配置中的值一致- 每位用户都会获得自己的 Metabase 账户(首次登录时自动开通)
- 令牌过期时间设为 {N} 分钟
- 该端点由
重试策略
文档获取:
- 如果获取
llms-embedding-full.txt时返回 404,请确认 Metabase 版本号并重试。如果仍然失败,将第 1 步标记为 ❌ 受阻。
验证:
- 如果 AskUserQuestion 未得到回答,则该步骤保持受阻状态——不要猜测或依据假设继续操作。
- 如果第 5 步中的任何验证检查在 3 次修复尝试后仍失败,将第 5 步标记为 ❌ 受阻,并报告失败的检查及原因。