0

I'm trying to set up Superset with Auth0. I've found somewhat similar issues here and here.

I've set up the following configuration based on the first link above and trying to follow the Superset and Flask-AppBuilder docs:

    from flask_appbuilder.security.manager import (
        AUTH_OAUTH,
    )

    from superset.security import SupersetSecurityManager

    import json
    import logging
    import string
    import random

    nonce = ''.join(random.choices(string.ascii_uppercase + string.digits + string.ascii_lowercase, k = 30))

    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)

    AUTH_TYPE = AUTH_OAUTH
    AUTH_USER_REGISTRATION = True
    AUTH_USER_REGISTRATION_ROLE = "Admin"

    AUTH0_URL = os.getenv('AUTH0_URL')
    AUTH0_CLIENT_KEY = os.getenv('AUTH0_CLIENT_KEY')
    AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET')

    OAUTH_PROVIDERS = [
      { 'name':'auth0',
        'token_key':'access_token',
        'icon':'fa-at',
        'remote_app': {
            'api_base_url': AUTH0_URL,
            'client_id': AUTH0_CLIENT_KEY,
            'client_secret': AUTH0_CLIENT_SECRET,
            'server_metadata_url': AUTH0_URL + '/.well-known/openid-configuration',
            'client_kwargs': {
                'scope': 'openid profile email'
            },
            'response_type': 'code token',
            'nonce': nonce,
        }
      }
    ]

    class CustomSsoSecurityManager(SupersetSecurityManager):
        def oauth_user_info(self, provider, response=None):
            logger.debug('oauth2 provider: {0}'.format(provider))
            if provider == 'auth0':
                res = self.appbuilder.sm.oauth_remotes[provider].get(AUTH0_URL + '/userinfo')
                logger.debug('response: {0}'.format(res))
                if res.raw.status != 200:
                    logger.error('Failed to obtain user info: %s', res.json())
                    return
                # user_info = self.appbuilder.sm.oauth_remotes[provider].parse_id_token(res)
                # logger.debug('user_info: {0}'.format(user_info))
                me = res.json()
                return {
                    'username' : me['email'],
                    'name' : me['name'],
                    'email' : me['email'],
                }

    CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager

The full error log message is:

2022-03-18 18:53:56,854:ERROR:flask_appbuilder.security.views:Error authorizing OAuth access token: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.

NOTES:

  • I can see an access_token parameter in the redirect url, so it seems to be working with Auth0 correctly.
  • I don't see any of the debug lines in the CustomSsoSecurityManager being written, so my guess is that I have not correctly set that up (or my logging is not correctly configured).
  • I've tried using both Regular Web Application and Single Page Application application types in Auth0, and both fail in the same way.

I would appreciate any help in understanding what I might be missing or what else I need to do to configure Auth0 to work with Superset.

Ryan Riley
  • 11
  • 2

1 Answers1

1

I was able to make it work using the JSON Web Key Set endpoint provided by Auth0, look at this example and adapt it accordingly:

from jose import jwt
from requests import request
from superset.security import SupersetSecurityManager


class CustomSecurityManager(SupersetSecurityManager):
    def request(self, url, method="GET", *args, **kwargs):
        kwargs.setdefault("headers", {})
        response = request(method, url, *args, **kwargs)
        response.raise_for_status()
        return response

    def get_jwks(self, url, *args, **kwargs):
        return self.request(url, *args, **kwargs).json()

    def get_oauth_user_info(self, provider, response=None):
        if provider == "auth0":
            id_token = response["id_token"]
            metadata = self.appbuilder.sm.oauth_remotes[provider].server_metadata
            jwks = self.get_jwks(metadata["jwks_uri"])
            audience = self.appbuilder.sm.oauth_remotes[provider].client_id
            payload = jwt.decode(
                id_token,
                jwks,
                algorithms=["RS256"],
                audience=audience,
                issuer=metadata["issuer"],
            )
            first_name, last_name = payload["name"].split(" ", 1)
            return {
                "email": payload["email"],
                "username": payload["email"],
                "first_name": first_name,
                "last_name": last_name,
            }
        return super().get_oauth_user_info(provider, response)
sebasuy
  • 494
  • 2
  • 17
  • Thank you for the response, and apologies for the delay in replying. I attempted to use your snippet, but my helm install fails with `Error: UPGRADE FAILED: post-upgrade hooks failed: timed out waiting for the condition`. I've tried uninstalling then re-installing, but I keep getting the same error. I tried adding `jose` and `python-jose` to the `bootstrapScript`, but the logs report the package cannot be found. – Ryan Riley Mar 28 '22 at 15:35
  • Specific error: `ModuleNotFoundError: No module named 'jose'` – Ryan Riley Mar 28 '22 at 15:51
  • Just a small note: python-jose has to be installed – Pavel Klammert May 30 '22 at 16:37