'Apache Superset and Auth0 returns "The browser (or proxy) sent a request that this server could not understand."

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.



Solution 1:[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)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1