I am trying to embed a superset dashboard into a simple front-end application. However, when the embedded dashboard always throws 403: Forbidden. I am pretty sure I have set the correct permissions and everything. I even tried accessing it using admin user, still it fails.

How to reproduce the bug

The source for the application can be found here - https://github.com/adimyth/superset_embedded

superset application

Added the following configuration in superset_config.py

FEATURE_FLAGS = {"ALERT_REPORTS": True, "EMBEDDED_SUPERSET": True}

SESSION_COOKIE_SAMESITE = None
ENABLE_PROXY_FIX = True
PUBLIC_ROLE_LIKE_GAMMA = True
CORS_OPTIONS = {
    'supports_credentials': True,
    'allow_headers': ['*'],
    'resources': ['*'],
    'origins': ['http://localhost:8088', 'http://localhost:8000']
}

This gave me the following dashboard it to embed into my application Screenshot 2022-11-29 at 2 28 54 PM

frontend application

<head>
    <script src="https://unpkg.com/@preset-sdk/embedded"></script>
    <style>
        iframe {
            height: 100%;
            width: 100%;
            border: none;
        }
    </style>
</head>
<body>
    <p id="dashboard-container"> </p>
    <script>
        // 1. Request guest_token from our backend, which runs at localhost:8000 by default 
            async function fetchGuestTokenFromBackend() {
                let response = await fetch('http://127.0.0.1:8000/guest-token');
                let data = await response.json()
                return data
        }

        // 2. Uses Preset Embedded SDK to embed the dashboard as iFrame
        const myDashboard = presetSdk.embedDashboard({
          id: "{{ DASHBOARD_ID }}",
          supersetDomain: "{{ SUPERSET_DOMAIN }}",
          mountPoint: document.getElementById("dashboard-container"),
          fetchGuestToken: () => fetchGuestTokenFromBackend(),
          dashboardUiConfig: { hideTitle: true, hideChartControls: true}
        });
    </script>
</body>

The frontend simply calls the /guest-token from backend and passes it to the presetSdk.embedDashboard

backend application

The /guest-token endpoint simply calls 2 methods from the helper (superset.py) file

@app.get("/guest-token")
async def analytics_view(request: Request):
    access_token = superset.authenticate()
    guest_token = superset.get_guest_token_for_dashboard(
        dashboard_id=DASHBOARD_ID, access_token=access_token
    )
    return guest_token

The helper has 2 methods - authenticate() which authenticates the superset-admin user & get_guest_token_for_dashboard which is used to generate access token for the guest user

import json
import os

import requests
from dotenv import load_dotenv

load_dotenv()


URL_AUTH = os.getenv("URL_AUTH")
URL_GUEST_TOKEN = os.getenv("URL_GUEST_TOKEN")
USERNAME = os.getenv("USERNAME")
FIRST_NAMER = os.getenv("FIRST_NAMER")
LAST_NAME = os.getenv("LAST_NAME")


def authenticate(
    username="admin",
    password="admin",
):
    response = requests.post(
        "http://localhost:8088/api/v1/security/login",
        headers={
            "Content-Type": "application/json",
            "Accept": "application/json",
            "Access-Control-Allow-Origin": "http://localhost:8000",
        },
        data=json.dumps(
            {
                "username": username,
                "password": password,
                "provider": "db"
            }
        ),
    )
    return response.json()["access_token"]


def get_guest_token_for_dashboard(
    dashboard_id,
    access_token,
    username=USERNAME,
    first_name=FIRST_NAMER,
    last_name=LAST_NAME,
):
    response = requests.post(
        URL_GUEST_TOKEN,
        data=json.dumps(
            {
                "user": {
                    "username": username,
                    "first_name": first_name,
                    "last_name": last_name,
                },
                "resources": [
                    {
                        "type": "dashboard",
                        "id": dashboard_id,
                    }
                ],
                "rls": [],
            }
        ),
        headers={
            "Authorization": "Bearer " + access_token,
            "Accept": "application/json",
            "Content-Type": "application/json",
        },
    )
    return response.json()["token"]

Finally we pass environment variables. Note that the DASHBOARD_ID matches from the screenshot above. Also note that I am passing superset-admin creds to the embedded dashboard

URL_AUTH=http://localhost:8088/api/v1/security/login
URL_GUEST_TOKEN=http://localhost:8088/api/v1/security/guest_token/
USERNAME=admin
FIRST_NAMER=Supserset
LAST_NAME=Admin
DASHBOARD_ID=b0a944b2-4ab5-47b7-a31d-c3eca4c36397
SUPERSET_DOMAIN=http://localhost:8088/

Error

When loading the frontend application which has the embedded dashboard, it returns -

{"errors": [{"message": "403 Forbidden: You don't have the permission to access the requested resource. It is either read-protected or not readable by the server.", "error_type": "GENERIC_BACKEND_ERROR", "level": "error", "extra": {"issue_codes": [{"code": 1011, "message": "Issue 1011 - Superset encountered an unexpected error."}]}}]}

Expected results

I was expecting to see the actual dashboard in the frontend application

Actual results

Screenshot 2022-11-29 at 2 37 17 PM

What's surprising is that after decoding the guest token, we can clearly see that the user is superset admin & has access to the dashboard 🤯 Screenshot 2022-11-29 at 2 39 15 PM

Environment

(please complete the following information):

  • browser type and version: Chrome
  • superset version: Built from the latest code as of 29th Nov
  • python version: 3.9
  • node.js version: NA
  • any feature flags active: {"ALERT_REPORTS": True, "EMBEDDED_SUPERSET": True}

Checklist

Make sure to follow these steps before submitting your issue - thank you!

  • [x] I have checked the superset logs for python stacktraces and included it here as text if there are any.
  • [x] I have reproduced the issue with at least the latest released version of superset.
  • [x] I have checked the issue tracker for the same issue and I haven't found one similar.

Comment From: rusackas

Pinging @lilykuang here, but also sharing this on the Superset Slack workspace in the #embedding-superset channel in case anyone there has insight. Let me know if you would like help connecting there as well to join the thread.

Comment From: cwegener

The first thing to try would be to remove ALL the allowed domains and leave the field empty.

Background: The allowed domains field is incorrectly labeled in the UI. What this field does is to check the "Referer" header and compare it against the values in this field, which is IMO a bit awkward.

Relevant discussion in Slack. https://apache-superset.slack.com/archives/C01EP56QGTS/p1663143592850569?thread_ts=1663071066.037159&cid=C01EP56QGTS

If that doesn't help, the next step is to investigate the Guest permissions configuration.

Comment From: adimyth

Hey! Thanks for the response! Yeah, I would like to join the apache superset slack channel to discuss this & other issues further

Comment From: adimyth

The first thing to try would be to remove ALL the allowed domains and leave the field empty.

Background: The allowed domains field is incorrectly labeled in the UI. What this field does is to check the "Referer" header and compare it against the values in this field, which is IMO a bit awkward.

Relevant discussion in Slack. https://apache-superset.slack.com/archives/C01EP56QGTS/p1663143592850569?thread_ts=1663071066.037159&cid=C01EP56QGTS

If that doesn't help, the next step is to investigate the Guest permissions configuration.

Adding the following in superset_config.py seems to be working for me -

# Dashboard embedding
GUEST_ROLE_NAME = "Gamma"
GUEST_TOKEN_JWT_SECRET = "test-guest-secret-change-me"
GUEST_TOKEN_JWT_ALGO = "HS256"
GUEST_TOKEN_HEADER_NAME = "X-GuestToken"
GUEST_TOKEN_JWT_EXP_SECONDS = 300  # 5 minutes

I shall test this out & revert!

Comment From: cwegener

Yup! GUEST_ROLE_NAME = "Gamma" will give the Guest Token all the required (and much more) Superset permissions in order to access the required Superset API functions.

The exact permissions to use for setting up your own custom role in Superset have been recently documented in a Github discussion here: https://github.com/apache/superset/discussions/18814#discussioncomment-4056030

Comment From: rusackas

For posterity, just linking a Slack conversation related to this issue here

Comment From: tejaskatariya

Hi,

My Dashboard is getting embedding successfully in firefox, but on chrome it is giving error 400 for CSRF.

I have done the below config changes

SESSION_COOKIE_HTTPONLY = True # Prevent cookie from being read by frontend JS? SESSION_COOKIE_SECURE = True # Prevent cookie from being transmitted over non-tls? SESSION_COOKIE_SAMESITE = None # One of [None, 'None', 'Lax', 'Strict'] GUEST_ROLE_NAME = "Gamma" PUBLIC_ROLE_LIKE_GAMMA= True

ENABLE_PROXY_FIX = True ENABLE_CORS = True CORS_OPTIONS: Dict[Any, Any] = { 'supports_credentials': True, 'allow_headers': [''], 'resources':[''], 'origins': ['*'] }

Can anyone help with what am i missing?

Comment From: vishaltps

I have added a blog to implement RLS with a custom role and limited permissions - https://medium.com/@vishalsadriya1224/embedding-apache-superset-dashboards-in-ruby-on-rails-and-react-a-role-level-security-guide-697da01676af

Comment From: httpie-salesrun

After set GUEST_ROLE_NAME = 'Gamma' you must to set permission in gama role to grant guest token

Apache Superset 403 Forbidden error when trying to embed a dashboard & view it using a guest user token

Comment From: dongdong1103

Hey! Thanks for the response! Yeah, I would like to join the apache superset slack channel to discuss this & other issues further

I have encountered the same problem. How can I solve it? Configuration is as follows # cors WTF_CSRF_ENABLED = False TALISMAN_ENABLED = False ENABLE_PROXY_FIX = True PUBLIC_ROLE_LIKE_GAMMA = True GUEST_ROLE_NAME = 'Gamma' TALISMAN_ENABLED = False ENABLE_CORS = True CORS_OPTIONS = { "supports_credentials": True, "allow_headers": "", "expose_headers": "", "resources": "", "origins": "" }

Comment From: Lau08

This config worked for me:

FEATURE_FLAGS = { "ALERT_REPORTS": True, "EMBEDDED_SUPERSET": True }

GUEST_ROLE_NAME = "Gamma"
GUEST_TOKEN_JWT_SECRET = "your-secret-here"
GUEST_TOKEN_JWT_AUDIENCE = "audi"

TALISMAN_ENABLED = False

OVERRIDE_HTTP_HEADERS = {
    "X-Frame-Options": "ALLOWALL",
    "Content-Security-Policy": "frame-ancestors 'self' http://localhost:5173"
}
ENABLE_CORS = True

And in the dashboard to embed in superset, copy the URL like this: http://localhost:5173 with the http://

Image

This worked using a guest token, payload to get it from the Svelte application in my case, and using the SDK:

const payload = {
        user: {
            first_name: 'Embedded first_name',
            last_name: 'Embedded last_name',
            username: 'Embedded_user'
        },
        resources: [
            {
                id: SUPERSET_DASHBOARD_ID, //your dashboard id
                type: 'dashboard'
            }
        ],
        rls_rules: [],
        aud: "audi", // same as GUEST_TOKEN_JWT_AUDIENCE in superset
        type: 'guest'
    };

const token = encodeJwt(payload, SUPERSET_GUEST_SECRET);