Token refresh failed when using AsyncOAuth2Client with client credentials
radiophysicist opened this issue · comments
Describe the bug
In case of using AsyncOAuth2Client with client credential I'm obtaining a TypeError exception after token expiration when making POST request.
Error Stacks
{
"exc_type": "TypeError",
"exc_value": "Invalid type for url. Expected str or httpx.URL, got <class 'NoneType'>: None",
"frames": [
...
{
"filename": "/app/support_integration/api_client/support.py",
"line": "",
"lineno": 55,
"locals": {
"data_inner": "\"{'personalNumber': 1951254, 'initialTemplateId': 18184, 'fieldQuestionAnswer': [\"+73",
"file_size": "7557",
"initial_template_id": "18184",
"log": "\"<BoundLoggerFilteringAtNotset(context={'logger_name': 'support_integrati\"+774",
"personal_number": "1951254",
"request_data": "'{\\'objectBinding\\': \\'{\"personalNumber\": 1951254, \"initialTemplateId\": 18184, \"fiel'+238",
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"template_question_answers": "[SupportAPIQA(question='Укажите номер магазина SAP/Торг.ERP:', answer='4063')]",
"url": "https://support.stage.api.xxx.xxx/api/external/utp/appeal",
"xls_file": "<tempfile._TemporaryFileWrapper object at 0x7f1525f29e90>"
},
"name": "create_ticket"
},
{
"filename": "/venv/lib/python3.11/site-packages/httpx/_client.py",
"line": "",
"lineno": 1892,
"locals": {
"auth": "<httpx._client.UseClientDefault object at 0x7f152ab926d0>",
"content": "None",
"cookies": "None",
"data": "'{\\'objectBinding\\': \\'{\"personalNumber\": 1951254, \"initialTemplateId\": 18184, \"fiel'+238",
"extensions": "None",
"files": "{'file': <tempfile._TemporaryFileWrapper object at 0x7f1525f29e90>}",
"follow_redirects": "<httpx._client.UseClientDefault object at 0x7f152ab926d0>",
"headers": "None",
"json": "None",
"params": "None",
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"timeout": "10",
"url": "https://support.stage.api.xxx.xxx/api/external/utp/appeal"
},
"name": "post"
},
{
"filename": "/venv/lib/python3.11/site-packages/authlib/integrations/httpx_client/oauth2_client.py",
"line": "",
"lineno": 86,
"locals": {
"__class__": "<class 'authlib.integrations.httpx_client.oauth2_client.AsyncOAuth2Client'>",
"auth": "<httpx._client.UseClientDefault object at 0x7f152ab926d0>",
"kwargs": "'{\\'content\\': None, \\'data\\': {\\'objectBinding\\': \\'{\"personalNumber\": 1951254, \"initia'+521",
"method": "POST",
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"url": "https://support.stage.api.xxx.xxx/api/external/utp/appeal",
"withhold_token": "False"
},
"name": "request"
},
{
"filename": "/venv/lib/python3.11/site-packages/authlib/integrations/httpx_client/oauth2_client.py",
"line": "",
"lineno": 116,
"locals": {
"access_token": "'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJVZFBBcjhTdmoyYURjQ21ES2l6NTAw'+1245",
"refresh_token": "None",
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"token": "\"{'access_token': 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJVZFBBcjhTdm\"+1409",
"url": "None"
},
"name": "ensure_active_token"
},
{
"filename": "/venv/lib/python3.11/site-packages/authlib/integrations/httpx_client/oauth2_client.py",
"line": "",
"lineno": 125,
"locals": {
"auth": "'<authlib.integrations.httpx_client.oauth2_client.OAuth2ClientAuth object at 0x7f'+11",
"body": "grant_type=client_credentials&scope=email",
"headers": "\"{'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencode\"+17",
"kwargs": "{}",
"method": "POST",
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"url": "None"
},
"name": "_fetch_token"
},
{
"filename": "/venv/lib/python3.11/site-packages/httpx/_client.py",
"line": "",
"lineno": 1892,
"locals": {
"auth": "'<authlib.integrations.httpx_client.oauth2_client.OAuth2ClientAuth object at 0x7f'+11",
"content": "None",
"cookies": "None",
"data": "{'grant_type': 'client_credentials', 'scope': 'email'}",
"extensions": "None",
"files": "None",
"follow_redirects": "<httpx._client.UseClientDefault object at 0x7f152ab926d0>",
"headers": "\"{'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencode\"+17",
"json": "None",
"params": "None",
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"timeout": "<httpx._client.UseClientDefault object at 0x7f152ab926d0>",
"url": "None"
},
"name": "post"
},
{
"filename": "/venv/lib/python3.11/site-packages/authlib/integrations/httpx_client/oauth2_client.py",
"line": "",
"lineno": 90,
"locals": {
"__class__": "<class 'authlib.integrations.httpx_client.oauth2_client.AsyncOAuth2Client'>",
"auth": "'<authlib.integrations.httpx_client.oauth2_client.OAuth2ClientAuth object at 0x7f'+11",
"kwargs": "\"{'content': None, 'data': {'grant_type': 'client_credentials', 'scope': 'email'}\"+342",
"method": "POST",
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"url": "None",
"withhold_token": "False"
},
"name": "request"
},
{
"filename": "/venv/lib/python3.11/site-packages/httpx/_client.py",
"line": "",
"lineno": 1561,
"locals": {
"auth": "'<authlib.integrations.httpx_client.oauth2_client.OAuth2ClientAuth object at 0x7f'+11",
"content": "None",
"cookies": "None",
"data": "{'grant_type': 'client_credentials', 'scope': 'email'}",
"extensions": "None",
"files": "None",
"follow_redirects": "<httpx._client.UseClientDefault object at 0x7f152ab926d0>",
"headers": "\"{'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencode\"+17",
"json": "None",
"method": "POST",
"params": "None",
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"timeout": "<httpx._client.UseClientDefault object at 0x7f152ab926d0>",
"url": "None"
},
"name": "request"
},
{
"filename": "/venv/lib/python3.11/site-packages/httpx/_client.py",
"line": "",
"lineno": 345,
"locals": {
"content": "None",
"cookies": "None",
"data": "{'grant_type': 'client_credentials', 'scope': 'email'}",
"extensions": "None",
"files": "None",
"headers": "\"{'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencode\"+17",
"json": "None",
"method": "POST",
"params": "None",
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"timeout": "<httpx._client.UseClientDefault object at 0x7f152ab926d0>",
"url": "None"
},
"name": "build_request"
},
{
"filename": "/venv/lib/python3.11/site-packages/httpx/_client.py",
"line": "",
"lineno": 375,
"locals": {
"self": "'<support_integration.api_client.support.SupportAPIClient object at 0x7f1'+10",
"url": "None"
},
"name": "_merge_url"
},
{
"filename": "/venv/lib/python3.11/site-packages/httpx/_urls.py",
"line": "",
"lineno": 119,
"locals": {
"kwargs": "{}",
"self": "<repr-error \"'URL' object has no attribute '_uri_reference'\">",
"url": "None"
},
"name": "__init__"
}
],
"is_cause": false,
"syntax_error": null
}
To Reproduce
My code simplified:
class BaseAPIClient(AsyncOAuth2Client):
"""
Base class for client of API published via integration platform.
Authorization is OAUTH 2.0 with client ID
"""
def __init__(
self,
client_id: str,
client_secret: str,
oauth2_token_url: str,
api_base_url: str,
):
super().__init__(
client_id=client_id,
client_secret=client_secret,
scope="email",
)
self._token_url = oauth2_token_url
self._api_base_url = api_base_url
async def start(self) -> None:
if not self.token:
try:
await self.fetch_token(url=self._token_url)
except Exception:
self._handle_exception(sys.exc_info(), in_auth=True)
self.headers["Authorization"] = f"Bearer {self.token['access_token']}"
logger.info("Authorized successfully", token_url=self._token_url)
def _handle_exception(self, exc_info: tuple, in_auth: bool = False) -> None:
exc_type, exc = exc_info[:2]
if exc_type == OAuthError or in_auth:
msg = "Failed to authorize on API platform"
logger.error(msg, token_url=self._token_url, exc_info=exc_info)
raise BaseAPIClientError(msg) from exc
msg = "Unexpected API error"
logger.error(msg, exc_info=exc_info)
raise BaseAPIClientError(msg) from exc
cc = SupportAPIClient(
client_id=settings.api_reg_client_id,
client_secret=settings.api_reg_client_secret,
oauth2_token_url=settings.api_reg_token_url,
)
await cc.start()
# Wait some time for token to expire
await cc.post(...) # <-- Crashes
Expected behavior
Expected the token to be refreshed / re-obtained and than request complete successfully
Environment:
- OS: Ubuntu linux in docker container
- Python Version: 3.10
- Authlib Version: 1.3.0
Additional context
The problem seems to originate from line:
authlib/authlib/oauth2/client.py
Line 275 in 610622e
In case of client credentials there are both no
token_endpoint
in metadata and url
in token.Token data:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJVZFBBcjhTdmoyYURjQ21ES2l6NTAwUFBmTnB0X0hRdE5FWldLdWlKLV9rIn0.eyJleHAiOjE3MTU3NjMxMDYsImlhdCI6MTcxNTc2MjIwNiwianRpIjoiMGNjNTg0ZTAtM2QyMy00YWQ1LTg2MDItMGU1NWVlYmNjN2Y5IiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay54NS5ydS9yZWFsbXMvQ1NJUCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI3YTA3MjA1OC0zOGNiLTQ3YTAtYmY3Yi1jMzNjODJiYmU2MzgiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJwcm9kdWN0X3JlcHJlc2VudGF0aW9uX3N5c3RlbSIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1jc2lwIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SG9zdCI6IjEwMC4xMjYuMzIuMTgzIiwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LXByb2R1Y3RfcmVwcmVzZW50YXRpb25fc3lzdGVtIiwiY2xpZW50QWRkcmVzcyI6IjEwMC4xMjYuMzIuMTgzIiwiY2xpZW50X2lkIjoicHJvZHVjdF9yZXByZXNlbnRhdGlvbl9zeXN0ZW0ifQ.g8cwDaPrS-ya1CxwgqUq3pKYMti2Ruy-aO3hs9sopQAx1cPvAsEV_-S7l95BW3ww1crO6RcS57OuQhUZ97tenpOlHcGjBXspDwB8RRRgzkUGDIAUY4HX_IG5UEfqz4VFeuVqWMNvKZXtskFRRdqnBIFF_QJlTODZeT-FrIW0voui6iH-OPCrH4e-0E4xpnCSa4inxDBIK9rAoEE4A78EhakPqODXVxriykqtUnHrqf2FbR9l6zJ4KjpFpFUeiTHDQHgHSyP6MnbVnnewDSbMdmhsvbFDO0IwoSHd6pywuc8yvLH-3SXWM-QnZn4KXePZEFz_0l7d9UUHU5X0ypnZFg",
"expires_at": 1715763106,
"expires_in": 900,
"not-before-policy": 0,
"refresh_expires_in": 0,
"scope": "profile email",
"token_type": "Bearer"
}
Metadata:
{
"grant_type": "client_credentials"
}
Is it intended, a bug or my client misconfiguration?