fief-dev / fief

Users and authentication management SaaS
https://www.fief.dev
Other
538 stars 44 forks source link

Internal Server Error (500) when validating access token on service side #192

Closed Kh-Oleg closed 1 year ago

Kh-Oleg commented 1 year ago

Describe the bug

When I access REST API with the access token, provided by Fief, the service responds with 500 Internal Server Error from the token validation functions.

To Reproduce

You need to setup some sample service, using Fief as Identity provider. In my case Fief runs at http://localhost:8001, my service runs at http://localhost:8000.

  1. Create a user
  2. Create a permission
  3. Generate access token
  4. Call service API with the token, provided at step 3. REST API must require the permission from step 2.
  5. See the stack trace below

Expected behavior

Token validation functions shall not raise exceptions in case of a valid token.

Configuration

Additional context

Stack trace:

INFO:     127.0.0.1:60174 - "POST /api/castle/create HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/dist-packages/httpx/_transports/default.py", line 60, in map_httpcore_exceptions
    yield
  File "/usr/local/lib/python3.11/dist-packages/httpx/_transports/default.py", line 353, in handle_async_request
    resp = await self._pool.handle_async_request(req)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/httpcore/_async/connection_pool.py", line 212, in handle_async_request
    raise UnsupportedProtocol(
httpcore.UnsupportedProtocol: Request URL has an unsupported protocol 'localhost://'.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.11/dist-packages/uvicorn/protocols/http/httptools_impl.py", line 436, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/fastapi/applications.py", line 276, in __call__
    await super().__call__(scope, receive, send)
  File "/usr/local/lib/python3.11/dist-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.11/dist-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/usr/local/lib/python3.11/dist-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/usr/local/lib/python3.11/dist-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/usr/local/lib/python3.11/dist-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/usr/local/lib/python3.11/dist-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/usr/local/lib/python3.11/dist-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.11/dist-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.11/dist-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.11/dist-packages/starlette/routing.py", line 66, in app
    response = await func(request)
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/fastapi/routing.py", line 227, in app
    solved_result = await solve_dependencies(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/fastapi/dependencies/utils.py", line 592, in solve_dependencies
    solved_result = await solve_dependencies(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/fastapi/dependencies/utils.py", line 621, in solve_dependencies
    solved = await call(**sub_values)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "<makefun-gen-0>", line 2, in _authenticated
  File "/usr/local/lib/python3.11/dist-packages/fief_client/integrations/fastapi.py", line 175, in _authenticated
    info = await result
           ^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/fief_client/client.py", line 892, in validate_access_token
    jwks = await self._get_jwks()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/fief_client/client.py", line 1013, in _get_jwks
    await self._get_openid_configuration(), "jwks_uri"
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/contextlib.py", line 222, in __aexit__
    await self.gen.athrow(typ, value, traceback)
  File "/usr/local/lib/python3.11/dist-packages/fief_client/client.py", line 995, in _get_httpx_client
    yield client
  File "/usr/local/lib/python3.11/dist-packages/fief_client/client.py", line 1003, in _get_openid_configuration
    response = await client.send(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/httpx/_client.py", line 1620, in send
    response = await self._send_handling_auth(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/httpx/_client.py", line 1648, in _send_handling_auth
    response = await self._send_handling_redirects(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/httpx/_client.py", line 1685, in _send_handling_redirects
    response = await self._send_single_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/httpx/_client.py", line 1722, in _send_single_request
    response = await transport.handle_async_request(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/dist-packages/httpx/_transports/default.py", line 352, in handle_async_request
    with map_httpcore_exceptions():
  File "/usr/lib/python3.11/contextlib.py", line 155, in __exit__
    self.gen.throw(typ, value, traceback)
  File "/usr/local/lib/python3.11/dist-packages/httpx/_transports/default.py", line 77, in map_httpcore_exceptions
    raise mapped_exc(message) from exc
httpx.UnsupportedProtocol: Request URL has an unsupported protocol 'localhost://'.

I also noticed a non-expected thing in the decoded access token - the 'iss' field contains https://localhost:8001, while I run Fief at http://localhost:8001.

{
 alg: "RS256",
 kid: "G_hwBL1iy-VvSr0uSMvV39xmvNss2ogkZWl7-3gJBEs"
}.
{
 aud: [
  "bla-bla-bla"
 ],
 azp: "bla-bla-bla",
 exp: 1684250951,
 iat: 1684164551,
 iss: "https://localhost:8001",
 permissions: [
  "createCastle"
 ],
 scope: "openid",
 sub: "e4718be2-06b4-4803-a73a-67da56e5c3d0"
}.
[signature]

Since I initialize FiefClient with http://localhost:8001, perhaps, it cannot match Base URLs from the Python code and from the token.

frankie567 commented 1 year ago

There is something strange in the stack trace:

httpx.UnsupportedProtocol: Request URL has an unsupported protocol 'localhost://'.

Are you sure you set the Fief URL correctly on the client?

Kh-Oleg commented 1 year ago

The bug seems to be gone after rebuilding docker images. Sorry for disturbance.

Kh-Oleg commented 1 year ago

A follow-up remark: I was indeed initializing FiefClient incorrectly: fief = FiefAsync('localhost:8001', client_id, client_secret) instead of fief = FiefAsync('http://localhost:8001', client_id, client_secret), my bad.

However, since FiefAsync requires protocol to be explicitly specified, it would be nice, if FiefAsync would raise an exception immediately, when constructing the object.