Support async functions in compliance hooks.
caarmen opened this issue · 0 comments
Is your feature request related to a problem? Please describe.
It's not possible to execute an additional request when using automatic token refresh.
Describe the solution you'd like
Be able to define an async function as a compliance hook.
Example:
def withings_compliance_fix(session: AsyncOAuth2Client):
async def _fix_refresh_token_request(url, headers, body):
async with httpx.AsyncClient() as client:
nonce_response = await client.post (...) # call Withings api to get a nonce
signing_params = # use the nonce to add the additional required parameters
body = add_params_to_qs(body, signing_params)
return url, headers, body
session.register_compliance_hook(
"refresh_token_request", _fix_refresh_token_request
)
oauth.register(
name="withings",
...
compliance_fix=withings_compliance_fix,
)
This fails in client.py:refresh_token():
for hook in self.compliance_hook['refresh_token_request']:
url, headers, body = hook(url, headers, body) # <---- here 👀
Error: TypeError: cannot unpack non-iterable coroutine object
Describe alternatives you've considered
Alternative 1: custom Auth
.
I've tried an alternative which worked for retrieving the initial access token, specifying an alternate Auth
implementation:
class SignatureAuth(OAuth2ClientAuth):
def auth_flow(
self, request: httpx.Request
) -> typing.Generator[httpx.Request, httpx.Response, None]:
nonce_request = prepare_nonce_request()
nonce_response = yield nonce_request
signed_request = # Extract the nonce, create a new request with the additional signing params
yield from super().auth_flow(signed_request)
...
withings: StarletteOAuth2App = oauth.create_client("withings")
response = await withings.authorize_access_token(
request,
auth=SignatureAuth(),
)
This approach doesn't work when I want to access a resource, using an expired access token.
withings: StarletteOAuth2App = oauth.create_client("withings")
response = await withings.post(url_to_resource, data=data, token=expired_token)
- If I specify my custom
auth
in thepost()
function, then theAuthorization: Bearer
header isn't added to requests with a non-expired token. - Even ignoring this problem with the Authorization header, my custom auth wouldn't be used anyway to refresh the token. In oauth2_client.py:ensure_active_token(), we have:
new_token = await self.fetch_token(url, grant_type='client_credentials')
It doesn't pass any auth
argument to fetch_token
. It falls back to OAuth2ClientAuth
.
Alternative 2: Don't sign requests
Additional context
Withings supports two ways to retrieve tokens. Here's the api doc.
It supports "using signature", which is what I've tried to do here.
It also supports "using secret". In this case, Authlib works just fine with token_endpoint_auth_method=client_secret_post
.
I just thought it would be better to not send the client secret over the network if possible. Note that, it appears that some of Withings apis (not all) require the signature approach.
Note: even though the Withings api has notions of nonce and signature, it doesn't appear to be oauth1. With client_secret_post
, it works fine with Authlib oauth2 apis.