frankie567/httpx-oauth

GitHub OAuth responses are urlencoded, not JSON

serain opened this issue · 2 comments

First of all, thanks for this. It looks really great and I'm hoping it can save me a lot of time.

I'm running into an issue trying to get OAuth flow working with GitHub. I'm getting a 500 on the callback. It looks like an issue with httpx_oauth client.get_access_token

INFO:     127.0.0.1:51913 - "GET /auth/github/callback?code=<MY_CODE>&state=<MY_STATE> HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 385, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/fastapi/applications.py", line 171, in __call__
    await super().__call__(scope, receive, send)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/starlette/applications.py", line 102, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/starlette/routing.py", line 550, in __call__
    await route.handle(scope, receive, send)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/starlette/routing.py", line 227, in handle
    await self.app(scope, receive, send)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/starlette/routing.py", line 41, in app
    response = await func(request)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/fastapi/routing.py", line 186, in app
    solved_result = await solve_dependencies(
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/fastapi/dependencies/utils.py", line 537, in solve_dependencies
    solved = await call(**sub_values)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/httpx_oauth/integrations/fastapi.py", line 40, in __call__
    access_token = await self.client.get_access_token(code, redirect_url)
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/httpx_oauth/oauth2.py", line 121, in get_access_token
    data = cast(Dict[str, Any], response.json())
  File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/httpx/_models.py", line 854, in json
    return jsonlib.loads(self.text, **kwargs)
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/__init__.py", line 357, in loads
    return _default_decoder.decode(s)
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/opt/python@3.8/Frameworks/Python.framework/Versions/3.8/lib/python3.8/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)```

Diving into httpx_oauth, it seems the issue is that GitHub is returning form-data instead of a json response:

b'access_token=<ACCESS_TOKEN>&scope=user%3Aemail&token_type=bearer'

so this line in oauth.py is throwing a JSONDecodeError:

data = cast(Dict[str, Any], response.json())

This can be fixed by adding the headers={"Accept": "application/json"} headers when fetching the access token, as per https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#web-application-flow

I then run into another problem down the line, as there's no expires_at key in GitHub's response:

File "/Users/user/.local/share/virtualenvs/console-HZgljEfn/lib/python3.8/site-packages/fastapi_users/router/oauth.py", line 102, in callback
    expires_at=token["expires_at"],
KeyError: 'expires_at'

Ok, I can reproduce the issue. It seems that GitHub is returning urlencoded responses instead of JSON (🙄). I'll transfer this issue to httpx-oauth. Thank you for the feeback!