requests / requests-oauthlib

OAuthlib support for Python-Requests!
https://requests-oauthlib.readthedocs.org/
ISC License
1.72k stars 423 forks source link

Missing access Token parameter with OAuth2Session, but plain ol' requests works. #324

Open realimpat opened 6 years ago

realimpat commented 6 years ago

I'm building an api integration, and I can get it working using plain requests yet not with requests-oauthlib. The traceback goes to the oauthlib itself, but this is similar to unsolved issue 286 on this repo, so I'll post here for now.

The api I'm hitting isn't public, so I'll do my best to provide context even if you can't directly reproduce.

Here's what works with requests. This is at the callback stage, and the state is definitely the same state from the authorization url:

access_url = access_token_url + '?grant_type=authorization_code' + '&code=' + request.GET.get('code', '') + '&client_id=' + my_client_id + '&redirect_uri=' + redirect_uri + '&client_secret=' + client_secret + '&state=' + request.session['oauth_state']
token = requests.post(access_url)

Here's what fails with requests-oauthlib:

myobject = OAuth2Session(client_id = client_id, redirect_uri = redirect_uri, state = request.session['oauth_state'])
token = myobject.fetch_token(access_token_url, authorization_response=request.build_absolute_uri(), client_secret=client_secret

I'm fairly sure that the request.build_absolute_uri() is not the problem, because that part works for other API integrations; that most certainly returns the full url it needs to parse.

Anyways here's the error traceback:


File "/app/.heroku/python/lib/python3.6/site-packages/requests_oauthlib/oauth2_session.py" in fetch_token
  244.         self._client.parse_request_body_response(r.text, scope=self.scope)

File "/app/.heroku/python/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/base.py" in parse_request_body_response
  411.         self.token = parse_token_response(body, scope=scope)

File "/app/.heroku/python/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/parameters.py" in parse_token_response
  379.     validate_token_parameters(params)

File "/app/.heroku/python/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/parameters.py" in validate_token_parameters
  389.         raise MissingTokenError(description="Missing access token parameter.")

So, somehow oauthlib raises an error because it can't find the access token parameter when it tries to validate whether parse_token_response() worked. So something seems to be going wrong at parse_token_response().

And this is what the token looks like when we do obtain the token:

{
"access_token": “<access_token>”,
"expires_in": 36000.0,
"refresh_token”: “<refresh_token>”
}

If someone can tell me how to inspect exactly what raw http requests are being sent by object.fetch_token(), that would also help me diagnose further. Is there a way to inspect the oauth2session object to find that, or does anyone happen to know an easy way to find that for a django app on heroku? (it's not in heroku logs)

Thanks for contributing to such an elegant package. The overall quality really makes me want to fix this rather than use the plain old requests code in my app.

jorwoods commented 6 years ago

@realimpat I had the exact same problem. Wasn't a public API, so I couldn't publish the details of how I was calling it, and couldn't reproduce it without having to build my own webapp.

I tried running it with pdb turned on, and the refresh token in my case was definitely present in the response from the web server, just not be stored within the auth object that was exposed to me at the end. I was never able to pin down exactly where in the flow the refresh token wasn't getting passed / saved.

grissman commented 6 years ago

@singingwolfboy I had this problem too with a private API. The fetch_token call was returning a 401 and not generating a token.

I spoke with my API provider, and it turns out they require everything be passed in the url querystring, but there doesn't seem to be an option for that in the fetch_token method. Is this accurate? If so, can you add an option to pass params into the fetch_token method?

When I changed your oauth2_session.py fetch_token from self.post(data=dict(urldecode(body), ...) to self.post(params=dict(urldecode(body), ...), it worked. I also made a pull request in case that's easiest for you.

Thanks for this fantastic package!

datacubed commented 6 years ago

Has this been fixed at all? We use this package and it is excellent! But I'm having a similar issue to above.

client = LegacyApplicationClient(client_id=client_id) oauth = OAuth2Session(client=client) token = oauth.fetch_token( token_url=token_url, username=username, password=password, client_id=client_id, client_secret=client_secret)

This gives me the error:

Traceback (most recent call last): File "webgainstest.py", line 26, in client_secret=client_secret) File "/usr/local/lib/python2.7/dist-packages/requests_oauthlib/oauth2_session.py", line 244, in fetch_token self._client.parse_request_body_response(r.text, scope=self.scope) File "/usr/local/lib/python2.7/dist-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 411, in parse_request_body_response self.token = parse_token_response(body, scope=scope) File "/usr/local/lib/python2.7/dist-packages/oauthlib/oauth2/rfc6749/parameters.py", line 379, in parse_token_response validate_token_parameters(params) File "/usr/local/lib/python2.7/dist-packages/oauthlib/oauth2/rfc6749/parameters.py", line 389, in validate_token_parameters raise MissingTokenError(description="Missing access token parameter.") oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.

meyer1994 commented 5 years ago

@datacubed Same thing. But I get the invalid_grant error from the server. But using the old requests module works fine.

realimpat commented 5 years ago

My colleague grissman submitted a pull request that fixed this issue for us. I don't think it has been merged yet.

Essentially what was happening (at least in our case) is that the API provider only accepted parameters through the url querystring. For some reason, OAuthlib only accepts parameters through headers. So we modified the OAuthlib source code to allow an option to toggle if you need to do things through the urls instead of via headers.

The code works, so if you can view that PR you can implement it locally if you need in the meantime.

On Fri, Oct 5, 2018 at 3:37 PM João Meyer notifications@github.com wrote:

@datacubed https://github.com/datacubed Same thing. But I get the invalid_grant error from the server. But using the old requests module works fine.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/requests/requests-oauthlib/issues/324#issuecomment-427491583, or mute the thread https://github.com/notifications/unsubscribe-auth/AhMFp82F79iACYrN4KvcXhJLWFhuAtGbks5uh8LwgaJpZM4V3eVv .

jlaird-espresso commented 5 years ago

I had the same problem, but it went away when I put a trailing slash '/' on the token_url. That has nothing to do with the requests-oauthlib library -- it was a django rest server and the oauth2 token url routes needed that trailing slash. YMMV

ashutosh-aneja commented 5 years ago

oauthlib.oauth2.rfc6749.errors.MissingTokenError oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.

Traceback (most recent call last) File "/Python_2/env/lib/python3.5/site-packages/flask/app.py", line 2309, in call return self.wsgi_app(environ, start_response) File "//Python_2/env/lib/python3.5/site-packages/flask/app.py", line 2295, in wsgi_app response = self.handle_exception(e) File "/Python_2/env/lib/python3.5/site-packages/flask/app.py", line 1741, in handle_exception reraise(exc_type, exc_value, tb) File "/Python_2/env/lib/python3.5/site-packages/flask/_compat.py", line 35, in reraise raise value File "/Python_2/env/lib/python3.5/site-packages/flask/app.py", line 2292, in wsgi_app response = self.full_dispatch_request() File "/Python_2/env/lib/python3.5/site-packages/flask/app.py", line 1815, in full_dispatch_request rv = self.handle_user_exception(e) File "/Python_2/env/lib/python3.5/site-packages/flask/app.py", line 1718, in handle_user_exception reraise(exc_type, exc_value, tb) File "/Python_2/env/lib/python3.5/site-packages/flask/_compat.py", line 35, in reraise raise value File "/Python_2/env/lib/python3.5/site-packages/flask/app.py", line 1813, in full_dispatch_request rv = self.dispatch_request() File "/Python_2/env/lib/python3.5/site-packages/flask/app.py", line 1799, in dispatch_request return self.view_functionsrule.endpoint File "/Python_2/main.py", line 122, in callback token = google.fetch_token(Auth.TOKEN_URI,client_secret=Auth.CLIENT_SECRET,authorization_response=request.url) File "/Python_2/env/lib/python3.5/site-packages/requests_oauthlib/oauth2_session.py", line 244, in fetch_token self._client.parse_request_body_response(r.text, scope=self.scope) File "/Python_2/env/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 409, in parse_request_body_response self.token = parse_token_response(body, scope=scope) File "/Python_2/env/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 376, in parse_token_response validate_token_parameters(params) File "/env/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 386, in validate_token_parameters """Ensures token precence, token type, expiration and scope in params.""" if 'error' in params: raise_from_error(params.get('error'), params)

if not 'access_token' in params:
    raise MissingTokenError(description="Missing access token parameter.")

if not 'token_type' in params:
    if os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'):
        raise MissingTokenTypeError()

oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.

how to solve this,plz anybody help me.

karivatj commented 5 years ago

Any updates to this? I'm having similar problems as OP when trying to access Withings API. Weird enough, Google API seems to be working as expected tho.

Traceback (most recent call last): File "nokia_api.py", line 11, in <module> creds = auth.get_credentials(authorization_response) File "C:\Python36\lib\site-packages\nokia\__init__.py", line 86, in get_credentials client_secret=self.consumer_secret) File "C:\Python36\lib\site-packages\requests_oauthlib\oauth2_session.py", line 244, in fetch_token self._client.parse_request_body_response(r.text, scope=self.scope) File "C:\Python36\lib\site-packages\oauthlib\oauth2\rfc6749\clients\base.py", line 415, in parse_request_body_response self.token = parse_token_response(body, scope=scope) File "C:\Python36\lib\site-packages\oauthlib\oauth2\rfc6749\parameters.py", line 425, in parse_token_response validate_token_parameters(params) File "C:\Python36\lib\site-packages\oauthlib\oauth2\rfc6749\parameters.py", line 435, in validate_token_parameters raise MissingTokenError(description="Missing access token parameter.") oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.

psilo909 commented 5 years ago

same for me with withings api :-/

gnomic commented 5 years ago

Getting the same err: MissingTokenError: (missing_token) Missing access token parameter.

psilo909 commented 5 years ago

I downgraded the requests-oauthlib library and now my requests with python-nokia are fine again..

adamhooper commented 5 years ago

I got this same experience with Intercom OAuth; I added include_client_id=True to my session.fetch_token() params and now it works.

librarywebchic commented 5 years ago

I noticed that if your code don't set a scope for your OAuth request, you get this error. I was using the "Backend Application Flow" as documented - https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#backend-application-flow

And it didn't work because of an error in the documentation which says

>>> from oauthlib.oauth2 import BackendApplicationClient
>>> client = BackendApplicationClient(client_id=client_id)
>>> oauth = OAuth2Session(client=client)
>>> token = oauth.fetch_token(token_url='https://provider.com/oauth2/token', client_id=client_id,
        client_secret=client_secret)

And should say something like

>>> scope = ['hello','world']
>>> from oauthlib.oauth2 import BackendApplicationClient
>>> client = BackendApplicationClient(client_id=client_id, scope=scope)
>>> oauth = OAuth2Session(client=client)
>>> token = oauth.fetch_token(token_url='https://provider.com/oauth2/token', client_id=client_id,
        client_secret=client_secret)
shaftoe commented 4 years ago

I was also hitting this even following all the above recommendations (beside downgrading) and this commit from another project got me on the right track.

Basically I was hitting the wrong endpoint path but from the Traceback it was almost impossible to tell. I leave the details here for the records:

def get_oauth_token(client_id, client_secret, token_url):
    """https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html"""

    scope = ['hello','world']

    from oauthlib.oauth2 import BackendApplicationClient
    client = BackendApplicationClient(client_id=client_id, scope=scope)

    from requests_oauthlib import OAuth2Session
    oauth = OAuth2Session(client=client)
    token = oauth.fetch_token(token_url=token_url,
                              client_id=client_id,
                              client_secret=client_secret,
                              include_client_id=True)

    return token
Traceback (most recent call last):
  File "[...]lambda_utils.py", line 130, in <module>
    "[...url...]",
  File "[...]lambda_utils.py", line 122, in get_oauth_token
    include_client_id=True)
  File "[...]requests_oauthlib/oauth2_session.py", line 360, in fetch_token
    self._client.parse_request_body_response(r.text, scope=self.scope)
  File "[...]oauthlib/oauth2/rfc6749/clients/base.py", line 421, in parse_request_body_response
    self.token = parse_token_response(body, scope=scope)
  File "[...]oauthlib/oauth2/rfc6749/parameters.py", line 431, in parse_token_response
    validate_token_parameters(params)
  File "[...]oauthlib/oauth2/rfc6749/parameters.py", line 441, in validate_token_parameters
    raise MissingTokenError(description="Missing access token parameter.")
oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.

Testing this on macOS 10.14.6:

$ python
Python 3.7.6 (default, Jan 18 2020, 11:17:36) 
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import oauthlib
>>> import requests_oauthlib
>>> oauthlib.__version__, requests_oauthlib.__version__
('3.1.0', '1.3.0')
calcium commented 4 years ago

I was having the same error... raise MissingTokenError(description="Missing access token parameter.") oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.

and in my case, the URL I was using was wrong. :-)

I had "oauth2/token" when in my case it should have been "oauth/token"

Example cut and pasted from the readthedocs.io works, after my custom changes as above.

shaftoe commented 4 years ago

@calcium unfortunately I've forgot about the circumstances of this issue, it might be I was facing the same error as yours (wrong URL), thanks for pointing it out

heathervant commented 4 years ago

I am having the same error message: raise MissingTokenError(description="Missing access token parameter.") oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.

I am curious what file you @calcium are editing when you say "I had "oauth2/token" when in my case it should have been "oauth/token"" ? also what do you mean by wrong url? I have my redirectURI for my Mendeley application set to http://localhost:5000/oauth because I am following the flask app example, but I may be totally missing something here.

clapas commented 4 years ago

This happened to me and the problem was in the response, it was lacking the expected JSON structure consisting of an object with the key access_token set —I was returning the response badly from my mock server.

JimHokanson commented 3 years ago

The following should apply to most cases but I figure I'll add my specifics first.

I'm trying to make a request with a token I know is expired (needs to be refreshed). I'm using the "3rd" option for refreshing outlined here:

https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#refreshing-tokens

My calling code looks like:

# token was populated from a previous call but contains the access and refresh tokens 
refresh_url = 'https://api.fitbit.com/oauth2/token'
extra = {
    'client_id': client_id,
    'client_secret': client_secret,
}
client = OAuth2Session(client_id, token=token, auto_refresh_url=refresh_url,
                       auto_refresh_kwargs=extra, token_updater=token_saver)
url = 'https://api.fitbit.com/1.2/user/-/sleep/date/2021-01-01-2021-01-23.json'
wtf = client.get(url)

So, when a call gets made, if the token is expired, OAuth2Session has a method called request which we are calling (via get) that tries to refresh the token. However, in my case, there is no authorization (which requires the client_secret). Basically this call:

                    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)

But client secret and client_id would need to be passed in via get I think. I'm not sure how to pass in 'auth' or if auth would be populated elsewhere (but again, this would require the client_secret which we aren't really passing into the session constructor). The end result is the call to the refresh token has no authorization.

Anyway, much of this may be specific to my case, but here's the generic part. In the refresh call, there is this line:

        self.token = self._client.parse_request_body_response(r.text, scope=self.scope)

Calling this line throws the missing access token error. I think the correct way of interpreting this error, in my case, is not that I did not pass in an access token (what I originally thought), but that the response from the server did not contain an access token!

So what happened in my case? (and how might this help you)

The error is coming from oauthlib:

 if not 'access_token' in params:
        raise MissingTokenError(description="Missing access token parameter.")

So, I wanted to see what exactly was missing, and changed this code to:

 if not 'access_token' in params:
        import pdb
        pdb.set_trace()
        raise MissingTokenError(description="Missing access token parameter.")

Basically I've just enabled debugging. Feel free to use your own debugging approach. In debugging I can now look at the value of params, which is:

{'errors': [{'errorType': 'invalid_client', 'message': 'Invalid authorization header format. The header was not recognized to be a valid header for any of known implementations or a client_id was not specified in case of a public client Received header = null. Visit https://dev.fitbit.com/docs/oauth2 for more information on the Fitbit Web API authorization process.'}], 'success': False}

So basically fitbit threw a bunch of json back at us saying my request was bad because there was no authorization. OK, so I still don't know how to fix that, but at least I know what the general problem is.

So in summary, this error can be triggered when the response from the server is missing an access token parameter, not necessarily that your call is missing an access token (Note, if you're trying to get an access token and not a refresh token, this may be more obvious to you since you don't already have an access token). Unfortunately, errors from the server are being swallowed by requests-oauthlib and/or oauthlib? such that instead of the error message from the server (with potentially more relevant details), you only are told that the access token is missing (i.e. "something went wrong and we're not sure what").

Note, I haven't dissected this call more:

        self.token = self._client.parse_request_body_response(r.text, scope=self.scope)

But it seems like it may be a good idea if either this was wrapped in a try-except statement, or the status code from the refresh call (or access call) was inspected, or perhaps both ...

and again, I don't know how to fix my code, but I'm off to SO to follow up (someone previously asked this question) https://stackoverflow.com/questions/62086784/how-to-refresh-the-token-in-requests-oauthlib

Hopefully this helps ...

thencke commented 3 years ago

I got this same experience with Intercom OAuth; I added include_client_id=True to my session.fetch_token() params and now it works.

This solution worked for Teamwork API, thanks

andvsilva commented 2 years ago

Hi, Everyone.

`Linux 20.04.1-Ubuntu 2021 x86_64 x86_64 x86_64 GNU/Linux

$ python --version Python 3.8.8 `

I am trying to use the module "requests_oauthlib" (pip install requests_oauthlib)

raise MissingTokenError(description="Missing access token parameter.") oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter. 256

To investigate this issue, I did the example from Backend Application Flow

>>> client_id = 'your_client_id'
>>> client_secret = 'your_client_secret'

>>> from oauthlib.oauth2 import BackendApplicationClient
>>> from requests_oauthlib import OAuth2Session
>>> client = BackendApplicationClient(client_id=client_id)
>>> oauth = OAuth2Session(client=client)
>>> token = oauth.fetch_token(token_url='https://provider.com/oauth2/token', client_id=client_id,
        client_secret=client_secret)

Does not work.

I am also using the module icecream for debug: pip install icecream

ic| oauthlib.__version__: '3.1.1'
    requests_oauthlib.__version__: '1.3.0'
ic| token_url: 'https://provider.com/oauth2/token'
ic| auth: <requests.auth.HTTPBasicAuth object at 0x7fb7f0b23940>
ic| client: <oauthlib.oauth2.rfc6749.clients.backend_application.BackendApplicationClient object at 0x7fb7f0b1ba00>
ic| oauth: <requests_oauthlib.oauth2_session.OAuth2Session object at 0x7fb7f0a7bee0>
Traceback (most recent call last):
  File "auth.py", line 25, in <module>
    token = oauth.fetch_token(token_url=token_url, auth=auth)
  File "/home/andsilva/anaconda3/lib/python3.8/site-packages/requests_oauthlib/oauth2_session.py", line 360, in fetch_token
    self._client.parse_request_body_response(r.text, scope=self.scope)
  File "/home/andsilva/anaconda3/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 429, in parse_request_body_response
    self.token = parse_token_response(body, scope=scope)
  File "/home/andsilva/anaconda3/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 425, in parse_token_response
    validate_token_parameters(params)
  File "/home/andsilva/anaconda3/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 435, in validate_token_parameters
    raise MissingTokenError(description="Missing access token parameter.")
oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.
256

to reproduce the issue: the source code

from oauthlib.oauth2 import BackendApplicationClient
from requests.auth import HTTPBasicAuth
from requests_oauthlib import OAuth2Session
import oauthlib, requests_oauthlib

from icecream import ic

ic(oauthlib.__version__, requests_oauthlib.__version__)

client_id = 'my_id'
client_secret = 'my_secret'
token_url = 'https://provider.com/oauth2/token'

ic(token_url)

auth = HTTPBasicAuth(client_id, client_secret)
ic(auth)

client = BackendApplicationClient(client_id=client_id)
ic(client)

oauth = OAuth2Session(client=client)
ic(oauth)

token = oauth.fetch_token(token_url=token_url, auth=auth)
ic(token)

Someone have an idea of how to fix this,

Thanks in advance, cheers.

Andre

andvsilva commented 2 years ago

Work in progress.

following the traceback Error:

I found this:

ic| oauthlib.__version__: '3.1.1'
    requests_oauthlib.__version__: '1.0.0'
ic| token_url: 'https://provider.com/oauth2/token'
ic| auth: <requests.auth.HTTPBasicAuth object at 0x7f3fdfcc7100>
ic| client: <oauthlib.oauth2.rfc6749.clients.backend_application.BackendApplicationClient object at 0x7f3fdfccd100>
ic| oauth: <requests_oauthlib.oauth2_session.OAuth2Session object at 0x7f3fdfeba2e0>
> /home/andsilva/anaconda3/lib/python3.8/site-packages/oauthlib/oauth2/rfc6749/parameters.py(434)validate_token_parameters()
-> if 'error' in params:
(Pdb) l
429     
430     
431     def validate_token_parameters(params):
432         breakpoint()
433         """Ensures token presence, token type, expiration and scope in params."""
434  ->     if 'error' in params:
435             raise_from_error(params.get('error'), params)
436     
437         if not 'access_token' in params:
438             raise MissingTokenError(description="Missing access token parameter.")

-> params does not have 'access_token'

and the variable params in the file parameters.py (source code): In line 424: params = OAuth2Token(params, old_scope=scope)

So, maybe the class OAuth2Token does not return the acess_token : hash commit

Hi @ib-lundgren can you help me in this issue? I hope this description can help you. Thank you very much!

KaiRoesner commented 2 years ago

I guess these issues may be related to #302 .

adyekjaer commented 2 years ago

If this helps anyone, I had to make requests against a {'Server': 'Microsoft-Azure-Application-Gateway/v2'} server which apparently have some sort of sick WAF setting. Adding the following to fetch_token() fixed my problem.

headers={'User-Agent': 'PostmanRuntime/7.29.0'}

NotSoShaby commented 11 months ago

interesting that our of all, this comment by @adyekjaer worked for me. Thanks

BEEFF commented 2 months ago

I had a similar issue with using the incorrect token url, if you open up

.../oauthlib/oauth2/rfc6749/parameters.py

Add the line print(params)

This should help you debug the issue.

bradthurber commented 2 weeks ago

I encountered this issue, and the resolution was to add include_client_id=True to the call to oauth.fetch_token.

scope = ["domain.filesystem"]

oauth = OAuth2Session(client=LegacyApplicationClient(client_id=client_id,scope=scope))
token = oauth.fetch_token(
    token_url="https://api.domain.com/puboauth/token",
    username=username,
    password=password,
    client_id=client_id,
    client_secret=client_secret,
    include_client_id=True,
)

The clue was in the source file here https://github.com/requests/requests-oauthlib/blob/master/requests_oauthlib/oauth2_session.py