嵌入式分析 SDK - 身份验证
嵌入式分析 SDK 仅在 Pro 和 Enterprise 计划(包括自托管和 Metabase Cloud)中可用。
为了在生产环境中使用 SDK,您需要设置 SSO 身份验证。
如果您正在本地开发,您也可以设置 API 密钥 身份验证。
设置 JWT SSO
要设置 JWT SSO,您需要 Metabase Pro 或 Enterprise 许可证(如果您没有许可证,请查看 此快速入门指南)。
以下是高级概述
1. 在您的 Metabase 中启用 JWT SSO
- 通过转到 **管理员设置** > **设置** > **身份验证** 并点击 **设置** 来配置 JWT。
- 输入 JWT 身份提供商 URI,例如
https://:9090/sso/metabase。这是您将在后端添加的新端点,用于处理身份验证。 - 生成一个密钥并将其复制到剪贴板。
2. 在您的后端添加一个新端点以处理身份验证
您需要在后端添加一个库来签名您的 JSON Web Tokens。
对于 Node.js,我们推荐 jsonwebtoken
npm install jsonwebtoken --save
接下来,在您的后端设置一个端点(例如,/sso/metabase),该端点使用您的 Metabase JWT 共享密钥为已认证用户生成 JWT。此端点必须返回一个带有 jwt 属性的 JSON 对象,其中包含已签名的 JWT。 例如:{ "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 https://:${PORT}`);
});
自定义 JWT 身份验证
您可以通过使用 defineMetabaseAuthConfig 函数指定 fetchRequestToken 函数来定制 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: "https://:3000",
});
响应应为 { jwt: "{JWT_TOKEN}" } 的形式
使用 SAML SSO 进行身份验证
要将 SAML 单点登录与嵌入式分析 SDK 结合使用,您需要在 Metabase 和您的身份提供商 (IdP) 中都设置 SAML。请参阅关于 基于 SAML 的身份验证 的文档。
在 Metabase 和您的 IdP 中配置 SAML 后,您可以通过将 MetabaseAuthConfig 中的 preferredAuthMethod 设置为 "saml" 来配置 SDK 使用 SAML。
// Pass this configuration to MetabaseProvider.
const authConfig = defineMetabaseAuthConfig({
metabaseInstanceUrl: "https://:3000",
preferredAuthMethod: "saml",
});
通常,使用嵌入式分析 SDK 进行 SAML 身份验证会涉及将用户重定向到弹出窗口中的身份提供商登录页面进行身份验证。成功身份验证后,用户将被重定向回嵌入的内容。
由于 SAML 流程中涉及的重定向和弹出窗口的性质,SDK 的 SAML 身份验证在所有嵌入式上下文中可能无法无缝工作,尤其是在 iframe 中,具体取决于浏览器安全策略和您的 IdP 的配置。我们建议在您的目标环境中测试身份验证流程。
与 JWT 身份验证不同,您无法在后端实现自定义的 fetchRequestToken 函数来配合 SAML 与 SDK。
如果同时启用了 SAML 和 JWT,SDK 将默认为 SAML
您可以通过在身份验证配置中设置 preferredAuthMethod="jwt" 来覆盖此默认行为,以偏好 JWT 身份验证方法。
authConfig: {
metabaseInstanceUrl: "...",
preferredAuthMethod: "jwt",
// other JWT config...
}
获取 Metabase 身份验证状态
您可以使用 useMetabaseAuthStatus hook 查询 Metabase 身份验证状态。如果您希望在用户未进行身份验证时完全隐藏 Metabase 组件,这将非常有用。
此 hook 只能在被 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 身份验证。您只需在配置对象中使用 apiKey 键包含您的 API 密钥即可。
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 身份提供商 URI 设置为您的 JWT SSO 端点的 URL,例如 https://:9090/sso/metabase。
之前
const authConfig = defineMetabaseAuthConfig({
metabaseInstanceUrl: "https://your-metabase.example.com",
authProviderUri: "https://: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: "https://:3000",
authProviderUri: "https://:9090/sso/metabase", // Remove this line
});
之后
const authConfig = defineMetabaseAuthConfig({
fetchRequestToken: async () => {
// No parameters
const response = await fetch("https://:9090/sso/metabase", {
// Hardcode your endpoint URL
method: "GET",
headers: { Authorization: `Bearer ${yourToken}` },
});
return await response.json();
},
metabaseInstanceUrl: "https://: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 的文档。