#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