嵌入式分析SDK - 认证

嵌入式分析SDK仅适用于专业版企业版计划(包括自托管和Metabase云)。但是,您可以在本地机器上使用API密钥进行嵌入认证,从而在没有许可证的情况下试用SDK。

要在生产环境中使用SDK,您需要通过SSO设置认证。

如果您正在本地开发,也可以使用API密钥设置认证。

设置JWT SSO

要设置JWT SSO,您需要Metabase专业版或企业版许可证(如果您没有许可证,请查看此快速入门

以下是高层概述

  1. 在Metabase中启用JWT SSO
  2. 在您的后端添加一个新端点来处理认证
  3. 在您的前端将SDK连接到您的新端点

1. 在您的Metabase中启用JWT SSO

  1. 通过访问 管理设置 > 设置 > 认证 并点击 设置 来配置JWT。
  2. 输入JWT身份提供商URI,例如 https://:9090/sso/metabase。这是您将在后端添加的新端点,用于处理认证。
  3. 生成密钥并将其复制到剪贴板。

2. 在您的后端添加一个新端点来处理认证

您需要在后端添加一个库来签署您的JSON Web Tokens。

对于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 https://:${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: "https://:3000",
});

响应应为 { jwt: "{JWT_TOKEN}" } 形式

使用SAML SSO进行认证

SAML认证仅适用于专业版企业版计划(包括自托管和Metabase云)。但是,您可以在本地机器上使用API密钥进行嵌入认证,从而在没有许可证的情况下试用SDK。

要将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内,这取决于浏览器安全策略和您的身份提供商的配置。我们建议在您的目标环境中测试认证流程。

与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进行认证。您只需在配置对象中使用键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,则需要进行以下更改。

前端更改:

后端更改:

此外,如果您已设置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 的文档。

这有帮助吗?

感谢您的反馈!
想改进这些文档吗?提出修改建议。
© . This site is unofficial and not affiliated with Metabase, Inc.