Apache Superset supports this out of the box through Flask-AppBuilder (FAB), the framework Superset is built on. The pieces are all there. What is missing is a single, current walkthrough that covers the Okta side, the Superset side, the role mapping, and the handful of reverse-proxy and cookie settings that turn a working configuration into a redirect loop. This is that walkthrough.
By the end, you will have:
- Okta as the only login path for Superset, with the local password form gone.
- Users created automatically on first login, with their Okta groups mapped to Superset roles.
- Role changes in Okta reflected in Superset at next login.
- A troubleshooting table for the errors this setup produces in practice.
The configuration shown here applies to Superset 2.1 through 4.x. The examples were written against Superset 4.x with the Flask-AppBuilder 4 series it ships with.
How Superset authentication actually works
Superset does not implement authentication itself. FAB does, through its SecurityManager, and Superset extends that with SupersetSecurityManager. FAB supports five authentication types, selected with AUTH_TYPE in superset_config.py:
| AUTH_TYPE | What it does | Typical use |
|---|---|---|
| AUTH_DB | Username and password stored in Superset’s metadata database | Default, small teams, demos |
| AUTH_LDAP | Bind against an LDAP or Active Directory server | On-premises corporate directories |
| AUTH_OAUTH | OAuth 2.0 / OpenID Connect against one or more providers | Okta, Azure AD, Google, Keycloak, Auth0 |
| AUTH_OID | Legacy OpenID 2.0 | Rarely used today |
| AUTH_REMOTE_USER | Trust a header set by an upstream proxy | Behind an SSO-terminating gateway |
Okta is an AUTH_OAUTH provider. Under the hood, FAB uses the Authlib client library to run the authorization-code flow, fetch the user’s profile from the provider’s userinfo endpoint, and hand the result to a method called oauth_user_info. FAB ships a handler for Okta, so a standard setup works without a custom security manager — though as Step 2 explains, there is one good reason to write one anyway.
Prerequisites
- Admin access to your Okta org, or someone who has it and twenty minutes.
- A running Superset instance reachable over HTTPS on a stable hostname. OAuth callbacks to localhost work for testing; callbacks to plain HTTP in production do not.
- The ability to edit
superset_config.pyand restart Superset. - Authlib installed in the Superset environment. The official Docker image includes it. For a pip install, run
pip install authlibifpython -c "import authlib"fails. - A short list of Okta groups you want to map to Superset roles. Three is a good start: administrators, analysts who can write SQL, and viewers.
Step 1: Create the Okta application integration
In the Okta Admin Console:
- Go to Applications, then Applications, and click Create App Integration.
- Choose OIDC – OpenID Connect as the sign-in method and Web Application as the application type.
- Name it something your users will recognise, for example “Superset BI”.
- Under Grant type, leave Authorization Code ticked. Superset does not need implicit or hybrid flows.
- Set the Sign-in redirect URI to
https://superset.example.com/oauth-authorized/okta. The last path segment must match the provider name you will use insuperset_config.py, which in this guide isokta. - Set the Sign-out redirect URI to
https://superset.example.com/login/. - Under Assignments, assign the groups that should be allowed to log in. Anyone not assigned will be refused by Okta before Superset ever sees them, which is the behaviour you want.
- Save, then copy the Client ID and Client secret from the General tab.
Choose the authorization server and expose groups
Okta has two kinds of authorization servers. The org authorization server at https://<your-org>.okta.com issues tokens for Okta’s own APIs and does not let you add custom claims. The default custom authorization server at https://<your-org>.okta.com/oauth2/default does, and it is what you need if you want group-based role mapping. Every URL in this guide uses the custom server.
To make Okta include the user’s groups in the response Superset reads:
- Go to Security, then API, open the
defaultauthorization server, and select the Claims tab. - Click Add Claim. Name it
groups, set the value type to Groups, filter Matches regex with the value.*, and include it in Any scope. - Add the claim for both the ID Token and the Access Token, with type Always. FAB does not read the ID token; it calls Okta’s
userinfoendpoint with the access token and maps roles from that payload. Configuring only the ID token is the most common reason group mapping silently does nothing. - On the Scopes tab, confirm a
groupsscope exists, or add one. Superset will request it.
The regex .* returns every group the user belongs to. In large orgs, that can be a long list. Filter by a prefix such as ^superset_ if you want to keep the token small; everything else in this guide still works.
Step 2: Configure Superset
Add the following to superset_config.py. Keep the client secret out of the file and read it from the environment or your secrets manager.
import os
from flask_appbuilder.security.manager import AUTH_OAUTH
OKTA_BASE_URL = os.environ["OKTA_BASE_URL"] # e.g. https://acme.okta.com
OKTA_ISSUER = f"{OKTA_BASE_URL}/oauth2/default"
AUTH_TYPE = AUTH_OAUTH
OAUTH_PROVIDERS = [
{
"name": "okta",
"icon": "fa-circle-o",
"token_key": "access_token",
"remote_app": {
"client_id": os.environ["OKTA_CLIENT_ID"],
"client_secret": os.environ["OKTA_CLIENT_SECRET"],
"api_base_url": f"{OKTA_ISSUER}/v1/",
"client_kwargs": {"scope": "openid profile email groups"},
"server_metadata_url": f"{OKTA_ISSUER}/.well-known/openid-configuration",
"access_token_url": f"{OKTA_ISSUER}/v1/token",
"authorize_url": f"{OKTA_ISSUER}/v1/authorize",
"jwks_uri": f"{OKTA_ISSUER}/v1/keys",
},
}
]
# Create a Superset user the first time someone signs in through Okta.
AUTH_USER_REGISTRATION = True
# Role for users whose groups match nothing in AUTH_ROLES_MAPPING.
AUTH_USER_REGISTRATION_ROLE = "Gamma"
# Okta group name -> list of Superset roles.
AUTH_ROLES_MAPPING = {
"superset_admins": ["Admin"],
"superset_analysts": ["Alpha", "sql_lab"],
"superset_viewers": ["Gamma"],
}
# Re-evaluate roles from the groups claim on every login, so a change in
# Okta takes effect the next time the user signs in.
AUTH_ROLES_SYNC_AT_LOGIN = True
What each block does:
nameis the provider key. It appears in the login button and, more importantly, in the callback path/oauth-authorized/okta. It must match the redirect URI you registered in Okta.server_metadata_urllets Authlib discover the token endpoint, signing keys and supported scopes from Okta’s OpenID configuration document. The explicitaccess_token_url,authorize_urlandjwks_urientries are belt and braces; they let the flow keep working if discovery is ever blocked by an egress firewall.api_base_urlis where FAB fetches the user profile from. FAB’s built-in Okta handler callsuserinforelative to this URL, which resolves tohttps://acme.okta.com/oauth2/default/v1/userinfo.client_kwargs.scopemust includegroups, or the claim you created in Step 1 will never arrive.AUTH_USER_REGISTRATIONis what turns a successful Okta login into a Superset user record. Without it, only users you created by hand can log in.AUTH_ROLES_MAPPINGkeys are Okta group names, exactly as they appear in the token. Values are lists of Superset role names.Admin,Alpha,Gamma,sql_labandPublicare the roles Superset creates by default; any custom role you have created works too.AUTH_ROLES_SYNC_AT_LOGINis the setting most people miss. Without it, roles are assigned once, at registration, and removing someone from an Okta group has no effect on what they can see in Superset. Note the flip side: with sync on, Okta is the only source of truth, so a role you grant by hand in Superset’s UI is wiped at that user’s next login.
When you need a custom security manager
FAB’s built-in Okta handler works, but it derives the username from the token’s sub claim and prefixes it, so your user list fills up with entries like okta_00u1a2b3c4d5e6f7g8h9. That is unpleasant to administer and impossible to eyeball against an audit request. Overriding oauth_user_info is also the fix if your Okta org names the groups claim differently, nests it, or you want usernames to be email addresses regardless of what preferred_username says:
import logging
from superset.security import SupersetSecurityManager
log = logging.getLogger(__name__)
class OktaSecurityManager(SupersetSecurityManager):
def oauth_user_info(self, provider, response=None):
if provider != "okta":
return super().oauth_user_info(provider, response)
me = self.appbuilder.sm.oauth_remotes[provider].get("userinfo").json()
# Logs which claims arrived without dumping personal data.
log.debug("Okta userinfo claims: %s", sorted(me.keys()))
return {
"username": me["email"].lower(),
"first_name": me.get("given_name", ""),
"last_name": me.get("family_name", ""),
"email": me["email"].lower(),
"role_keys": me.get("superset_roles", me.get("groups", [])),
}
# Also in superset_config.py, after the class is importable:
CUSTOM_SECURITY_MANAGER = OktaSecurityManager
The role_keys list is what FAB matches against AUTH_ROLES_MAPPING. Using the email address as the username is a deliberate choice: it is stable, unique, and survives a rename in Okta better than preferred_username does. Pick one convention before the first login, because changing it later creates duplicate user records.
That debug line is also your diagnostic for Step 1: if groups is missing from the logged claim list, the problem is the Okta claim configuration, not Superset.
Step 3: Settings that matter behind a reverse proxy
Almost every production Superset sits behind a load balancer, an ingress controller, or an Nginx proxy that terminates TLS. That changes what Superset thinks its own URL is, and OAuth is unforgiving about URLs.
from datetime import timedelta
# Trust X-Forwarded-* headers from the proxy so Superset builds https:// URLs.
ENABLE_PROXY_FIX = True
PROXY_FIX_CONFIG = {"x_for": 1, "x_proto": 1, "x_host": 1, "x_port": 1, "x_prefix": 1}
# Cookies must be sent on the callback from Okta.
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax" # "Strict" breaks the OAuth callback
# Log people out after a working day; Okta deactivation does not end live sessions.
PERMANENT_SESSION_LIFETIME = timedelta(hours=10)
Three points worth underlining:
- Without
ENABLE_PROXY_FIX, Superset sees the request as plain HTTP and constructs aredirect_uristarting withhttp://. Okta compares it to thehttps://value you registered and rejects the login with a redirect URI mismatch. SESSION_COOKIE_SAMESITE = "Strict"looks like the secure choice, and it is the one that breaks OAuth. The callback from Okta is a cross-site navigation. With Strict, the browser withholds the session cookie, Superset cannot find the state it stored before redirecting, and you get amismatching_stateerror. UseLax.- Deactivating someone in Okta stops new logins immediately but does not touch sessions Superset has already issued.
PERMANENT_SESSION_LIFETIMEbounds how long that window can be. For sensitive data, make it shorter.
Step 4: Restart and test
Restart Superset. The login page now shows a single “Sign in with Okta” button and no username or password fields.
- Sign in with a user who is in
superset_admins. You should land on the welcome page with the Admin menu visible. - Go to Settings, then List Users, and confirm the user was created with the expected username, email, and roles.
- Sign in as a user in
superset_viewersfrom a private window. Confirm they see dashboards they are permitted to see and nothing else. Row-level security rules, if you have them, still apply exactly as before; SSO changes how people authenticate, not what the roles allow. - Move that user into
superset_analystsin Okta, sign them out and back in, and confirm SQL Lab appears. This provesAUTH_ROLES_SYNC_AT_LOGINis working. - Remove the user from every Superset group in Okta and sign in again. They should now hold only the
AUTH_USER_REGISTRATION_ROLE. If you would rather they be refused entirely, remove them from the Okta application assignment instead.
Keep a break-glass path. Switching to AUTH_OAUTH removes the password form, so if Okta is unavailable, nobody can log in. Document the rollback: a copy of the previous superset_config.py with AUTH_DB, a known-good admin account in the metadata database, and the restart command. Test it once before you need it.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Okta shows “The redirect_uri parameter must be a Login redirect URI” | The callback URL Superset sent does not match Okta exactly | Check the provider name, the scheme (http vs https), and trailing slashes. Enable ENABLE_PROXY_FIX behind a proxy. |
mismatching_state: CSRF Warning! State not equal in request and response |
Session cookie not sent on the callback | Set SESSION_COOKIE_SAMESITE = "Lax" and make sure SESSION_COOKIE_SECURE matches the scheme in use. |
| Login succeeds, but every user gets the Gamma role only | Groups claim missing from the userinfo response | Confirm the groups claim is configured for the Access Token as well as the ID Token, the groups scope is requested, and you are not using the org authorization server URLs. |
| “Access is Denied” after signing in | User created without roles, or registration disabled | Set AUTH_USER_REGISTRATION = True and a valid AUTH_USER_REGISTRATION_ROLE. |
invalid_client in Superset logs |
Wrong client ID or secret, or the app is not a Web Application type | Re-copy the credentials; recreate the integration as an OIDC Web Application if needed. |
Usernames look like okta_00u1a2b3... |
FAB’s built-in handler derives the username from the sub claim |
Add the custom security manager from Step 2 before the first production login. |
| Two accounts for the same person | Username convention changed between logins | Pick one of preferred_username or email in oauth_user_info and keep it. Merge or delete the duplicate in List Users. |
| Roles do not update when Okta groups change | Sync disabled | Set AUTH_ROLES_SYNC_AT_LOGIN = True and have the user sign out and in. |
| Login button missing; password form still shown | Config not loaded | Confirm SUPERSET_CONFIG_PATH points to the file, or the file is on PYTHONPATH, and that Superset was actually restarted. |
To see exactly what Okta returned, run Superset with debug logging for a single login using the log.debug line in the custom security manager above. If you need the claim values and not just their names, log the full payload once and remove it immediately afterwards; the response contains personal data.
Operational notes
- Onboarding is now an Okta task. Add the person to the right group and assign them the application. There is nothing to do in Superset.
- Offboarding is two steps. Deactivate in Okta, then either wait for
PERMANENT_SESSION_LIFETIMEto expire or, for immediate effect, set the user to inactive in Superset’s List Users. - Role grants must happen in Okta. With
AUTH_ROLES_SYNC_AT_LOGINenabled, editing a user’s roles in the Superset UI lasts only until their next login. Change the Okta group instead. - Service accounts and API clients that authenticate with a username and password against Superset’s REST API will stop working under
AUTH_OAUTH. Move them to a token-based approach or keep a separate, restricted provider for them. - Multiple providers are allowed.
OAUTH_PROVIDERSis a list. Some teams keep Okta for staff and a second provider for contractors or an embedded-analytics tenant. - Audit trail. Superset’s own logs record the username on each request. With SSO, those usernames are guaranteed to be real identities, which is what your auditor was after in the first place.
Where this fits in a hardened deployment
SSO is one control in a production Superset setup. It sits alongside role design, row-level security, network exposure, metadata database backups, and a patching cadence. If you are putting Superset in front of a wider audience, or an audit is on the calendar, it is worth reviewing all of those together rather than one at a time.
Our Superset Architecture Review is a fixed-scope, five-day review of exactly that: authentication and SSO, RBAC and RLS, caching and async queries, upgrade readiness and dashboard performance, delivered as a written report with prioritised fixes. If you would rather someone else kept the platform patched and monitored, that is what Managed Support is for.
For the wider set of controls, see our earlier post on data governance and security best practices for Superset deployments.