嵌入式分析 SDK - 认证
要在生产环境中使用 SDK,您需要设置 SSO 认证。
如果您在本地开发,也可以使用 API 密钥 设置认证。
设置 JWT SSO
要设置 JWT SSO,您需要 Metabase Pro 版或企业版许可证(如果您没有许可证,请查看 此快速入门)
这是一个高层概览
1. 在 Metabase 中启用 JWT SSO
- 通过导航至 管理员设置 > 设置 > 认证,然后点击 设置 来配置 JWT
- 生成一个密钥并将其复制到剪贴板。
2. 在后端添加新端点以处理认证
您需要在后端添加一个库来签名您的 JSON Web Token。
对于 Node.js,我们推荐使用 jsonwebtoken
npm install jsonwebtoken --save
接下来,在您的后端设置一个端点(例如,/sso/metabase
),该端点使用您的 Metabase JWT 共享密钥为已认证用户生成 JWT。此端点必须返回一个包含签名 JWT 的 jwt
属性的 JSON 对象。例如:{ "jwt": "your-signed-jwt" }
。
此 Node.js 示例代码使用 Express 设置了一个端点
import express from "express";
import cors from "cors";
import session from "express-session";
import jwt from "jsonwebtoken";
import fetch from "node-fetch";
// Replace this with your Metabase URL
const METABASE_INSTANCE_URL = "YOUR_METABASE_URL_HERE";
// Replace this with the JWT signing secret you generated when enabling
// JWT SSO in your Metabase.
const METABASE_JWT_SHARED_SECRET = "YOUR_SECRET_HERE";
const app = express();
app.get("/sso/metabase", async (req, res) => {
// Usually, you would grab the user from the current session
// Here it's hardcoded for demonstration purposes
// Example:
// const { user } = req.session;
const user = {
email: "rene@example.com",
firstName: "Rene",
lastName: "Descartes",
group: "Customer",
};
if (!user) {
console.log("no user");
res.status(401).json({
status: "error",
message: "not authenticated",
});
return;
}
const token = jwt.sign(
{
email: user.email,
first_name: user.firstName,
last_name: user.lastName,
groups: [user.group],
exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minutes expiration
},
METABASE_JWT_SHARED_SECRET,
);
// The user backend should return a JSON object with the JWT.
res.status(200).json({ jwt: token });
});
使用 Next.js App Router 的示例
import jwt from "jsonwebtoken";
const user = {
email: "rene@example.com",
firstName: "Rene",
lastName: "Descartes",
group: "Customer",
};
const METABASE_JWT_SHARED_SECRET = process.env.METABASE_JWT_SHARED_SECRET || "";
const METABASE_INSTANCE_URL = process.env.METABASE_INSTANCE_URL || "";
export async function GET() {
const token = jwt.sign(
{
email: user.email,
first_name: user.firstName,
last_name: user.lastName,
groups: [user.group],
exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minutes expiration
},
// This is the JWT signing secret in your Metabase JWT authentication setting
METABASE_JWT_SHARED_SECRET,
);
// The user backend should return a JSON object with the JWT.
return Response.json({ jwt: token });
}
使用 Next.js Pages Router 的示例
import type { NextApiRequest, NextApiResponse } from "next";
import jwt from "jsonwebtoken";
const user = {
email: "rene@example.com",
firstName: "Rene",
lastName: "Descartes",
group: "Customer",
};
const METABASE_JWT_SHARED_SECRET = process.env.METABASE_JWT_SHARED_SECRET || "";
const METABASE_INSTANCE_URL = process.env.METABASE_INSTANCE_URL || "";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const token = jwt.sign(
{
email: user.email,
first_name: user.firstName,
last_name: user.lastName,
groups: [user.group],
exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minutes expiration
},
// This is the JWT signing secret in your Metabase JWT authentication setting
METABASE_JWT_SHARED_SECRET,
);
// The user backend should return a JSON object with the JWT.
res.status(200).json({ jwt: token });
}
使用同一端点处理交互式和 SDK 嵌入
如果您有一个现有后端端点已配置用于交互式嵌入,并希望将该端点也用于 SDK 嵌入,则可以通过检查 SDK 添加到其请求中的 response=json
查询参数来区分请求。
- 对于 SDK 请求,您应该返回一个包含 JWT 的 JSON 对象(
{ jwt: string }
)。 - 对于交互式嵌入请求,您将继续执行重定向。
这是一个同时处理两者的 Express.js 端点示例
import express from "express";
import jwt from "jsonwebtoken";
// Replace this with your Metabase URL
const METABASE_INSTANCE_URL = "YOUR_METABASE_URL_HERE";
// Replace this with the JWT signing secret you generated when enabling
// JWT SSO in your Metabase.
const METABASE_JWT_SHARED_SECRET = "YOUR_SECRET_HERE";
const app = express();
app.get("/sso/metabase", async (req, res) => {
// This is an example endpoint that can handle both traditional interactive
// embedding requests and SDK embedding requests.
// Detect if the request is coming from the SDK by checking for the
// 'response=json' query parameter added by the SDK.
const isSdkRequest = req.query.response === "json";
// Usually, you would grab the user from the current session
// Here it's hardcoded for demonstration purposes
// Example:
// const { user } = req.session;
const user = {
email: "rene@example.com",
firstName: "Rene",
lastName: "Descartes",
group: "Customer",
};
// Generate the JWT
const token = jwt.sign(
{
email: user.email,
first_name: user.firstName,
last_name: user.lastName,
groups: [user.group],
exp: Math.round(Date.now() / 1000) + 60 * 10, // 10 minutes expiration
},
METABASE_JWT_SHARED_SECRET,
);
if (isSdkRequest) {
// For SDK requests, return a JSON object with the JWT.
res.status(200).json({ jwt: token });
} else {
// For interactive embedding, construct the Metabase SSO URL
// and redirect the user's browser to it.
const ssoUrl = `${METABASE_INSTANCE_URL}/auth/sso?token=true&jwt=${token}`;
res.redirect(ssoUrl);
}
});
3. 在前端将 SDK 连接到新端点
更新前端代码中的 SDK 配置,使其指向您后端的认证端点。
const authConfig = defineMetabaseAuthConfig({
metabaseInstanceUrl: "https://your-metabase.example.com", // Required: Your Metabase instance URL
});
(可选)如果您使用标头而不是 Cookie 来认证从前端到后端的调用,则需要使用 自定义 fetch 函数。
如果您的前端和后端不共享域,则需要启用 CORS
您可以在后端添加一些中间件来处理跨域请求。
// Middleware
// If your FE application is on a different domain from your BE, you need to enable CORS
// by setting Access-Control-Allow-Credentials to true and Access-Control-Allow-Origin
// to your FE application URL.
//
// Limitation: We currently only support setting one origin in Authorized Origins in Metabase for CORS.
app.use(
cors({
credentials: true,
}),
);
app.use(
session({
secret: SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: { secure: false },
}),
);
app.use(express.json());
// routes
app.get("/sso/metabase", metabaseAuthHandler);
app.listen(PORT, () => {
console.log(`API running at http://localhost:${PORT}`);
});
自定义 JWT 认证
您可以通过 defineMetabaseAuthConfig
函数指定 fetchRefreshToken
函数来定制 SDK 获取刷新令牌的方式
// Pass this configuration to MetabaseProvider.
// Wrap the fetchRequestToken function in useCallback if it has dependencies to prevent re-renders.
const authConfig = defineMetabaseAuthConfig({
fetchRequestToken: async () => {
const response = await fetch(
"https://{{ YOUR_CLIENT_HOST }}/api/metabase/auth",
{
method: "GET",
headers: { Authorization: `Bearer ${yourToken}` },
},
);
// The backend should return a JSON object with the shape { jwt: string }
return await response.json();
},
metabaseInstanceUrl: "http://localhost:3000",
});
响应应为 { jwt: "{JWT_TOKEN}" }
的形式
使用 SAML SSO 认证
要在嵌入式分析 SDK 中使用 SAML 单点登录,您需要在 Metabase 和身份提供商 (IdP) 中都设置 SAML。请参阅 基于 SAML 的认证 文档。
在 Metabase 和您的 IdP 中配置 SAML 后,您可以通过将 MetabaseAuthConfig
中的 preferredAuthMethod
设置为 "saml"
来配置 SDK 使用 SAML
// Pass this configuration to MetabaseProvider.
const authConfig = defineMetabaseAuthConfig({
metabaseInstanceUrl: "http://localhost:3000",
preferredAuthMethod: "saml",
});
在嵌入式分析 SDK 中使用 SAML 认证通常会涉及将用户重定向到带有身份提供商登录页面的弹出窗口进行认证。成功认证后,用户将被重定向回嵌入内容。
由于 SAML 流程涉及重定向和弹出窗口的特性,SDK 的 SAML 认证可能无法在所有嵌入上下文中无缝工作,特别是在 iframe 内部,具体取决于浏览器安全策略和 IdP 的配置。我们建议您在目标环境中测试认证流程。
与 JWT 认证不同,当 SAML 与 SDK 配对使用时,您将无法在后端实现自定义的 fetchRequestToken
函数。
如果同时启用 SAML 和 JWT,SDK 将默认使用 SAML
您可以通过在认证配置中设置 preferredAuthMethod="jwt"
来覆盖此默认行为,优先选择 JWT 认证方法
authConfig: {
metabaseInstanceUrl: "...",
preferredAuthMethod: "jwt",
// other JWT config...
}
获取 Metabase 认证状态
您可以使用 useMetabaseAuthStatus
钩子查询 Metabase 认证状态。如果您想在用户未认证时完全隐藏 Metabase 组件,这会很有用。
此钩子只能在 MetabaseProvider
包装的组件中使用。
const auth = useMetabaseAuthStatus();
if (auth.status === "error") {
return <div>Failed to authenticate: {auth.error.message}</div>;
}
if (auth.status === "success") {
return <InteractiveQuestion questionId={110} />;
}
使用 API 密钥进行本地认证
嵌入式分析 SDK 仅在生产环境中支持 JWT 认证。API 密钥认证仅支持本地开发和评估目的。
在本地开发并试用 SDK 时,您可以使用 API 密钥进行认证。
首先,创建一个 API 密钥。
然后,您可以使用 API 密钥在您的应用程序中对 Metabase 进行认证。您只需在配置对象中包含您的 API 密钥,使用键:apiKey
。
import {
MetabaseProvider,
defineMetabaseAuthConfig,
} from "@metabase/embedding-sdk-react";
const authConfigApiKey = defineMetabaseAuthConfig({
metabaseInstanceUrl: "https://metabase.example.com",
apiKey: "YOUR_API_KEY",
});
export default function App() {
return (
<MetabaseProvider authConfig={authConfigApiKey} className="optional-class">
Hello World!
</MetabaseProvider>
);
}
安全警告:每个终端用户必须拥有自己的 Metabase 账户
每个终端用户必须拥有自己的 Metabase 账户。
终端用户共享 Metabase 账户的问题在于,即使您通过 SDK 在客户端筛选数据,所有终端用户仍然可以访问会话令牌,他们可以使用该令牌直接通过 API 访问 Metabase,从而获取他们不应该看到的数据。
但是,如果每个终端用户都有自己的 Metabase 账户,您可以在 Metabase 中配置权限,这样每个人都只能访问他们应该访问的数据。
除此之外,我们认为共享账户是不公平的使用。SDK 的公平使用涉及为嵌入式分析的每个终端用户提供自己的 Metabase 账户。
SDK 54 或更低版本上的 JWT SSO 设置升级指南
如果您正在从 SDK 1.54.x 或更低版本升级并使用 JWT SSO,您需要进行以下更改。
前端更改:
- 从所有
defineMetabaseAuthConfig
调用中移除authProviderUri
- 如果使用自定义
fetchRequestToken
: 更新函数签名并硬编码认证端点 URL
后端更改:
此外,如果您已设置 SAML,但更希望使用 JWT SSO,则需要设置 首选认证方法。
从认证配置中移除 authProviderUri
defineMetabaseAuthConfig
不再接受 authProviderUri
参数,因此您需要将其移除。
Metabase 中的管理员设置更改:
在管理员设置 > 认证 > JWT SSO 中,将 JWT Identity Provider URI
设置为您的 JWT SSO 端点 URL,例如 http://localhost:9090/sso/metabase
。
之前
const authConfig = defineMetabaseAuthConfig({
metabaseInstanceUrl: "https://your-metabase.example.com",
authProviderUri: "http://localhost:9090/sso/metabase", // Remove this line
});
之后
const authConfig = defineMetabaseAuthConfig({
metabaseInstanceUrl: "https://your-metabase.example.com",
});
SDK 现在使用在 Metabase 管理员设置(管理员 > 设置 > 认证 > JWT)中配置的 JWT 身份提供商 URI 设置。
更新 fetchRequestToken
函数签名
fetchRequestToken
函数不再接收 URL 参数。您现在必须在函数中直接指定您的认证端点。
之前
const authConfig = defineMetabaseAuthConfig({
fetchRequestToken: async (url) => {
// Remove url parameter
const response = await fetch(url, {
method: "GET",
headers: { Authorization: `Bearer ${yourToken}` },
});
return await response.json();
},
metabaseInstanceUrl: "http://localhost:3000",
authProviderUri: "http://localhost:9090/sso/metabase", // Remove this line
});
之后
const authConfig = defineMetabaseAuthConfig({
fetchRequestToken: async () => {
// No parameters
const response = await fetch("http://localhost:9090/sso/metabase", {
// Hardcode your endpoint URL
method: "GET",
headers: { Authorization: `Bearer ${yourToken}` },
});
return await response.json();
},
metabaseInstanceUrl: "http://localhost:3000",
});
更新您的 JWT 端点以处理 SDK 请求
您的 JWT 端点现在必须同时处理 SDK 请求和交互式嵌入请求。SDK 会添加一个 response=json
查询参数来区分其请求。对于 SDK 请求,返回一个包含 JWT 的 JSON 对象。对于交互式嵌入,则继续像以前一样重定向。
如果您使用的是自定义的 fetchRequestToken
,则需要更新端点以检测 SDK 请求中的 req.query.response === "json"
。
app.get("/sso/metabase", async (req, res) => {
// SDK requests include 'response=json' query parameter
const isSdkRequest = req.query.response === "json";
const user = getCurrentUser(req);
const token = jwt.sign(
{
email: user.email,
first_name: user.firstName,
last_name: user.lastName,
groups: [user.group],
exp: Math.round(Date.now() / 1000) + 60 * 10,
},
METABASE_JWT_SHARED_SECRET,
);
if (isSdkRequest) {
// For SDK requests, return JSON object with jwt property
res.status(200).json({ jwt: token });
} else {
// For interactive embedding, redirect as before
const ssoUrl = `${METABASE_INSTANCE_URL}/auth/sso?token=true&jwt=${token}`;
res.redirect(ssoUrl);
}
});
阅读其他 Metabase 版本 的文档。