Transparent JWT-based SSO and multi-tenant dashboard embedding for Apache Superset
Project description
superset-auth-kit
superset-auth-kit (SAK) is a Flask extension for Apache Superset that provides transparent JWT-based SSO and multi-tenant embedding for SaaS applications.
It integrates directly into Superset's Flask-AppBuilder security layer — no proxy, no sidecar, no fork required.
Features
- JWT SSO — Validate HS256, RS256, or ES256 tokens from your identity provider and create a Superset session in one POST request.
- Anti-replay protection — JTI blocklist with pluggable backends: in-memory (single process) or Redis (distributed).
- Multi-tenant context — Thread-safe and greenlet-safe tenant propagation via
contextvars.ContextVar(PEP 567). Fail Closed: raises an error rather than leaking data across tenants. - Row Level Security (RLS) —
current_tenant()Jinja macro for SQL templates. Works withcache_key_wrapperto guarantee per-tenant cache isolation. - Declarative role management —
CapabilityBundledefinitions provisioned idempotently into Superset's FAB database. Two production-ready bundles included:DashboardConsumer(white-label embed) andChartAuthor(authoring workspace). - CLI —
superset authkit provision-rolesandsuperset authkit check-compatfor pipeline and KubernetesinitContainerintegration. - Secure by default — Admin role injection blocked, session fixation protection, open-redirect prevention, HMAC-signed server sessions.
Compatibility
| SAK Version | Superset | Flask-AppBuilder | Python |
|---|---|---|---|
| 0.1.x | 6.1.x | 5.0.x | 3.10+ |
Installation
pip install superset-auth-kit[api]
Optional extras:
| Extra | Installs | Use case |
|---|---|---|
api |
flask, flask-login, marshmallow |
SSO endpoint |
redis |
redis |
Distributed JTI blocklist |
cli |
flask, click, sqlalchemy |
provision-roles CLI |
dev |
All of the above + pytest, mypy |
Development |
Quick Start
1. Configure Superset
In your superset_config.py:
from superset.security import SupersetSecurityManager
from superset_auth_kit.api.blueprint import create_sso_blueprint, init_app
from superset_auth_kit.providers.jwt import JwtProvider
from superset_auth_kit.replay.blocklist import InMemoryJtiStore
from superset_auth_kit.security.manager import build_manager
from superset_auth_kit.sync.role_mapper import RoleMapper
from superset_auth_kit.tenant.context import TenantContext
# Identity provider
_provider = JwtProvider(
secret_or_key="your-jwt-shared-secret", # or an RSA/EC public key
algorithms=["HS256"],
jti_store=InMemoryJtiStore(), # use RedisJtiStore in production
)
# IdP role -> Superset role mapping
_mapper = RoleMapper(
mapping={"viewer": "sak__dashboard_consumer", "analyst": "sak__chart_author"},
allowed_roles=frozenset({"sak__dashboard_consumer", "sak__chart_author"}),
default_roles=("sak__dashboard_consumer",),
)
# Inject SAK into Superset's security layer
CUSTOM_SECURITY_MANAGER = build_manager(
SupersetSecurityManager,
identity_provider=_provider,
role_mapper=_mapper,
)
# Register the SSO endpoint blueprint
BLUEPRINTS = [create_sso_blueprint()]
FLASK_APP_MUTATOR = init_app
# Expose current_tenant() in Jinja SQL templates
JINJA_CONTEXT_ADDONS = {
"current_tenant": TenantContext.get_tenant,
}
2. Provision roles
superset authkit provision-roles
This creates sak__dashboard_consumer and sak__chart_author in the FAB database.
The command is idempotent — safe to run on every deployment.
3. Authenticate via SSO
Your frontend sends a signed JWT to:
POST /api/v1/auth/sso
Content-Type: application/json
{
"token": "<signed-jwt>",
"redirect_to": "/superset/dashboard/1/"
}
On success: 302 redirect to redirect_to with a Flask session cookie set.
JWT Claims
The JWT must contain the following claims:
| Claim | Required | Description |
|---|---|---|
sub |
✅ | Unique user identifier |
email |
✅ | User email address |
given_name |
✅ | First name |
family_name |
✅ | Last name |
roles |
✅ | List of IdP role names (strings) |
tenant_id |
✅ | Tenant identifier — ^[a-zA-Z0-9_-]{1,128}$ |
exp |
✅ | Expiration timestamp (standard JWT claim) |
iat |
recommended | Issued-at timestamp |
jti |
recommended | Unique token ID (enables anti-replay) |
Claim names are configurable via ClaimMapping in JwtProvider.
Row Level Security
In your Superset SQL template (dataset or RLS filter):
-- Correct: tenant_id is included in BOTH the SQL filter AND the cache key
WHERE tenant_id = '{{ cache_key_wrapper(current_tenant()) }}'
The cache_key_wrapper call ensures that the tenant value is included in the
SHA-256 cache key hash, preventing cross-tenant cache collisions.
Role Management
Built-in capability bundles
| Bundle key | FAB role name | Profile |
|---|---|---|
dashboard_consumer |
sak__dashboard_consumer |
Embed-only viewer. Zero menu_access. Read-only. |
chart_author |
sak__chart_author |
Chart and dashboard authoring. No SQL Lab or admin access. |
CLI
# Provision all bundles
superset authkit provision-roles
# Provision specific bundles
superset authkit provision-roles --bundle dashboard_consumer
# Dry-run: show what would change without applying
superset authkit provision-roles --dry-run
# Force re-provision even if version matches
superset authkit provision-roles --force
# Check compatibility with the installed Superset version
superset authkit check-compat
Versioning
Each bundle has a version: int. When you modify a bundle's permission graph,
increment the version. On the next provision-roles run, SAK detects the version
change and applies the diff automatically.
Downgrades (deploying an older bundle version over a newer database version) are blocked with an explicit error — they must be an intentional explicit action, not a silent side effect of a rollback.
Configuration Reference
| Config variable | Type | Description |
|---|---|---|
CUSTOM_SECURITY_MANAGER |
type |
Required — output of build_manager() |
BLUEPRINTS |
list |
Required — [create_sso_blueprint()] |
FLASK_APP_MUTATOR |
callable |
Required — init_app |
JINJA_CONTEXT_ADDONS |
dict |
Required for RLS — {"current_tenant": TenantContext.get_tenant} |
SESSION_COOKIE_SECURE |
bool |
Recommended True in production |
SESSION_COOKIE_HTTPONLY |
bool |
Recommended True |
SESSION_COOKIE_SAMESITE |
str |
Recommended "Lax" or "Strict" |
JwtProvider options
JwtProvider(
secret_or_key=..., # str (HMAC) or RSA/EC public key PEM
algorithms=["HS256"], # non-empty list — required by PyJWT 2.x
jti_store=InMemoryJtiStore(),
claim_mapping=ClaimMapping( # optional, all fields have defaults
sub="sub",
email="email",
given_name="given_name",
family_name="family_name",
roles="roles",
tenant_id="tenant_id",
),
leeway=0, # clock skew tolerance in seconds
)
Redis JTI store
from superset_auth_kit.replay.blocklist import RedisJtiStore
import redis
jti_store = RedisJtiStore(
client=redis.Redis.from_url("redis://localhost:6379/0"),
ttl=3600, # seconds — should match your JWT max expiry
)
Docker Compose Integration
superset:
image: apache/superset:latest
environment:
SUPERSET_CONFIG_PATH: /app/pythonpath/superset_config.py
command: >
bash -c "
pip install superset-auth-kit[api,cli] &&
superset db upgrade &&
superset init &&
superset authkit provision-roles &&
superset run -p 8088 --host 0.0.0.0 --with-threads
"
Kubernetes / Helm
initContainers:
- name: provision-sak-roles
image: "apache/superset:latest"
command: ["superset", "authkit", "provision-roles"]
env:
- name: SUPERSET_CONFIG_PATH
value: /app/pythonpath/superset_config.py
This guarantees provisioning completes before application pods start — the recommended K8s pattern for data initialization.
Development
git clone https://github.com/plinxore/superset-auth-kit.git
cd superset-auth-kit
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
# Run unit tests (no Docker required)
pytest tests/unit/ -v --tb=short -m "not integration"
# Run integration tests (requires Docker)
pytest tests/integration/ -v -m integration --timeout=300
# Type checking
mypy superset_auth_kit/ --ignore-missing-imports \
--exclude 'superset_auth_kit/cli/commands\.py'
See CONTRIBUTING.md for the full contribution guide.
Security
- JWT algorithm confusion:
algorithmsmust be a non-empty list;"none"is never accepted. - Admin role injection: blocked at
RoleMapperconstruction andUserSyncerruntime. - Tenant ID: validated by regex
^[a-zA-Z0-9_-]{1,128}$at every ContextVar write.TenantResolutionErroris raised (neverNonereturned) when the context is missing. - Session fixation:
session.clear()is called before everylogin_user(). - Open redirect:
redirect_toonly accepts relative paths starting with/.
License
MIT — Copyright (c) 2026 Plinxore
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file superset_auth_kit-0.1.1.tar.gz.
File metadata
- Download URL: superset_auth_kit-0.1.1.tar.gz
- Upload date:
- Size: 46.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
886b5ac3d4de39436e46c6cbecae1b9c3a5ca480c37536a8725716595bb67fda
|
|
| MD5 |
222c5cb3ec6303f0493c37e9240e0923
|
|
| BLAKE2b-256 |
59a6ce2c6074602fa281d7ea5493d0d11ba0f3ea58feaec59953e76099268fb3
|
File details
Details for the file superset_auth_kit-0.1.1-py3-none-any.whl.
File metadata
- Download URL: superset_auth_kit-0.1.1-py3-none-any.whl
- Upload date:
- Size: 52.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8aa548ace05eedf809fe0180ef9a5d26a0eeedc9f47ce8ca6427e3c33a4ad985
|
|
| MD5 |
c982ec45ab2c21c585cfc6fbe44a20a9
|
|
| BLAKE2b-256 |
ad68715a9e64bd272d6c8795b5eaa2ee420d4f84a6a129ea9b46e1d948ab112e
|