Включить авторизацию в oauth2session для запросов-oauthlib

#python #fitbit #requests-oauthlib

#python #fitbit #запросы-oauthlib

Вопрос:

При чтении различных документов кажется, что поставщикам oauth2 необязательно требуется авторизация для запросов токенов обновления. Я работаю с API FitBit, который, по-видимому, требует авторизации.

Я следую приведенным здесь инструкциям по обновлению токена с requests-oauthlib помощью: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#refreshing-tokens

Некоторый установочный код (не то, что я использую, но вы поняли идею:

 >>> token = {
...     'access_token': 'eswfld123kjhn1v5423',
...     'refresh_token': 'asdfkljh23490sdf',
...     'token_type': 'Bearer',
...     'expires_in': '-30',     # initially 3600, need to be updated by you
...  }
>>> client_id = r'foo'
>>> refresh_url = 'https://provider.com/token'
>>> protected_url = 'https://provider.com/secret'

>>> # most providers will ask you for extra credentials to be passed along
>>> # when refreshing tokens, usually for authentication purposes.
>>> extra = {
...     'client_id': client_id,
...     'client_secret': r'potato',
... }

>>> # After updating the token you will most likely want to save it.
>>> def token_saver(token):
...     # save token in database / session
 
 from requests_oauthlib import OAuth2Session
client = OAuth2Session(client_id, token=token, auto_refresh_url=refresh_url,
         auto_refresh_kwargs=extra, token_updater=token_saver)
r = client.get(protected_url)
 

Тем не менее, с этим звонком я получаю:

 MissingTokenError: (missing_token) Missing access token parameter.
 

Я знаю, что срок действия моего токена истек, но почему обновление не работает?

Ответ №1:

В этом отношении библиотека повреждена. См. #379.

Вы можете обойти это примерно так:

 def _wrap_refresh(func):
    def wrapper(*args, **kwargs):
        kwargs['auth'] = (client_id, client_secret)
        kwargs.pop('allow_redirects', None)
        return func(*args, **kwargs)
    return wrapper

client = OAuth2Session(client_id, token=token,
                       auto_refresh_url=refresh_url, 
                       token_updater=token_saver)
client.refresh_token = _wrap_refresh(client.refresh_token)
 

Ответ №2:

Редактировать: ниже все еще есть некоторая полезная информация, но переопределение функции аутентификации означает, что мои фактические запросы API теперь терпят неудачу (т. Е. Ниже неверный ответ) Я не уверен, как я получил один запрос, который я пытался выполнить в прошлый раз. Возможно, он просто вернул ошибку (в формате json), а не выдал ошибку, и я просто предположил, что отсутствие возникшей ошибки означает, что она действительно работает. Смотрите Правильное обходное решение от OrangeDog (пока библиотека не будет исправлена).

Что ж, я проверил ответ сервера FitBit непосредственно перед тем, как был выдан MissingTokenError. Оказывается, я получал сообщение об ошибке, в котором говорилось, что аутентификация была неверной.

Возможно, это само по себе полезный момент, на котором стоит остановиться на секунду. Ошибка MissingTokenError, по-видимому, возникает, когда ответ не содержит ожидаемого токена. Если вы сможете выполнить отладку и более внимательно изучить ответ, вы можете обнаружить, что сервер предоставляет немного больше деталей относительно того, почему ваш запрос был искажен. Я пошел к месту ошибки и добавил оператор печати. Это позволило мне увидеть сообщение JSON от FitBit. В любом случае, этот подход может быть полезен для других, получающих MissingTokenError.

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

В любом случае, после некоторой дальнейшей отладки аутентификация не была установлена. Кроме того, мой идентификатор клиента и секрет были опубликованы в теле (возможно, это не было проблемой). В примерах FitBit идентификатор клиента и секрет не были опубликованы в теле, а были переданы через аутентификацию. Итак, мне нужно, чтобы клиент передал аутентификацию в FitBit.

Итак, тогда вопрос заключался в том, как мне пройти аутентификацию. В настоящее время документация по этому вопросу отсутствует. Однако, просмотрев объект сеанса, я обнаружил свойство .auth, которое было установлено, и ссылку на проблему (# 278). В этой проблеме предусмотрен обходной путь (показан ниже с моим кодом) для ручной настройки аутентификации: https://github.com/requests/requests-oauthlib/issues/278

Обратите внимание, сеанс oauth наследуется от сеанса запросов, поэтому для тех, кто действительно хорошо знает запросы, это может быть очевидно …

В любом случае, решением было просто установить параметр auth после инициализации сеанса. Поскольку FitBit не нужны идентификатор клиента и секрет в теле, я также удалил передачу дополнительных функций (опять же, это может быть незначительной проблемой и на самом деле не влияет):

 import os
import json
from requests.auth import HTTPBasicAuth
from requests_oauthlib import OAuth2Session

client_id = ""
client_secret = ""

with open("tokens.json", "r") as read_file:
    token = json.load(read_file)       

save_file_path = os.path.abspath('tokens.json')

refresh_url = 'https://api.fitbit.com/oauth2/token'

def token_saver(token):    
    with open(save_file_path, "w") as out_file:
        json.dump(token, out_file, indent = 6) 

#Note, I've removed the 'extras' input
client = OAuth2Session(client_id, token=token, auto_refresh_url=refresh_url, token_updater=token_saver)
#This was the magic line ...
auth = HTTPBasicAuth(client_id, client_secret)
client.auth = auth

url = 'https://api.fitbit.com/1.2/user/-/sleep/date/2021-01-01-2021-01-23.json'
wtf = client.get(url)
 

Хорошо, я думаю, что я скопировал этот код правильно, в настоящее время с моей стороны немного беспорядок. Ключевой частью была просто строка:

 client.auth = auth
 

После того, как клиент был инициирован.

Обратите внимание, мой токен содержит expires_at поле. Я не думаю, что сеанс обрабатывает с expires_in точки зрения точного времени. Другими словами, я думаю expires_in , что обновление происходит только в том случае, если его значение меньше 0. Я не думаю, что он смотрит на время создания объекта и запускает таймер или устанавливает свойство, чтобы узнать, что expires_in относится к. expires_at Поле, с другой стороны, похоже, предоставляет (я думаю) поле, которое проверяется, чтобы убедиться, что токен не истек во время запроса, поскольку expires_at это реальное, не относительное время. Вот мой токен dict (с поддельными токенами и идентификатором пользователя):

 {'access_token': '1234',
 'expires_in': 28800,
 'refresh_token': '5678',
 'scope': ['heartrate',
  'profile',
  'settings',
  'nutrition',
  'location',
  'weight',
  'activity',
  'sleep',
  'social'],
 'token_type': 'Bearer',
 'user_id': 'abcd',
 'expires_at': 1611455442.4566112}
 

Комментарии:

1. Существует также это, которое может в какой-то момент предложить решение. Я не мог по-настоящему понять предлагаемые решения, и я не возражаю против отправки аутентификации при каждом вызове, что, по-видимому, делает мое решение… github.com/requests/requests-oauthlib/issues/379