Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token refresh failed when using AsyncOAuth2Client with client credentials #650

Open
radiophysicist opened this issue May 20, 2024 · 0 comments
Assignees
Labels

Comments

@radiophysicist
Copy link

radiophysicist commented May 20, 2024

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:

new_token = self.fetch_token(url, grant_type='client_credentials')

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants