嵌入式分析 SDK - 身份验证

⚠️ 此功能为 Beta 版。欢迎试用,但请注意,功能可能会发生变化(并且可能无法按预期工作)。

嵌入式分析 SDK 仅在 ProEnterprise 计划(包括自托管和 Metabase 云)中提供。但是,您可以使用 API 密钥对嵌入进行身份验证,从而在本地机器上试用 SDK,而无需许可证。

关于在使用 SDK 时处理身份验证的说明。

从您的服务器验证用户身份

SDK 需要您的应用程序后端中的一个端点,该端点将某人登录到您的 Metabase 并返回令牌。SDK 将使用该令牌对 Metabase 的调用进行身份验证。

SDK 将调用此端点以获取新令牌,或刷新即将过期的现有令牌。

用于生成令牌的示例代码

此示例在应用程序 /sso/metabase 中设置一个端点,该端点使用共享密钥创建令牌,以对 Metabase 的调用进行身份验证。

const express = require("express");
const cors = require("cors");
const session = require("express-session");
const jwt = require("jsonwebtoken");
const fetch = require("node-fetch");

async function metabaseAuthHandler(req, res) {
  const { user } = req.session;

  if (!user) {
    return res.status(401).json({
      status: "error",
      message: "not authenticated",
    });
  }

  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,
  );
  const ssoUrl = `${METABASE_INSTANCE_URL}/auth/sso?token=true&jwt=${token}`;

  try {
    const response = await fetch(ssoUrl, { method: "GET" });
    const session = await response.text();

    console.log("Received session", session);
    return res.status(200).set("Content-Type", "application/json").end(session);
  } catch (error) {
    if (error instanceof Error) {
      res.status(401).json({
        status: "error",
        message: "authentication failed",
        error: error.message,
      });
    }
  }
}

const app = express();

// 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://127.0.0.1:${PORT}`);
});

安全警告:每个最终用户必须拥有自己的 Metabase 账户

每个最终用户必须拥有自己的 Metabase 账户。

最终用户共享 Metabase 账户的问题在于,即使您在客户端通过 SDK 筛选数据,所有最终用户仍然可以访问会话令牌,他们可以使用该令牌通过 API 直接访问 Metabase,以获取他们不应该看到的数据。

但是,如果每个最终用户都拥有自己的 Metabase 账户,您可以在 Metabase 中配置权限,每个人都只能访问他们应该访问的数据。

除此之外,我们将共享账户视为不公平使用。SDK 的公平使用包括为嵌入式分析的每个最终用户提供他们自己的 Metabase 账户。

获取 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} />;
}

自定义 JWT 身份验证

您可以通过在 config prop 中指定 fetchRefreshToken 函数来自定义 SDK 获取刷新令牌的方式

/**
 * This is the default implementation used in the SDK.
 * You can customize this function to fit your needs, such as adding headers or excluding cookies.

 * The function must return a JWT token object, or return "null" if the user is not authenticated.

 * @returns {Promise<{id: string, exp: number} | null>}
 */
async function fetchRequestToken(url) {
  const response = await fetch(url, {
    method: "GET",
    credentials: "include",
  });

  return await response.json();
}

// Pass this configuration to MetabaseProvider.
// Wrap the fetchRequestToken function in useCallback if it has dependencies to prevent re-renders.
const config = { fetchRequestToken };

使用 API 密钥在本地进行身份验证

嵌入式分析 SDK 仅在生产环境中支持 JWT 身份验证。API 密钥身份验证仅支持用于本地开发和评估目的。

为了在本地开发以试用 SDK,您可以使用 API 密钥进行身份验证。

首先,创建一个 API 密钥

然后,您可以使用 API 密钥在您的应用程序中通过 Metabase 进行身份验证。您只需使用密钥 apiKey 将 API 密钥包含在 config 对象中即可。

const authConfig = {
    ...
    apiKey: "YOUR_API_KEY"
    ...
};

export default function App() {
  return (
    <MetabaseProvider authConfig={authConfig} className="optional-class">
      Hello World!
    </MetabaseProvider>
  );

阅读其他版本的 Metabase 的文档。