من یک Backend Developer هستم که دوست دارم توی مسیر یادگیری و تجربه رشد کنم. ساختن ، همیشه برام هیجانانگیزه و تلاش میکنم هر روز چیز جدیدی یاد بگیرم 🌱
چگونه با استفاده از Metabase SDK و FastAPI، دسترسی امن و محدود به دادههای داشبوردها ایجاد کردیم؟

در تیم پشتیبانی ، نیاز داشتیم اپراتورها فقط به دادههایی خاص از داشبوردهای متابیس دسترسی داشته باشن — نه بیشتر.
ما نمیخواستیم:
- کاربر وارد محیط متابیس بشه، 
- چیزی از کوئریها ببینه، 
- یا حتی به لینک یا iframe داشبوردها دسترسی داشته باشه. 
🎯 راهحل نهایی: React + Metabase SDK + FastAPI Proxy
در نهایت، این معماری رو پیادهسازی کردیم:
- در سمت فرانتاند، از Metabase Embedding SDK استفاده کردیم. 
- این SDK امکان رندر مستقیم داشبورد داخل کامپوننتهای ریاکت رو میده. 
- اما برای امنیت بیشتر، جلوی ارتباط مستقیم SDK با متابیس رو گرفتیم و یک لایه پروکسی با FastAPI بینشون قرار دادیم. 
جریان کار به این شکله:
- کاربر وارد پنل پشتیبانی میشه (احراز هویت با JWT داخلی ما). 
- کاربر تنها داشبوردهای رو میبینه که با توجه به رول/پرمیشنهاش در سیستم تعریف شدن. 
- وقتی SDK میخواد دادهی یک داشبورد رو بگیره، درخواست رو به جای متابیس، به سرویس پروکسی ما میفرسته. 
- در پروکسی (FastAPI): - JWT اعتبارسنجی میشه. 
- پرمیشن کاربر برای این داشبورد چک میشه. 
- در صورت تایید، درخواست به API متابیس فوروارد میشه. 
 
- پاسخ متابیس بهصورت JSON دریافت میشه و به SDK برگشت داده میشه. 
- SDK داده رو گرفته و خودش داخل ریاکت رندر میکنه . 
چرا SDK رو بهجای embed URL انتخاب کردیم؟
- کنترل کامل روی ظاهر و تعامل در ریاکت 
- بدون استفاده از iframe و محدودیتهایش 
- امکان شخصیسازی request ها و header ها 
- خروجی JSON آماده برای ویژوالسازی دلخواه 
نکات امنیتی مهم
- ارتباط مستقیم بین کاربر و متابیس کاملاً قطع شده. 
- فقط پروکسی ما به API متابیس دسترسی داره. 
- سطح دسترسی هر کاربر قبل از ارسال request به متابیس، در سرویس ما بررسی میشه. 
- دادهی خام، کوئریها یا اطلاعات متا به کاربر داده نمیشه؛ فقط خروجی پاکشده و کنترلشده از متابیس. 
نمونه کد سمپل پروکسی سرویس :
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse, Response
import httpx
import json
from urllib.parse import urlencode
app = FastAPI()
METABASE_URL = "https://your-metabase-url.com"  # ← آدرس متابیس
METABASE_API_KEY = "your-api-key"               # ← کلید API متابیس
@app.api_route("/{path:path}", methods=["GET", "POST", "OPTIONS"])
async def proxy_metabase(path: str, request: Request):
    # ⛔️ اینجا میتونی دسترسیهای کاربر رو بررسی کنی
    # --------------------------------------------------------------------
    # مثال:
    # user = await get_current_user(request)
    # if not user.has_permission(path):
    #     raise HTTPException(status_code=403, detail="Permission denied")
    # --------------------------------------------------------------------
    # ساخت آدرس کامل متابیس
    query_string = urlencode(dict(request.query_params))
    full_url = f"{METABASE_URL}/{path}"
    if query_string:
        full_url += f"?{query_string}"
    # گرفتن بدنه درخواست در صورت نیاز
    body = None
    if request.method in {"POST", "PUT", "PATCH"}:
        raw = await request.body()
        if raw:
            body = json.loads(raw.decode("utf-8"))
    # ارسال درخواست به متابیس
    async with httpx.AsyncClient() as client:
        response = await client.request(
            method=request.method,
            url=full_url,
            json=body,
            headers={
                "x-api-key": METABASE_API_KEY,
                "User-Agent": "fastapi-proxy"
            }
        )
    # بازگرداندن پاسخ
    content_type = response.headers.get("content-type", "")
    if "application/json" in content_type:
        return JSONResponse(content=response.json(), status_code=response.status_code)
    return Response(
        content=response.content,
        status_code=response.status_code,
        media_type=content_type
    )
سمت فرانت هم به این شکل عمل کردیم:
📁 ساختار پیشنهادی
src/
├── metabase/
│   ├── MetabaseProvider.jsx
│   ├── Dashboard.jsx
│   └── config.js
✅ config.js (تنظیمات اتصال به متابیس)
import { defineMetabaseAuthConfig, defineMetabaseTheme } from '@metabase/embedding-sdk-react'
const config = defineMetabaseAuthConfig({
  preferredAuthMethod: 'jwt',
  metabaseInstanceUrl: 'https://proxy-api.example.com', // آدرس متابیس پروکسی شما
  // درخواست گرفتن توکن JWT از API بکاند
  fetchRequestToken: async () => {
    const response = await fetch('https://proxy-api.example.com/auth/sso?preferred_method=jwt', {
      method: 'GET',
      credentials: 'include', // ← برای ارسال کوکیها
    })
    return response.json() // باید { token: '...' } برگردونه
  },
})
// تم اختیاری (میتونید حذفش کنید)
const theme = defineMetabaseTheme({ colorScheme: 'light' })
export { config, theme }✅ MetabaseProvider.jsx (Provider اصلی برای کانتکست)
import { config, theme } from './config'
import { MetabaseProvider as SDKProvider } from '@metabase/embedding-sdk-react'
function MetabaseProvider({ children }) {
  return (
    <SDKProvider authConfig={config} theme={theme}>
      {children}
    </SDKProvider>
  )
}
export { MetabaseProvider }
✅ Dashboard.jsx (نمایش داشبورد خاص با پارامتر)
import { useMemo } from 'react'
import { useLocation, useParams } from 'react-router-dom'
import { StaticDashboard, MetabaseProvider } from '@metabase/embedding-sdk-react'
function objectifyQuery(search) {
  const query = new URLSearchParams(search)
  const obj = {}
  for (const [key, value] of query.entries()) {
    obj[key] = value
  }
  return obj
}
function DashboardPage() {
  const { id } = useParams() // مثلاً /dashboard/12
  const { search } = useLocation()
  const queryParams = useMemo(() => objectifyQuery(search), [search])
  return (
    <div style={{ height: '100vh' }}>
      <MetabaseProvider>
        <StaticDashboard
          dashboardId={+id}
          initialParameters={queryParams}
          withTitle
        />
      </MetabaseProvider>
    </div>
  )
}
export default DashboardPage
نتیجهگیری
اگر به دنبال پیادهسازی یک سیستم امن و حرفهای برای نمایش داشبوردهای متابیس هستید که:
- دسترسیها و پرمیشنها را به صورت دقیق و مبتنی بر نقش کاربران مدیریت کند، 
- بدون استفاده از iframe و با حفظ یکپارچگی تجربه کاربری در فرانتاند ریاکت باشد، 
- کوئریها و لینکهای حساس متابیس را از دید کاربران نهایی مخفی نگه دارد، 
- و قابلیت توسعه و سفارشیسازی آسان را داشته باشد، 
ترکیب استفاده از بسته @metabase/embedding-sdk همراه با یک سرویس پروکسی امن مبتنی بر FastAPI، راهکاری ایدهآل، مقیاسپذیر و قابل اعتماد خواهد بود که به راحتی میتوانید آن را مطابق نیازهای سازمان خود توسعه دهید و بهینهسازی کنید.
نمونه داشبورد پیاده سازی شده :

 
                   
            
مطلبی دیگر از این انتشارات
از نیاز تا MVP : ساخت اکستنشن تبدیل گفتار به متن فارسی برای پشتیبانی باسلام
مطلبی دیگر از این انتشارات
از خاکستر بحران تا نقشهی آینده: نقشه مناطق بمباران شده ایران
مطلبی دیگر از این انتشارات
چالش روانخوانی فونت در باسلام