pyatmo icon indicating copy to clipboard operation
pyatmo copied to clipboard

ClientAuth stopped working after Netatmo discontinued the /oauth2/token API

Open Lock128 opened this issue 1 year ago • 4 comments

Hey

It looks like the /oauth2/token API was finally deprecated...and because of that the things described in usage.md dont work anymore see marker here: https://dev.netatmo.com/apidocumentation/oauth#client-credential

error message:

DEBUG:requests_oauthlib.oauth2_session:Encoding `client_id` "5ef5c45497729b1a4d7fe0c3" with `client_secret` as Basic auth credentials.
DEBUG:requests_oauthlib.oauth2_session:Requesting url https://api.netatmo.com/oauth2/token using method POST.
DEBUG:requests_oauthlib.oauth2_session:Supplying headers {'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'} and data {'grant_type': 'password', 'username': 'user, 'password': 'password', 'scope': 'read_station'}
DEBUG:requests_oauthlib.oauth2_session:Passing through key word arguments {'timeout': None, 'auth': <requests.auth.HTTPBasicAuth object at 0xb59efa50>, 'verify': True, 'proxies': None}.
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.netatmo.com:443
DEBUG:urllib3.connectionpool:https://api.netatmo.com:443 "POST /oauth2/token HTTP/1.1" 400 None
DEBUG:requests_oauthlib.oauth2_session:Request to fetch token completed with status 400.
DEBUG:requests_oauthlib.oauth2_session:Request url was https://api.netatmo.com/oauth2/token
DEBUG:requests_oauthlib.oauth2_session:Request headers were {'User-Agent': 'python-requests/2.21.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': 'application/json', 'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'Content-Length': '96', 'Authorization': 'Basic NWVmNWM0NTQ5NzcyOWIxYTRkN2ZlMGMzOjJIb2ZDdEx5RGtSQ1VXTG52eGxCYWVod2ZtdVJGWXgwR29VQ1A2aDdVQg=='}
DEBUG:requests_oauthlib.oauth2_session:Request body was grant_type=password&username=user&password=password&scope=read_station
DEBUG:requests_oauthlib.oauth2_session:Response headers were {'Server': 'nginx', 'Date': 'Thu, 13 Jul 2023 08:34:44 GMT', 'Content-Type': 'application/json', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Expires': '0', 'Cache-Control': 'no-cache, must-revalidate', 'X-XSS-Protection': '1; mode=block', 'Access-Control-Allow-Origin': '*', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'X-Powered-By': 'Netatmo'} and content {"error":"unauthorized_client"}.
DEBUG:requests_oauthlib.oauth2_session:Invoking 0 token response hooks.
Traceback (most recent call last):
  File "lockatmo6.py", line 38, in <module>
    scope="read_station",
  File "/home/pi/.local/lib/python3.7/site-packages/pyatmo/auth.py", line 266, in __init__
    scope=scope,
  File "/home/pi/.local/lib/python3.7/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/pi/.local/lib/python3.7/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 421, in parse_request_body_response
    self.token = parse_token_response(body, scope=scope)
  File "/home/pi/.local/lib/python3.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 431, in parse_token_response
    validate_token_parameters(params)
  File "/home/pi/.local/lib/python3.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 438, in validate_token_parameters
    raise_from_error(params.get('error'), params)
  File "/home/pi/.local/lib/python3.7/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 405, in raise_from_error
    raise cls(**kwargs)
oauthlib.oauth2.rfc6749.errors.UnauthorizedClientError: (unauthorized_client) ```

Lock128 avatar Jul 13 '23 08:07 Lock128

https://forum.netatmo.com/viewtopic.php?t=21383

Lock128 avatar Jul 14 '23 13:07 Lock128

I came across this error too in my ETL jobs.

So if anyone else is wondering how to implement a non-interactive authentication flow now:

First you have to generate a refresh token at https://dev.netatmo.com. Just select your app, scroll down to the "Token generator" and generate a new one with the appropriate scopes. Then grab the refresh token and pluck it into the following snippet:

auth = pyatmo.NetatmoOAuth2(
    client_id=client_id,
    client_secret=client_secret
)
auth.extra["refresh_token"] = "<refresh-token>"
auth.refresh_tokens()

client = pyatmo.WeatherStationData(auth) // or pyatmo.PublicData, pyatmo.HomeData, ...
...

Since refresh tokens don't seem to expire this should work for the foreseeable future. Although I'm not sure if we'll have to call refresh_tokens() manually again, once the access token expires - since I create a new client every time my jobs run.

oscarsommerer avatar Jul 15 '23 12:07 oscarsommerer

This worked well for me when I tried it, thanks a lot @oscarsommerer

Is there a documentation on when the tokens expire?

Are you "saving" the results of auth.refresh_tokens() somewhere?

Lock128 avatar Jul 17 '23 10:07 Lock128

Is this still an issue?

cgtobi avatar Nov 26 '23 20:11 cgtobi