Flask-AppBuilder Authentication in Apache Superset#
Superset does not implement its own login/credential-verification logic. Instead, all core authentication — validating passwords, binding to LDAP, handling OAuth/SAML redirects — is delegated to Flask-AppBuilder (FAB), which ships its own SecurityManager, auth views, and login forms. Superset extends FAB through SupersetSecurityManager and a thin SupersetAuthView, leaving most auth behavior in FAB's code.
Architecture#
Browser
│ POST /login/
▼
SupersetAuthView (superset/views/auth.py)
│ ↳ extends FAB's AuthView → renders React app template
│
▼ form submission handled by FAB's AuthDBView / AuthLDAPView / AuthOAuthView / ...
│ calls sm.auth_user_db() / sm.auth_user_ldap() / sm.auth_user_oauth()
│ on failure → flash(invalid_login_message, "warning")
▼
SupersetSecurityManager (superset/security/manager.py)
│ ↳ extends FAB's SecurityManager (flask_appbuilder.security.sqla.manager)
│ inherits all credential-checking methods
▼
Flask-Login session created; Superset RBAC checks applied on every request
Key Files#
| File | Role |
|---|---|
superset/security/manager.py | SupersetSecurityManager — extends FAB's SecurityManager; owns RBAC, permissions, guest tokens |
superset/views/auth.py | SupersetAuthView (/login) — minimal shim that renders the React template; SupersetRegisterUserView for email-activation flow |
superset/initialization/__init__.py | Wires CUSTOM_SECURITY_MANAGER into FAB's appbuilder |
flask_appbuilder/security/views.py | FAB's AuthView, AuthDBView, AuthLDAPView, etc. — actual login form handling |
flask_appbuilder/security/manager.py | FAB's BaseSecurityManager — auth_user_db(), auth_user_ldap(), update_user_auth_stat() |
Authentication Flow and Error Messages#
Login Flow#
SupersetAuthView handles GET /login/: if the user is already authenticated it redirects to the index; otherwise it renders the React app template. The actual form submission is handled by FAB's AuthDBView.login() or AuthLDAPView.login() (depending on AUTH_TYPE), which call sm.auth_user_db(username, password) or sm.auth_user_ldap(username, password) respectively (see FAB's views.py).
Where "Invalid login. Please try again." Comes From#
The error message displayed on failed login does not exist in Superset's codebase. It originates in FAB's AuthView base class:
# flask_appbuilder/security/views.py
class AuthView(BaseView):
invalid_login_message = lazy_gettext("Invalid login. Please try again.")
When auth_user_db() returns None (wrong password, unknown user, or inactive user), AuthDBView and AuthLDAPView call flash(as_unicode(self.invalid_login_message), "warning") and redirect back to the login page . The server-side log message is LOGMSG_WAR_SEC_LOGIN_FAILED = "Login Failed for user: %s" written at INFO level, also from FAB's constants.
auth_user_db() Behavior (in FAB)#
FAB's BaseSecurityManager.auth_user_db() (see FAB's manager.py):
- Returns
None(triggers the flash error) when: user not found by username or email, oruser.is_active == False. - Performs a fake
check_password_hasheven on missing users (constant-time defense against user enumeration). - On success: calls
update_user_auth_stat(user, success=True)— incrementslogin_count, resetsfail_login_count. - On wrong password: calls
update_user_auth_stat(user, success=False)— incrementsfail_login_count.
Misleading "Invalid Login" in Non-Login Contexts#
The same FAB-originated message can appear in unexpected contexts, e.g., in SQL Lab when Celery workers cannot authenticate to the Superset metadata database (not the target database). If the Superset web pod and worker pod use different SQLALCHEMY_DATABASE_URI or SECRET_KEY values, the worker's session decryption fails, FAB surfaces this as an authentication failure, and the same "Invalid login" flash message appears . Check environment consistency across pods before assuming a user credential problem.
SupersetSecurityManager: What It Owns vs. What FAB Owns#
SupersetSecurityManager extends flask_appbuilder.security.sqla.manager.SecurityManager. The split:
FAB owns (inherited, not overridden by Superset):
- Credential verification:
auth_user_db(),auth_user_ldap(),auth_user_oauth(),auth_user_saml() - Login form views:
AuthDBView,AuthLDAPView,AuthOAuthView,AuthSAMLView - User model CRUD, password hashing (
werkzeug.security.generate_password_hash) LoginManager(Flask-Login) setup anduser_loader- Auth error message string:
"Invalid login. Please try again." - Rate limiting (
AUTH_RATE_LIMITED,AUTH_RATE_LIMIT)
Superset owns (defined or overridden in SupersetSecurityManager):
create_login_manager()— adds a customrequest_loaderfor embedded guest tokensget_guest_user_from_request()/create_guest_access_token()/parse_jwt_guest_token()— guest (embedded) JWT authraise_for_access()— fine-grained RBAC checks on dashboards, datasources, charts, SQL tables- Permission lifecycle hooks:
database_after_insert/delete/update,dataset_after_insert/delete/update(SQLAlchemy event listeners) - Role sync:
sync_role_definitions()— creates Admin/Alpha/Gamma/sql_lab roles and syncs custom permissions register_views()— overrides FAB to swap inSupersetAuthViewandSupersetRegisterUserView, and trims FAB's security menu items
Configuration Reference#
All auth config keys are FAB conventions, set in superset/config.py:
| Key | Default | Description |
|---|---|---|
AUTH_TYPE | AUTH_DB | Auth backend: AUTH_DB, AUTH_LDAP, AUTH_OAUTH, AUTH_REMOTE_USER, AUTH_SAML |
AUTH_ROLE_ADMIN | "Admin" | Role name for admins |
AUTH_ROLE_PUBLIC | "Public" | Role for unauthenticated access |
AUTH_USER_REGISTRATION | False | Enable self-registration |
AUTH_USER_REGISTRATION_ROLE | public role | Default role for self-registered users |
AUTH_ROLES_MAPPING | {} | Map LDAP/OAuth group keys → FAB role names |
AUTH_ROLES_SYNC_AT_LOGIN | False | Re-sync roles from IdP on every login |
AUTH_RATE_LIMITED | False | Enable FAB's built-in rate limiting on login |
AUTH_RATE_LIMIT | "10 per 20 second" | Rate limit expression when enabled |
CUSTOM_SECURITY_MANAGER | None | Override with a subclass of SupersetSecurityManager |
CUSTOM_SECURITY_MANAGER is validated at startup: the custom class must extend SupersetSecurityManager (not raw FAB's manager), or app init raises an exception . This is the correct extension point for overriding auth behavior (e.g., adding custom auth_user_db logic, overriding FAB API views for permission filtering ).
Common Debugging Scenarios#
"Invalid login. Please try again." on correct credentials
- Check
ab_user.activecolumn in the metadata DB — FAB rejects inactive users with the same message. - In Kubernetes deployments, verify that
SQLALCHEMY_DATABASE_URIandSECRET_KEYare identical across web and Celery worker pods. Mismatches cause workers to fail session decryption, which FAB surfaces as an auth error .
Error doesn't appear in Superset source — where is it?
- Search
flask_appbuilder/security/views.py(FAB'sinvalid_login_message) andflask_appbuilder/security/manager.py(auth_user_db,auth_user_ldap). These files live in theflask-appbuilderpip package, not in the Superset repo .
JWT Bearer tokens returning empty results from APIs
- FAB's
before_requesthook may resetg.userafter JWT middleware sets it. Workaround: use aFLASK_APP_MUTATORbefore_requesthook to explicitly setg.userfrom the decoded token before FAB's hook runs .
SSO / OAuth session not re-evaluated after external logout
- Superset has no built-in mechanism to detect external IdP logouts. Session persistence is managed entirely by Flask-Login (FAB's
create_login_manager), so an active Superset cookie will remain valid until it expires or is explicitly invalidated .
Customizing auth behavior
- Subclass
SupersetSecurityManager, override the target method (e.g.,auth_user_db, or a FAB API view class attribute likepermission_view_menu_api), and setCUSTOM_SECURITY_MANAGERin your config .