Oauth Integration of Frappe with Apache Superset

Hello All,

Trying to setup Frappe OAuth with Apache Superset.

  • Base Url in Social Login Key is http://frappehost.com

  • OAuth Client Setup redirect url as http://supersethost/oauth-authorized/frappe
    Grant Type - Authorization Code
    Response Type - Code

  • OAuth Settings in superset
    from flask_appbuilder.security.manager import AUTH_OAUTH
    AUTH_TYPE = AUTH_OAUTH
    OAUTH_PROVIDERS = [
    { 'name':'frappe',
    'token_key':'code', # Name of the token in the response of access_token_url
    'icon':'fa-address-card', # Icon for the provider
    'remote_app': {
    'client_id':'xxxxx', # Client Id (Identify Superset application)
    'client_secret':'yyyyy', # Secret for this Client Id (Identify Superset application)
    'client_kwargs':{
    'scope': 'openid' # Scope for the Authorization
    },
    'access_token_method':'POST', # HTTP Method to call access_token_url
    'access_token_params':{ # Additional parameters for calls to access_token_url
    'client_id':'xxxxx'
    },
    'access_token_headers':{ # Additional headers for calls to access_token_url
    'Authorization': 'token xxxxx:yyyyy'
    },
    'api_base_url':'http://frappehost.com/api/method/frappe.integrations.oauth2.openid_profile',
    'access_token_url':'http://frappehost.com/api/method/frappe.integrations.oauth2.get_token',
    'authorize_url':'http://frappehost.com/api/method/frappe.integrations.oauth2.authorize'
    }
    }
    ]

Below error occurs:
"POST /api/method/frappe.integrations.oauth2.get_token HTTP/1.1" 401 1620 superset_app | 2021-11-30 07:08:20,842:INFO:root:{'exc_type': 'AuthenticationError', 'exc': '["Traceback (most recent call last):\\n File \\"/home/frappe/frappe-bench/apps/frappe/frappe/app.py\\", line 66, in application\\n response = frappe.api.handle() \\n File \\"/home/frappe/frappe-bench/apps/frappe/frappe/api.py\\", line 40, in handle\\n validate_auth_via_api_keys()\\n File \\" /home/frappe/frappe-bench/apps/frappe/frappe/api.py\\", line 172, in validate_auth_via_api_keys\\n raise e\\n File \\"/home/frappe/ frappe-bench/apps/frappe/frappe/api.py\\", line 167, in validate_auth_via_api_keys\\n validate_api_key_secret(token[0], token[1])\\n File \\"/home/frappe/frappe-bench/apps/frappe/frappe/api.py\\", line 181, in validate_api_key_secret\\n user_secret = frappe.utils .password.get_decrypted_password (\\"User\\", user, fieldname=\'api_secret\')\\n File \\"/home/frappe/frappe-bench/apps/frappe/frappe/ utils/password.py\\", line 50, in get_decrypted_password\\n frappe.throw(_(\'Password not found\'), frappe.AuthenticationError)\\n File \\"/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py\\", line 360, in throw\\n msgprint(msg, raise_exception=exc, title= title, indicator=\'red\')\\n File \\"/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py\\", line 346, in msgprint\\n _raise_e xception()\\n File \\"/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py\\", line 315, in _raise_exception\\n raise raise_exc eption(msg)\\nfrappe.exceptions.AuthenticationError: Password not found\\n"]', '_server_messages': '["{\\"message\\": \\"Password not f ound\\", \\"indicator\\": \\"red\\"}"]'}

Able to get around this error if I comment the validate_auth_via_api_keys. It receives an Authorization Header with the OAuth Client Id and Secret (xxxxx:yyyyy) in above example. This is not available in the Frappe Users table and hence it fails.

Could someone help with what could be the possible configuration mistake ?

Thank you

these are the allowed flows in a test : https://github.com/frappe/frappe/blob/develop/frappe/tests/test_oauth20.py

it is not code it is access_token

no headers are required here.

I don’t think profile endpoint is the base_url

Thank you @revant_one.

It was a mistake in posting the snippet. The actual configuration used are:

'token_key':'access_token',
'api_base_url':'http://frappehost.com/api/resource',

Removed the access_token_headers and checked, but still getting the same error.

Not sure, why the Authorization Header gets passed, even if the configuration is removed.

what header is being passed?

pass some mock header, e.g. X-Mock-Header: Key 123

What gets passed is
Authorization: Basic Base64EncodedOAuthClientIdAndSecret

Whatever mock headers we set, those do not get passed in the /api/method/frappe.integrations.oauth2.get_token call.

Seems superset is adding this Authorization header based on the client_id and client_secret of the OAuth Client that we created in Frappe.

This will always fail, frappe api.py checks for basic header for all doctypes.
Can you pass Authorization: Bearer RandomToken and check if it overrides the Authorization header? If bearer token is unknown it doesn’t throw error, it just continues the request as Guest user

No matter, what is passed, it is getting overridden by the Authorization Basic header of OAuth ClientId and Client Secret.

Can there be some workaround for this ?

Core code needs to be updated.

refer this PR (https://github.com/frappe/frappe/pull/12895/files), I removed frappe.throw() and any kind of raised exception from code, that just makes the request pass through series of functions, and nothing is authenticated the end result should be request session user == Guest.

Will try and share updates.
Thank you.

It throws AuthenticationError which is not handled by the code, so the issue still remains the same.

try/except it?

Do you mean to say, I should make changes in my frappe core code, as the exception is getting raised from within frappe api.py ?

Yes. If it works, send a pull request.

This is what happened,

  • I added validate_oauth(), it validates an header and throws error if invalid
  • Someone else added similar function to validate secret and key.
  • I refactored my code to not throw any errors or raise exceptions, let the user be set or let it remain Guest. Framework takes care of permissions and errors as per user.
  • Refactor to code referenced from old validate_oauth() also needs refactor.
1 Like

sure… will take a look and share updates.

Yes, it works, if I try/ except the code.

@revant_one - Added this PR - https://github.com/frappe/frappe/pull/15161, not sure, what else needs to be done, as I am making Open source contribution for first time.

Try if this workaround works:

client_id = 'abcdefghij' # this is valid api_key
client_secret = 'jihgfedcba' # this is valid api_secret
cred_str = client_id + ":" + client_secret
encoded_cred_str = "Basic " + base64.b64encode(cred_str.encode('ascii')).decode("utf-8")

'access_token_headers':{    # Additional headers for calls to access_token_url
    'Authorization': encoded_cred_str
},

What I finally did to get this working is:
Added the below in nginx configuration (basically remove the Authorization Header for the get_token api)

set $xci $http_authorization;
if ($request_uri ~ "(.*)get_token(.*)") {
    set $xci "";
}
proxy_set_header Authorization $xci; 

May be helpful to any one facing similar issue in future.

Thank you for all the help @revant_one.

1 Like

Nice trick!

People who wish to authenticate their whitelisted endpoints with custom Basic Auth can also use this.