Auth0 SSO
Superset uses Flask-AppBuilder for login. This setup turns on OAuth, adds an Auth0 button on the login page, and maps Auth0 roles onto Superset roles at each login.
Roles are read from a custom claim on the ID token (https://superset.org/roles). Auth0 does not put that claim on /userinfo, so the security manager decodes the ID token after Auth0 returns it.
1. Create the Auth0 application
In the Auth0 dashboard, create a Regular Web Application.
| Setting | Value |
|---|---|
| Allowed Callback URLs | https://<your-superset-host>/oauth-authorized/auth0 |
| Allowed Logout URLs | https://<your-superset-host>/logout/ |
| Allowed Web Origins | https://<your-superset-host> |
For a local install, use http://localhost:8088 as the host. The path /oauth-authorized/auth0 must match the provider name in OAUTH_PROVIDERS (auth0).
Copy the Domain, Client ID, and Client Secret. You will paste them into superset_config.py.
2. Add roles to the ID token
Create an Auth0 role for each Superset role you want to assign. Prefix every role with superset_:
| Auth0 role | Superset role |
|---|---|
superset_Admin | Admin |
superset_Alpha | Alpha |
superset_Gamma | Gamma |
superset_sql_lab | sql_lab |
The Superset role must already exist. The mapper lowercases the name after the prefix, so superset_Admin matches the built-in Admin role.
Assign those roles to users, then add a Post Login Action that copies them onto the ID token:
exports.onExecutePostLogin = async (event, api) => {
const namespace = "https://superset.org";
const roles = event.authorization?.roles || [];
api.idToken.setCustomClaim(`${namespace}/roles`, roles);
};
Deploy the Action and attach it to the Login flow.
3. Install dependencies in the image
The security manager imports Authlib, requests, and PyJWT. A Postgres metadata database also needs psycopg2. The official image does not always include those packages in /app/.venv, so add Dockerfile.auth in the Superset docker directory:
FROM apachesuperset.docker.scarf.sh/apache/superset:6.1.0
USER root
RUN /app/.venv/bin/python -m ensurepip \
&& /app/.venv/bin/python -m pip install --no-cache-dir \
Authlib requests psycopg2-binary PyJWT \
&& /app/.venv/bin/python -c \
"import authlib, requests, psycopg2, jwt; print('Dependencies OK')"
USER superset
PyJWT is on that install line because oauth_user_info does import jwt. Build the image from the directory that contains Dockerfile.auth:
docker build -f Dockerfile.auth -t superset-auth:6.1.0 .
Use superset-auth:6.1.0 for every service that runs the Superset image (the app, init, worker, and beat). Those processes all load superset_config.py.
On a non-Docker install, install the same packages into the virtualenv that runs Superset:
pip install Authlib requests psycopg2-binary PyJWT
4. Configure superset_config.py
Add the following to superset_config.py. Replace YOUR_AUTH0_DOMAIN, YOUR_AUTH0_CLIENT_ID, and YOUR_AUTH0_CLIENT_SECRET with the values from the Auth0 application.
AUTH_ROLES_MAPPING must be a non-empty dict. Flask-AppBuilder only calls get_roles_from_keys when that mapping is set. The __dynamic__ key is a placeholder so the custom method can decide the roles. AUTH_ROLES_SYNC_AT_LOGIN updates an existing user's roles on every login.
import logging
import jwt
from flask_appbuilder.security.manager import AUTH_OAUTH
from requests.exceptions import HTTPError
from superset.security import SupersetSecurityManager
AUTH_TYPE = AUTH_OAUTH
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Public"
AUTH_ROLES_SYNC_AT_LOGIN = True
# Non-empty, or FAB skips get_roles_from_keys
AUTH_ROLES_MAPPING = {"__dynamic__": []}
OAUTH_PROVIDERS = [
{
"name": "auth0",
"icon": "fa-at",
"token_key": "access_token",
"remote_app": {
"client_id": "YOUR_AUTH0_CLIENT_ID",
"client_secret": "YOUR_AUTH0_CLIENT_SECRET",
"client_kwargs": {"scope": "openid profile email"},
"server_metadata_url": "https://YOUR_AUTH0_DOMAIN/.well-known/openid-configuration",
},
}
]
NS = "https://superset.org"
USERINFO_URL = "https://YOUR_AUTH0_DOMAIN/userinfo"
ROLE_PREFIX = "superset_"
class CustomSsoSecurityManager(SupersetSecurityManager):
def oauth_user_info(self, provider, response=None): # noqa: ARG002
try:
remote_app = self.appbuilder.sm.oauth_remotes.get(provider)
if remote_app is None:
logging.error("Provider %s is not configured in oauth_remotes.", provider)
return {}
me = remote_app.get(USERINFO_URL)
me.raise_for_status()
data = me.json()
logging.info("User info from Auth0: %s", data)
# Custom claims (roles) live in the ID token, not in /userinfo.
# Authlib has already validated this token with Auth0.
# verify_aud must be off: Auth0 sets aud to the client id, and
# PyJWT raises InvalidAudienceError when audience is not passed.
claims = {}
if response and response.get("id_token"):
claims = jwt.decode(
response["id_token"],
options={"verify_signature": False, "verify_aud": False},
algorithms=["RS256"],
)
logging.info("ID token claims: %s", claims)
roles = claims.get(f"{NS}/roles", [])
logging.info("Roles: %s", roles)
return {
"username": data.get("nickname", data.get("email", "")),
"first_name": data.get("given_name", ""),
"last_name": data.get("family_name", ""),
"email": data.get("email", ""),
"role_keys": roles,
}
except HTTPError as e:
logging.error("HTTP error occurred: %s", e)
except Exception as e:
logging.error("An error occurred while fetching user info: %s", e)
return {}
def get_roles_from_keys(self, role_keys):
# lowercase name -> Role object, one DB query
superset_roles = {r.name.lower(): r for r in self.get_all_roles()}
matched = set()
for key in role_keys:
if not key.lower().startswith(ROLE_PREFIX):
continue
name = key[len(ROLE_PREFIX) :].lower() # superset_Admin -> admin
role = superset_roles.get(name)
if role:
matched.add(role)
else:
logging.warning(
"No Superset role named '%s' for Auth0 role '%s'", name, key
)
return list(matched)
CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_SECURE = False # True only when you serve HTTPS
SESSION_COOKIE_HTTPONLY = True
Set SESSION_COOKIE_SECURE = True when Superset is served over HTTPS.
If login raises jwt.exceptions.InvalidAudienceError: Invalid audience, the ID token decode is missing algorithms=["RS256"] and options={"verify_signature": False, "verify_aud": False}. Auth0 always sets aud to the client id. The snippet above already passes both, because Authlib validated the token before this method runs.
5. Restart Superset
Restart the Superset process so it loads the new security manager. The login page should show an Auth0 button. After login, the user's Superset roles match the superset_* roles on the ID token. Users with no matching role stay on Public, the registration role.