How to Set Up Google Workspace SSO for Apache Superset

How to Set Up Google Workspace SSO for Apache Superset
For a company on Google Workspace, “Sign in with Google” is the SSO users already expect, and Flask-AppBuilder has a Google handler built in, so the first version of this integration takes fifteen minutes. The reason it deserves a full guide is what the first version gets wrong.

Two things. First, Google’s OAuth flow will happily authenticate any Google account, including personal Gmail, unless you restrict it. Second, Google’s user info response contains a name and an email address and nothing about groups, so Superset’s AUTH_ROLES_MAPPING has nothing to map from and every user lands in the default role. Both are fixable in superset_config.py, and the second has three solutions of increasing power.

The general mechanics of Superset OAuth, proxy settings and the break-glass procedure are in our Okta guide and are not repeated here. Written against Superset 4.x.

Step 1: Create the Google OAuth client

In the Google Cloud console, in a project owned by your organisation:

  • Go to APIs & Services, OAuth consent screen. Choose Internal as the user type. This alone restricts sign-in to accounts in your Workspace organisation and is the single most important setting in this guide. Fill in the app name (“Superset BI”), support email and developer contact. Add the scopes openid, .../auth/userinfo.email and .../auth/userinfo.profile. Save.
  • Go to Credentials, Create credentials, OAuth client ID. Application type Web application. Name it “Superset”.
  • Under Authorised JavaScript origins, add https://superset.example.com.
  • Under Authorised redirect URIs, add https://superset.example.com/oauth-authorized/google. The last segment must match the provider name in Superset’s config.
  • Create, and copy the Client ID and Client secret.

If your consent screen must be External (for example, contractors on a different Workspace domain need access), you lose the automatic organisation restriction, and the domain enforcement in Step 2 becomes mandatory rather than belt-and-braces.

Step 2: Configure Superset

python
import os
from flask_appbuilder.security.manager import AUTH_OAUTH

AUTH_TYPE = AUTH_OAUTH

WORKSPACE_DOMAIN = "example.com"

OAUTH_PROVIDERS = [
    {
        "name": "google",
        "icon": "fa-google",
        "token_key": "access_token",
        "remote_app": {
            "client_id": os.environ["GOOGLE_CLIENT_ID"],
            "client_secret": os.environ["GOOGLE_CLIENT_SECRET"],
            "api_base_url": "https://www.googleapis.com/oauth2/v2/",
            "client_kwargs": {
                "scope": "openid email profile",
                # hd is a hint that pre-selects the Workspace account picker.
                # It is NOT enforcement; enforcement is in the security manager below.
                "hd": WORKSPACE_DOMAIN,
                "prompt": "select_account",
            },
            "server_metadata_url": "https://accounts.google.com/.well-known/openid-configuration",
            "request_token_url": None,
            "access_token_url": "https://accounts.google.com/o/oauth2/token",
            "authorize_url": "https://accounts.google.com/o/oauth2/auth",
        },
    }
]

AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Gamma"
AUTH_ROLES_SYNC_AT_LOGIN = True

ENABLE_PROXY_FIX = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = "Lax"

With only this, any user your consent screen allows can log in and will receive the Gamma role. The next section adds domain enforcement and role assignment in a custom security manager.

SSO is the easy half of access control

Mapping Google Groups to Superset roles, keeping that sync from drifting, and layering row-level security on top is where most SSO setups quietly rot. A Superset Architecture Review covers all three.

Book a Superset Architecture Review

Step 3: Enforce the domain and assign roles

FAB’s Google handler returns username, first_name, last_name and email, and no role_keys. Override oauth_user_info to add both checks. Three options follow; pick one.

Option A: Roles by domain rule

The simplest useful version. Everyone in the domain is a viewer; an explicit list of emails are analysts or admins.

python
from flask import flash, redirect, url_for
from superset.security import SupersetSecurityManager

ADMINS = {"data.lead@example.com", "platform@example.com"}
ANALYSTS = {"finance.analyst@example.com", "ops.analyst@example.com"}


class GoogleSecurityManager(SupersetSecurityManager):
    def oauth_user_info(self, provider, response=None):
        if provider != "google":
            return super().oauth_user_info(provider, response)

        me = self.appbuilder.sm.oauth_remotes[provider].get("userinfo").json()
        email = (me.get("email") or "").lower()

        # Enforcement: refuse anything outside the Workspace domain.
        if not email.endswith("@" + WORKSPACE_DOMAIN) or not me.get("verified_email", True):
            return {}

        if email in ADMINS:
            role_keys = ["admin"]
        elif email in ANALYSTS:
            role_keys = ["analyst"]
        else:
            role_keys = ["viewer"]

        return {
            "username": email,
            "first_name": me.get("given_name", ""),
            "last_name": me.get("family_name", ""),
            "email": email,
            "role_keys": role_keys,
        }


CUSTOM_SECURITY_MANAGER = GoogleSecurityManager

AUTH_ROLES_MAPPING = {
    "admin": ["Admin"],
    "analyst": ["Alpha", "sql_lab"],
    "viewer": ["Gamma"],
}

Returning an empty dict makes FAB refuse the login. The allow-lists live in configuration, which is fine for a team of thirty and unpleasant for a company of three hundred. That is when you move to Option C.

Option B: Roles by email pattern

A middle step for organisations with structured addresses or aliases: a mapping from regex to role key.

python
import re

ROLE_PATTERNS = [
    (re.compile(r"^(data|bi)-admin@example\.com$"), "admin"),
    (re.compile(r"^.+\.analyst@example\.com$"), "analyst"),
]

def role_keys_for(email):
    for pattern, key in ROLE_PATTERNS:
        if pattern.match(email):
            return [key]
    return ["viewer"]

Substitute role_keys_for(email) into Option A. It scales a little further and is still visible in config.

Option C: Roles from Google Groups via the Directory API

The correct long-term answer: Google Groups are the source of truth, and Superset looks up the user’s groups at login. This uses the Admin SDK Directory API with a service account that has domain-wide delegation.

Setup in Google Cloud and Workspace admin:

  • Enable the Admin SDK API in the Cloud project.
  • Create a service account, create a JSON key for it, and note its client ID (the numeric one).
  • In the Workspace admin console, Security, Access and data control, API controls, Domain-wide delegation, add the service account’s client ID with the scope https://www.googleapis.com/auth/admin.directory.group.readonly.
  • Pick a Workspace admin account the service account will impersonate (a dedicated, low-privilege admin role that can read groups is enough).

Then in Superset:

python
import os
from google.oauth2 import service_account
from googleapiclient.discovery import build
from superset.security import SupersetSecurityManager

DIRECTORY_SCOPES = ["https://www.googleapis.com/auth/admin.directory.group.readonly"]
IMPERSONATE = os.environ["GOOGLE_DIRECTORY_ADMIN"]          # e.g. directory-reader@example.com
SA_KEY_PATH = os.environ["GOOGLE_DIRECTORY_SA_KEY"]          # mounted JSON key

_directory = None

def directory():
    global _directory
    if _directory is None:
        creds = service_account.Credentials.from_service_account_file(SA_KEY_PATH, scopes=DIRECTORY_SCOPES)
        creds = creds.with_subject(IMPERSONATE)
        _directory = build("admin", "directory_v1", credentials=creds, cache_discovery=False)
    return _directory


def google_groups_for(email):
    groups = []
    page_token = None
    while True:
        resp = directory().groups().list(userKey=email, pageToken=page_token).execute()
        groups.extend(g["email"] for g in resp.get("groups", []))
        page_token = resp.get("nextPageToken")
        if not page_token:
            return groups


class GoogleGroupsSecurityManager(SupersetSecurityManager):
    def oauth_user_info(self, provider, response=None):
        if provider != "google":
            return super().oauth_user_info(provider, response)
        me = self.appbuilder.sm.oauth_remotes[provider].get("userinfo").json()
        email = (me.get("email") or "").lower()
        if not email.endswith("@" + WORKSPACE_DOMAIN):
            return {}
        try:
            role_keys = google_groups_for(email)
        except Exception as exc:  # directory outage should not lock everyone out
            self.appbuilder.get_app.logger.error("Directory lookup failed for %s: %s", email, exc)
            role_keys = []
        return {
            "username": email,
            "first_name": me.get("given_name", ""),
            "last_name": me.get("family_name", ""),
            "email": email,
            "role_keys": role_keys,
        }


CUSTOM_SECURITY_MANAGER = GoogleGroupsSecurityManager

# Google Group email -> Superset roles
AUTH_ROLES_MAPPING = {
    "superset-admins@example.com": ["Admin"],
    "superset-analysts@example.com": ["Alpha", "sql_lab"],
    "superset-viewers@example.com": ["Gamma"],
}

Install google-api-python-client and google-auth into the Superset image. The lookup adds a few hundred milliseconds to login, once per session, which nobody notices. Decide deliberately what happens when the Directory API is unreachable: the code above falls back to the registration role so people can still see public dashboards, which is usually the right trade-off. If you would rather fail closed, return {} in the exception handler.

Step 4: Test

  • Restart Superset. The login page shows “Sign in with google”.
  • Sign in with an admin account. Check Settings, List Users for the expected role.
  • In a private window, attempt sign-in with a personal Gmail account. With an Internal consent screen, Google refuses before Superset is involved. With an External screen, the security manager refuses and you land back on the login page.
  • For Option C, add a user to superset-analysts@example.com, wait a minute for Google to propagate, sign out and in, and confirm SQL Lab appears.

Troubleshooting

Symptom Cause Fix
Error 400: redirect_uri_mismatch Redirect URI not registered exactly Add https://superset.example.com/oauth-authorized/google; enable ENABLE_PROXY_FIX
Error 403: org_internal Consent screen is Internal and the account is outside the organisation Expected for outsiders; for legitimate external users switch to External and rely on the security manager
Personal Gmail accounts can log in Consent screen External and no domain check Add the domain enforcement from Step 3
Everyone is Gamma No role_keys returned Implement Option A, B or C; the built-in handler returns none
Option C: 403 Not Authorized to access this resource/api Domain-wide delegation missing or wrong scope Add the service account client ID with the group read-only scope in Workspace admin
Option C: 400 Bad Request: Invalid Input: memberKey Impersonated account lacks group read permission Impersonate an account with a Groups Reader admin role
mismatching_state Session cookie not returned on callback SESSION_COOKIE_SAMESITE = "Lax", SESSION_COOKIE_SECURE matching the scheme

Operational notes

  • Offboarding. Suspending a user in Workspace blocks new logins immediately. Existing Superset sessions last until PERMANENT_SESSION_LIFETIME; keep it short.
  • Multiple domains. Workspace organisations with secondary domains should check membership of a set rather than a single suffix.
  • Service account key hygiene (Option C). Mount it as a secret, never in the image, and rotate on the same schedule as other credentials.
  • 2-Step Verification and Google’s context-aware access policies apply to Superset automatically, which is a good part of the reason to do this.

Frequently Asked Questions

Can I restrict Apache Superset sign-in to my Google Workspace domain?

Yes, and it is worth doing in two places. Setting the OAuth consent screen to Internal restricts sign-in to accounts in your Workspace organisation and is the single most important setting in the setup. Then check the email domain again server-side in oauth_user_info, returning an empty dict for anything outside it, so the restriction survives a consent screen that later has to change to External for contractors.

Is the hd parameter enough to block personal Gmail accounts?

No. hd is a hint that pre-selects the right account in Google’s account picker; it is not enforcement, and the flow can still be completed with a personal account. Enforcement is the Internal consent screen plus the explicit domain check in the security manager. Treat hd purely as a usability improvement.

How do I map Google Groups to Superset roles?

Google’s user info response carries a name and an email address and nothing about group membership, so AUTH_ROLES_MAPPING has nothing to match and every user lands in the default role. The long-term answer is to look the user’s groups up at login through the Admin SDK Directory API, using a service account with domain-wide delegation and the group read-only scope. For a team of thirty, allow-lists or email patterns in configuration are a reasonable interim step.

Do I need a service account to use Google Workspace SSO with Superset?

Only for group-based role assignment. Authentication itself needs nothing more than the OAuth client ID and secret. The service account, its JSON key and the domain-wide delegation grant exist solely so Superset can ask the Directory API which groups a user belongs to. If roles are assigned by allow-list or email pattern instead, skip it and avoid the key-rotation burden that comes with it.

Build It With Andolasoft

Getting the sign-in right is the first step; deciding what each role can see is the second. A Superset Architecture Review covers authentication, role design and row-level security together with caching, async queries and upgrade readiness, in five working days with a written report. If you want the whole platform run for you, service-account key rotation included, that is Managed Support. For the broader set of controls, see governance and security best practices for Superset.

If your Workspace organisation is on Superset and everyone is still landing in the same role, talk to us. We will map your Google Groups to a Superset role model that holds up, and give you a fixed-scope plan to get there.