Guest Token Authentication and Authorization#
Superset's embedded guest token system lets external applications render dashboards in iframes without requiring end-users to have Superset accounts. It is gated by the EMBEDDED_SUPERSET feature flag (default False). When enabled, every API request carrying an X-GuestToken header is resolved to a transient GuestUser rather than a session-based user.
Token Structure#
The guest token is a signed JWT whose payload is typed as GuestToken:
| Claim | Purpose |
|---|---|
user | Synthetic identity (username, first_name, last_name) |
resources | List of { type: "dashboard", id: <uuid or int> } entries |
rls_rules | List of { clause: "...", dataset: <optional id> } predicates |
iat / exp | Issued-at / expiry (default 300 s, config key GUEST_TOKEN_JWT_EXP_SECONDS) |
type | Must equal "guest" |
Token defaults are set in superset/config.py:
GUEST_ROLE_NAME = "Public"— the FAB role assigned to every guest userGUEST_TOKEN_JWT_SECRET— must be changed in productionGUEST_TOKEN_JWT_ALGO = "HS256"GUEST_TOKEN_HEADER_NAME = "X-GuestToken"
An optional GUEST_TOKEN_VALIDATOR_HOOK callable lets operators enforce custom constraints (e.g., "every token must include at least one RLS rule") before a token is issued .
Token Issuance#
The host application's backend calls POST /api/v1/security/guest_token/ (requires an admin or service-account credential). create_guest_access_token() in SupersetSecurityManager signs the claims with the configured secret and returns the JWT bytes.
Request Decoding: request_loader → GuestUser#
SupersetSecurityManager.create_login_manager() registers a custom Flask-Login request_loader. On every request — only when EMBEDDED_SUPERSET is enabled — Flask-Login calls request_loader, which delegates to get_guest_user_from_request().
That method:
- Reads the raw token from the
X-GuestTokenheader (orguest_tokenform field) . - Calls
parse_jwt_guest_token()to verify the signature, expiry, algorithm, and audience. - Validates that
user,resources,rls_rules, andtype == "guest"claims are present . Any failure logs a warning and returnsNone(→ 401). - Constructs a
GuestUserwith the configuredGUEST_ROLE_NAMErole viaget_guest_user_from_token().
GuestUser extends AnonymousUserMixin but sets is_authenticated = True and is_anonymous = False so that role-based code does not fall back to the Public role .
Dashboard-Scoped Access Control#
Two mechanisms enforce that a guest user only reaches the dashboards listed in their token.
1. raise_for_access() / has_guest_access()
raise_for_access() is the central authorization gate for all resource types. For dashboards it checks is_guest_user() first and, if true, calls has_guest_access(dashboard). All other access checks (owner, DASHBOARD_RBAC, datasource perms) are skipped for guest users — the token resource list is the sole authority .
has_guest_access() iterates user.resources and matches by dashboard integer ID or by the UUID stored in the embedded_dashboards record.
2. DashboardAccessFilter
DashboardAccessFilter in superset/dashboards/filters.py restricts SQLAlchemy list queries so only token-listed dashboards are returned. For guest users it adds a condition matching either Dashboard.embedded.uuid (UUID-based resource IDs) or Dashboard.id (legacy int IDs) .
Row-Level Security#
Guest token RLS rules are retrieved per-dataset by get_guest_rls_filters():
rule included if: rule has no "dataset" field OR rule["dataset"] == dataset.id
A rule without a dataset key applies to every dataset the query touches — always include dataset in multi-datasource dashboards to avoid unintended cross-dataset filtering .
The returned clauses are injected into the query's WHERE clause through the same pipeline as role-based RLS (via get_sqla_row_level_filters() in superset/connectors/sqla/models.py).
RLS clauses support Jinja2 templating. The ExtraCache.current_user_rls_rules() method in jinja_context.py detects whether the current user is a guest and routes to get_guest_rls_filters() accordingly. Standard identity helpers ({{ current_username() }}, {{ current_user_id() }}, {{ current_user_email() }}) work for guest users using the GuestUser's synthetic attributes .
Key Source Files#
| File | Role |
|---|---|
superset/security/guest_token.py | Type definitions: GuestToken, GuestTokenRlsRule, GuestUser |
superset/security/manager.py | create_guest_access_token, get_guest_user_from_request, parse_jwt_guest_token, has_guest_access, get_guest_rls_filters |
superset/dashboards/filters.py | DashboardAccessFilter — guest dashboard list restriction |
superset/jinja_context.py | current_user_rls_rules() — Jinja RLS routing for guests |
superset/config.py | All GUEST_TOKEN_* config knobs |
Known Issues#
GLOBAL_ASYNC_QUERIES+ guest tokens: guest users may see JWT validation errors due to a missingsubclaim (see issue #42073).- Session conflicts: mixing guest token embedding and OAuth/SSO in the same browser can cause the guest session to override OAuth cookies (see discussion #35231).
- RLS scope: omitting
datasetin a guest token RLS rule applies the clause globally to all datasets — rarely intended .