Skip to content

Unclear how to stay authed for longer than a day #119

Description

@stevenirby

I'm not sure how to keep my app authed for longer than one day.

Here is what I'm doing:

  • Running gather_keys_oauth2.py to get tokens.
  • In my app I do this.
    authd_client = fitbit.Fitbit(USER_ID, CONSUMER_SECRET,
                             access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)

How do I auth the app for much longer time?

Activity

  1. brad commented on May 5, 2017

    @brad
    Member

    Sorry @stevenirby the decumentation is a bit lacking on this (non-existent). What you need to do is supply an extra kwarg in the Fitbit constructor called refresh_cb. The value should be a function that accepts one argument: a token. The value of that argument will be a dictionary with the keys ['access_token', 'refresh_token', 'expires_at'].

    When python fitbit makes a call and discovers the access token is expired, it will automatically refresh the token and pass the new token to the function specified by refresh_cb. You will need to implement this function to store your new token somewhere persistent. The current fitbit client will automatically use the new token so there is no need to update anything there, but the next time you create a fitbit client, you will need to use the updated token that you saved. Does that make sense?

    See the function we use in django-fitbit for an example: https://github.com/orcasgit/django-fitbit/blob/3e696d45d58dac1a097d1ed7f82339895418705e/fitapp/models.py#L24

  2. stevenirby commented on May 5, 2017

    @stevenirby
    Author

    Oh wow, thanks for the quick reply! I figured it was close to that. I think I'm good now.

    Do you need some help updating docs or the README?

  3. brad commented on May 6, 2017

    @brad
    Member

    @stevenirby we would totally appreciate help with the docs!

  4. toothie96 commented on May 15, 2017

    @toothie96

    For those of us with little experience with code, could you possibly write out what lines need to be added and where? It'd be a massive help! thanks!

  5. lrpina commented on Jun 6, 2017

    @lrpina

    Hello!

    First, thanks for putting together this api and all the help provided a long the way.
    I'm still having trouble with refreshing the token and setting up refresh_cb correctly. I've approached setting up refresh_cb in several ways. Including:

    • assigning refresh_cb to an anonymous function. Like the following:

    lambda x: None

    or

    lambda x: {}

    I also added more specific fields to the token argument to the refresh_cb constructor. I actually replicated the function shown in: https://github.com/orcasgit/django-fitbit/blob/3e696d45d58dac1a097d1ed7f82339895418705e/fitapp/models.py#L24

    which makes a lot of sense! And so I replicated the function:

    def refresh_cb(self, token):
    	 """ Called when the OAuth token has been refreshed """
    	 self.access_token = token['access_token']
    	 self.refresh_token = token['refresh_token']
    	 self.expires_at = token['expires_at']
    	 self.save
    

    For background, this is how initialize the fitbit object

    authd = fitbit.Fitbit(consumer_key, consumer_secret, access_token=access_token, refresh_token=refresh_token, redirect_uri='http://localhost:8080', expires_at=expires_at, refresh_cb=refresh_cb)

    But I still get the error. I've tried to add print statements the api.py to trace the error and the error is triggered in the call to _request specifically where response = self.session.request(method, url, **kwargs)

    From what concerns me here is that code breaks before token_updater in _request is called.
    Help would be appreciated!

    in make request no url
    in make_request with url
    in _request
    printing **kwargs
    headers -> {'Accept-Language': 'en_US'}
    data -> {}
    client_id -> _client_id_ #not shown here for privacy
    client_secret -> _client_secret_ #not shown here for privacy
    Traceback (most recent call last):
      File "simple_request.py", line 104, in <module>
        sleep_data = authd.get_sleep(date)
      File "/Users/lpina/Repositories/python-fitbit/fitbit/api.py", line 833, in get_sleep
        return self.make_request(url)
      File "/Users/lpina/Repositories/python-fitbit/fitbit/api.py", line 279, in make_request
        response = self.client.make_request(*args, **kwargs)
      File "/Users/lpina/Repositories/python-fitbit/fitbit/api.py", line 109, in make_request
        **kwargs
      File "/Users/lpina/Repositories/python-fitbit/fitbit/api.py", line 80, in _request
        response = self.session.request(method, url, **kwargs)
      File "/Library/Python/2.7/site-packages/requests_oauthlib/oauth2_session.py", line 341, in request
        self.auto_refresh_url, auth=auth, **kwargs
      File "/Library/Python/2.7/site-packages/requests_oauthlib/oauth2_session.py", line 309, in refresh_token
        self.token = self._client.parse_request_body_response(r.text, scope=self.scope)
      File "/Library/Python/2.7/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 409, in parse_request_body_response
        self.token = parse_token_response(body, scope=scope)
      File "/Library/Python/2.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 376, in parse_token_response
        validate_token_parameters(params)
      File "/Library/Python/2.7/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 383, in validate_token_parameters
        raise_from_error(params.get('error'), params)
      File "/Library/Python/2.7/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 325, in raise_from_error
        raise cls(**kwargs)
    oauthlib.oauth2.rfc6749.errors.InvalidGrantError: (invalid_grant) 
    

    For background here's my version of the python-fitbit repository:

    https://github.com/lrpina/python-fitbit

  6. stevenirby commented on Jun 6, 2017

    @stevenirby
    Author

    I haven't had time to come back to this, but I too had problems making this work.

    The call back function I passed along never fired. So I'm not sure how to make this work.

  7. lrpina commented on Jun 6, 2017

    @lrpina

    @stevenirby, thanks for the quick reply. Hopefully others can chime in.

  8. lrpina commented on Jun 13, 2017

    @lrpina

    Update! I think it I got it work. Here's how I got mine to work:

    def r_cb(token):
    	 """ Called when the OAuth token has been refreshed """
    	 access_token = token['access_token']
    	 refresh_token = token['refresh_token']
    	 expires_at = token['expires_at']
    
    

    where the variables on the left ( access_token, refresh_token, expires_at) are local variables that get updated when r_cb is called.

  9. brad commented on Jun 13, 2017

    @brad
    Member

    @lrpina That could work, but you need to make sure to save those values somewhere permanent (like a database) and use them the next time you create a Fitbit object for the user. Does that make sense?

  10. lrpina commented on Jun 13, 2017

    @lrpina

    @brad, yes! I was just showing an example for others to view what the callback function looks like. Many of use couldn't figure out how to write the refresh_cb function properly. For example, it was unclear what the token dictionary needed to include.

    But yes, thanks for clarification. Callback function (in my case r_cb) needs to save the access_token, refresh_token, expires_at to permanent place, such as a database.

  11. tchellomello commented on Aug 30, 2017

    @tchellomello
    Contributor

    Thanks for the notes on this issue. I was able to fix a problem this problem home-assistant/core#9183 for Home Assistant.

  12. stuboo commented on Aug 17, 2018

    @stuboo

    This feature is incredibly valuable and, with the help of this thread, I'm able to successfully execute the callback. My issue is this: because the callback function can only take one variable, I'm at a loss as to how to store the tokens dictionary in my database. I have multiple users and need to be able to insert the credentials with the correct user_id.

    I am fully aware that my [in]ability do this is directly proportional to my [lack of] skill as a python programmer so I'm not asking for a change in the code (or even the documentation), but for your advice on how you would solve this problem. Thanks!

    For example:

    def refresh_callback(tokens):
        """ Called when the OAuth token has been refreshed """
        fbm.store_tokens(study_id, tokens) # a db call in my model
    
    if __name__ == "__main__":
        result = []
        for a in active_user_tokens:
            study_id = a['study_id']
            authd_client = fitbit.Fitbit(config.CLIENT_ID,
                                         config.CLIENT_SECRET,
                                         access_token=a['credentials']['access_token'],
                                         refresh_token=a['credentials']['refresh_token'],
                                         expires_at =a['credentials']['expires_in'],
                                         refresh_cb=refresh_callback)
            result.append(authd_client.sleep())
        print(result)

    This code gives me the sleep data (and I can print the new tokens inside the refresh_callback function, but I need access to those variables inside the loop so that I can update the database by a['user_id'].

    ::facepalm::
    global variable
    code updated to reflect what actually works

  13. brad commented on Aug 17, 2018

    @brad
    Member

    @stuboo Take a look at how we did it in django-fitbit. The refresh callback is actually a method on the DB model class so it has access to everything it needs:

        def refresh_cb(self, token):
            """ Called when the OAuth token has been refreshed """
            self.access_token = token['access_token']
            self.refresh_token = token['refresh_token']
            self.expires_at = token['expires_at']
            self.save()
    

    Will something like that work for your case?

  14. pete7863 commented on Jun 20, 2019

    @pete7863

    Has anyone had any success getting the refresh callback function to fire? My code is as follows

    import datetime
    import fitbit
    import json
    
    
    def refresh_callback(token):
        f_data = {
            'access_token': token['access_token'],
            'refresh_token': token['refresh_token'],
            'expires_at': token['expires_at']
        }
    
        with open('access_token_cache_file.json', 'w') as outfile:
            json.dump(f_data, outfile)
    
    with open('fitbit_config.json') as json_file:
        data = json.load(json_file)
        fitbit_user_id = data['fitbit']['user_id']
        fitbit_client_id = data['fitbit']['client_id']
    
    with open('access_token_cache_file.json') as json_file:
        data = json.load(json_file)
        access_token = data['access_token']
        refresh_token = data['refresh_token']
        expires_at = data['expires_at']
    
    client = fitbit.Fitbit(fitbit_user_id, fitbit_client_id, access_token=access_token, refresh_token=refresh_token,
                           expires_at=expires_at, refresh_cb=refresh_callback)
    
    date = datetime.datetime.today()
    print(client.activities(date))
    

    After my token expires, I get the following exceptions (traceback below):

    Traceback (most recent call last):
      File "/home/pete7863/.local/lib/python3.5/site-packages/requests_oauthlib/oauth2_session.py", line 395, in request
        http_method=method, body=data, headers=headers)
      File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 198, in add_token
        raise TokenExpiredError()
    oauthlib.oauth2.rfc6749.errors.TokenExpiredError: (token_expired) 
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/home/pete7863/influxdbmylife/fitbit/main.py", line 102, in <module>
        write_day(date, client, fb.activities(date))
      File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/utils.py", line 38, in _curried
        return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
      File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/api.py", line 348, in _COLLECTION_RESOURCE
        return self.make_request(url, data)
      File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/api.py", line 256, in make_request
        response = self.client.make_request(*args, **kwargs)
      File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/api.py", line 96, in make_request
        **kwargs
      File "/home/pete7863/.local/lib/python3.5/site-packages/fitbit/api.py", line 68, in _request
        response = self.session.request(method, url, **kwargs)
      File "/home/pete7863/.local/lib/python3.5/site-packages/requests_oauthlib/oauth2_session.py", line 408, in request
        self.auto_refresh_url, auth=auth, **kwargs
      File "/home/pete7863/.local/lib/python3.5/site-packages/requests_oauthlib/oauth2_session.py", line 374, in refresh_token
        self.token = self._client.parse_request_body_response(r.text, scope=self.scope)
      File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/clients/base.py", line 415, in parse_request_body_response
        self.token = parse_token_response(body, scope=scope)
      File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 425, in parse_token_response
        validate_token_parameters(params)
      File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/parameters.py", line 432, in validate_token_parameters
        raise_from_error(params.get('error'), params)
      File "/home/pete7863/.local/lib/python3.5/site-packages/oauthlib/oauth2/rfc6749/errors.py", line 405, in raise_from_error
        raise cls(**kwargs)
    oauthlib.oauth2.rfc6749.errors.InvalidClientError: (invalid_client)
    

    Is there something simple I am missing? I've tried to strip down my code to the barest form. Any help is greatly appreciated!

  15. pete7863 commented on Jun 29, 2019

    @pete7863

    I think I figured my issue out. For some reason I was initializing with the user_id and the client_id as the first two parameters when initializing the Fitbit client. Once I fixed this issue, my callback started working properly.

  16. MasonV commented on Sep 30, 2019

    @MasonV

    My issue with refresh_token is that it is worded as something that is automatic, but it is never called within the code itself. The token_updater also doesn't exist or at least I couldn't figure out how to make it exist through passing some sort of function originally.

    So my solution was to modify the source code and call it myself.

    `
    def refresh_token(self):

    token = {}
    
    token = self.session.refresh_token(
    
        self.refresh_token_url,
    
        auth=HTTPBasicAuth(self.client_id, self.client_secret)
    
    return token
    

    `

    In my main code I create the fitbit object with stored credentials and try to access data. If that doesn't work then I directly call the refresh_token() function. And if THAT doesn't work then I go through the entire reauthorization process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions