I'm trying to use Authlib to setup Keycloak as SSO for Superset. Everything works fine up until when user is redirected back to Superset. Then this error occured:
authlib.integrations.base_client.errors.MismatchingStateError: mismatching_state: CSRF Warning! State not equal in request and response.
Here's my code:
In superset_config.py
AUTH_TYPE = AUTH_OAUTH
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = 'Public'
CSRF_ENABLED = True
ENABLE_PROXY_FIX = True
OAUTH_PROVIDERS = [
{
'name': 'keycloak',
'token_key': 'access_token',
'icon': 'fa-icon',
'remote_app': {
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'client_kwargs': {
'scope': 'openid email profile'
},
'access_token_method': 'POST',
'api_base_url': 'https://KEYCLOAK_URL/auth/realms/REALM_NAME/protocol/openid-connect/',
'access_token_url': 'https://KEYCLOAK_URL/auth/realms/REALM_NAME/protocol/openid-connect/token',
'authorize_url': 'https://KEYCLOAK_URL/auth/realms/REALM_NAME/protocol/openid-connect/auth',
},
}
]
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
Here's my OIDCSecurityManager:
class OIDCSecurityManager(SupersetSecurityManager):
def get_oauth_user_info(self, provider, response=None):
if provider == 'keycloak':
me = self.appbuilder.sm.oauth_remotes[provider].get("userinfo")
return {
"first_name": me.data.get("given_name", ""),
"last_name": me.data.get("family_name", ""),
"email": me.data.get("email", "")
}
What can I do to resolve this problem? (this happens on all browser chrome, firefox, etc.)
I also had trouble with the OIDC configs, but this security manager configuration works for me.
Note - I've added roles to my Client configuration in Keycloak, and a mapper so the roles can be picked from the user info
response.
class CustomSsoSecurityManager(SupersetSecurityManager):
def oauth_user_info(self, provider, response=None):
logging.debug("Oauth2 provider: {0}.".format(provider))
logging.debug("Oauth2 oauth_remotes provider: {0}.".format(self.appbuilder.sm.oauth_remotes[provider]))
if provider == 'keycloak':
# Get the user info using the access token
res = self.appbuilder.sm.oauth_remotes[provider].get(os.getenv('KEYCLOAK_BASE_URL') + '/userinfo')
logger.info(f"userinfo response:")
for attr, value in vars(res).items():
print(attr, '=', value)
if res.status_code != 200:
logger.error('Failed to obtain user info: %s', res._content)
return
#dict_str = res._content.decode("UTF-8")
me = json.loads(res._content)
logger.debug(" user_data: %s", me)
return {
'username' : me['preferred_username'],
'name' : me['name'],
'email' : me['email'],
'first_name': me['given_name'],
'last_name': me['family_name'],
'roles': me['roles'],
'is_active': True,
}
def auth_user_oauth(self, userinfo):
user = super(CustomSsoSecurityManager, self).auth_user_oauth(userinfo)
roles = [self.find_role(x) for x in userinfo['roles']]
roles = [x for x in roles if x is not None]
user.roles = roles
logger.debug(' Update <User: %s> role to %s', user.username, roles)
self.update_user(user) # update user roles
return user