Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support async functions in compliance hooks. #619

Open
caarmen opened this issue Jan 14, 2024 · 0 comments
Open

Support async functions in compliance hooks. #619

caarmen opened this issue Jan 14, 2024 · 0 comments

Comments

@caarmen
Copy link

caarmen commented Jan 14, 2024

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 the post() function, then the Authorization: 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant