-
-
Couldn't load subscription status.
- Fork 426
Description
First of all, thank you for this wonderful library. It works great and saved me tons of time.
I am having issues with one aspect: auto token refresh. I have identified 3 distinct problems, which I will describe below and for which I will suggest solutions. I will also reference this issue from multiple other related issues. I am happy to provide a PR once we can agree upon a suitable solution.
I derived my solution from the following, as my issue is a variant of the same issue:
All of the problems start here:
auth = kwargs.pop('auth', None)
if client_id and client_secret and (auth is None):
log.debug('Encoding client_id "%s" with client_secret as Basic auth credentials.', client_id)
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
token = self.refresh_token(
self.auto_refresh_url, auth=auth, **kwargs)Issue 1
If I pass an auth kwarg intended for the request() method, it is eradicated here. I am not sure if this is an issue in practice.
Issue 2
Box, Google and Onedrive do not expect the client_id and client_secret to be provided via Basic authentication ala Authorization header. They instead expect these POSTed in the body. There is no way to customize this behavior. Also, I don't know what vendors expect what method here, I only know the 3 above as that is what I am working with.
Issue 3
Any kwargs I pass which are intended for the request() method end up being passed to refresh_token() and are used when accessing the token endpoint. For example, if my original request() uploads a file, that file will be transferred to the token endpoint when attempting to refresh the token.
Issue 4
client_id and client_secret are not normally passed to request() but are necessary for token refresh. Passing them is not a problem except for unintended consequences of them being passed along to super().request()
Proposed solution.
I think all of the above can be solved by making two changes.
-
Pass the
client_idandclient_secretto__init__()where they are stored as attributes (ofOAuth2Sessionor underlyingWebApplicationClient. In factclient_idis already handled this way, butfetch_token(),refresh_token()etc. do no utilize it and expect it passed again as a kwarg. This addresses issue 4. -
Add a
refresh_kwargsargument that accepts a dict, and provides kwargs passed along totoken_refresh()thus keeping them separate from the regular kwargs intended forrequest(). This solves issues 1 & 3. -
Place the
client_idandclient_secretinto the POST body by default, all three of the providers I have tried expect this instead of basic auth. Callers can userefresh_kwargsto provide anauthkwarg if necessary (their provider expects this instead). Optionally, ifauthis passed, theclient_idandclient_secretcould be omitted from the POST body (it is unlikely that a provider expects both). This solves issue 3.
I present my current workaround, which works perfectly for my providers.
def __init__(self):
# Omit token refresh args, we do this manually
self.oauthsession = OAuth2Session(token=token, **kwargs)
def request(self, method, url, chunk, headers={}, **kwargs):
"""
Perform HTTP request for OAuth.
"""
while True:
try:
return self.oauthsession.request(method, url, headers=headers,
**kwargs)
except TokenExpiredError:
# Do our own, since requests_oauthlib is broken.
token = self.oauthsession.refresh_token(
self.REFRESH_TOKEN_URL,
refresh_token=self.oauth_access.refresh_token,
client_id=self.provider.client_id,
client_secret=self.provider.client_secret)
self._refresh_token_callback(token)
continue