Skip to content

Conversation

@dabla
Copy link
Contributor

@dabla dabla commented Dec 13, 2025

This PR is also related to issue #59359.

Problem is during the logout route defined in the Keycloak provider is that there the refresh_token is also called directly on the Keycloak client. When a KeycloakPostError is raised when the refresh token fails, the error will also be raised and this will lead to an HTTP 500 Internal Server Error in the API server.

So I extracted a refresh_token method from the refresh_user method in the KeycloakAuthManager so the refresh_token method is guarded and thus catches the KeycloakPostError, that way I can also re-use that method in the logout route so that when an exception occurs the API server doesn't return an HTTP 500 Internal Server Error.

INFO:     172.31.60.49:0 - "GET /auth/logout HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/usr/local/lib/python3.13/site-packages/uvicorn/protocols/http/httptools_impl.py", line 409, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        self.scope, self.receive, self.send
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/fastapi/applications.py", line 1082, in __call__
    await super().__call__(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/applications.py", line 113, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/errors.py", line 186, in __call__
    raise exc
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/gzip.py", line 29, in __call__
    await responder(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/gzip.py", line 130, in __call__
    await super().__call__(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/gzip.py", line 46, in __call__
    await self.app(scope, receive, self.send_with_compression)
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/cors.py", line 85, in __call__
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/base.py", line 189, in __call__
    raise app_exc
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/base.py", line 144, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/exceptions.py", line 63, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "/usr/local/lib/python3.13/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/usr/local/lib/python3.13/site-packages/starlette/routing.py", line 716, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/routing.py", line 736, in app
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/routing.py", line 462, in handle
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/fastapi/applications.py", line 1082, in __call__
    await super().__call__(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/applications.py", line 113, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/errors.py", line 186, in __call__
    raise exc
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/usr/local/lib/python3.13/site-packages/starlette/middleware/exceptions.py", line 63, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "/usr/local/lib/python3.13/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/usr/local/lib/python3.13/site-packages/starlette/routing.py", line 716, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/routing.py", line 736, in app
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/routing.py", line 290, in handle
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/routing.py", line 78, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File "/usr/local/lib/python3.13/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "/usr/local/lib/python3.13/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/usr/local/lib/python3.13/site-packages/starlette/routing.py", line 75, in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/fastapi/routing.py", line 308, in app
    raw_response = await run_endpoint_function(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<3 lines>...
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/fastapi/routing.py", line 221, in run_endpoint_function
    return await run_in_threadpool(dependant.call, **values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/starlette/concurrency.py", line 38, in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/anyio/to_thread.py", line 61, in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        func, args, abandon_on_cancel=abandon_on_cancel, limiter=limiter
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/local/lib/python3.13/site-packages/anyio/_backends/_asyncio.py", line 2525, in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/anyio/_backends/_asyncio.py", line 986, in run
    result = context.run(func, *args)
  File "/usr/local/lib/python3.13/site-packages/airflow/providers/keycloak/auth_manager/routes/login.py", line 91, in logout
    tokens = client.refresh_token(user.refresh_token)
  File "/usr/local/lib/python3.13/site-packages/keycloak/keycloak_openid.py", line 410, in refresh_token
    return raise_error_from_response(data_raw, KeycloakPostError)
  File "/usr/local/lib/python3.13/site-packages/keycloak/exceptions.py", line 195, in raise_error_from_response
    raise error(
    ...<3 lines>...
    )
keycloak.exceptions.KeycloakPostError: 400: b'{"error":"invalid_grant","error_description":"Token is not active"}'
2025-12-13T08:57:45.315734Z [warning  ] KeycloakPostError encountered during token refresh. Suppressing the exception and returning None. [airflow.providers.keycloak.auth_manager.keycloak_auth_manager] loc=keycloak_manager.py:37
Traceback (most recent call last):
  File "/usr/local/airflow/plugins/infrabel/auth/keycloak_manager.py", line 32, in refresh_user
    tokens = client.refresh_token(user.refresh_token)
  File "/usr/local/lib/python3.13/site-packages/keycloak/keycloak_openid.py", line 410, in refresh_token
    return raise_error_from_response(data_raw, KeycloakPostError)
  File "/usr/local/lib/python3.13/site-packages/keycloak/exceptions.py", line 195, in raise_error_from_response
    raise error(
    ...<3 lines>...
    )
keycloak.exceptions.KeycloakPostError: 400: b'{"error":"invalid_grant","error_description":"Token is not active"}'

^ Add meaningful description above
Read the Pull Request Guidelines for more information.
In case of fundamental code changes, an Airflow Improvement Proposal (AIP) is needed.
In case of a new dependency, check compliance with the ASF 3rd Party License Policy.
In case of backwards incompatible changes please leave a note in a newsfragment file, named {pr_number}.significant.rst or {issue_number}.significant.rst, in airflow-core/newsfragments.

…ostError doesn't propagate to API server also which leads to Internal Server Error
@shahar1
Copy link
Contributor

shahar1 commented Dec 13, 2025

CI is unhappy :(

@dabla dabla marked this pull request as draft December 13, 2025 12:31
@dabla
Copy link
Contributor Author

dabla commented Dec 13, 2025

I've put this PR in draft because I still need to add a test for the logout route

@dabla
Copy link
Contributor Author

dabla commented Dec 13, 2025

CI is unhappy :(

Yes it's still WIP but I wanted to already start the PR so we know it's being handled ;-)

…en refresh_token is being invoked in logout route
@dabla dabla marked this pull request as ready for review December 13, 2025 12:37
@dabla
Copy link
Contributor Author

dabla commented Dec 13, 2025

Test added

Copy link
Contributor

@vincbeck vincbeck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One nit, overall, looks good

dabla and others added 2 commits December 15, 2025 17:27
…/keycloak_auth_manager.py

Co-authored-by: Vincent <97131062+vincbeck@users.noreply.github.com>
@dabla dabla changed the title Fix logout route in Keycloak provider so a KeycloakostError doesn't leads to Internal Server Error in API server Fix logout route in Keycloak provider so a KeycloakostError doesn't lead to Internal Server Error in API server Dec 15, 2025
@dabla dabla requested a review from vincbeck December 16, 2025 06:40
Copy link
Member

@potiuk potiuk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NICE!

@potiuk potiuk merged commit 5ee00b3 into apache:main Dec 16, 2025
82 checks passed
FoxHelms pushed a commit to FoxHelms/airflow that referenced this pull request Dec 17, 2025
…ead to Internal Server Error in API server (apache#59382)

* refactor: Fix logout route in Keycloak provider also so the KeycloakPostError doesn't propagate to API server also which leads to Internal Server Error

* refactor: Fixed static checks

* refactor: Fixed refresh_token invocations

* refactor: Must call refresh_user in refresh route

* refactor: refresh_token must always return a dict

* refactor: Added test when keycloak client raises KeycloakPostError when refresh_token is being invoked in logout route

* refactor: Fixed some additional static checks

* refactor: Refactored refresh_user

* refactor: Reformatted imports

* refactor: Fixed mocking in refresh test

* refactor: Removed unused mocking of keycloak client in test_refresh_token

* refactor: Fixed mock get_auth_manager and added missing import KeycloakAuthManagerUser

* refactor: Refresh token route calls refresh_user instead of refresh_token

* refactor: Changed assert on refresh user

* Update providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py

Co-authored-by: Vincent <97131062+vincbeck@users.noreply.github.com>

* refactor: Fixed calls to refresh_tokens instead of refresh_token

---------

Co-authored-by: Vincent <97131062+vincbeck@users.noreply.github.com>
TempestShaw pushed a commit to TempestShaw/airflow that referenced this pull request Dec 24, 2025
…ead to Internal Server Error in API server (apache#59382)

* refactor: Fix logout route in Keycloak provider also so the KeycloakPostError doesn't propagate to API server also which leads to Internal Server Error

* refactor: Fixed static checks

* refactor: Fixed refresh_token invocations

* refactor: Must call refresh_user in refresh route

* refactor: refresh_token must always return a dict

* refactor: Added test when keycloak client raises KeycloakPostError when refresh_token is being invoked in logout route

* refactor: Fixed some additional static checks

* refactor: Refactored refresh_user

* refactor: Reformatted imports

* refactor: Fixed mocking in refresh test

* refactor: Removed unused mocking of keycloak client in test_refresh_token

* refactor: Fixed mock get_auth_manager and added missing import KeycloakAuthManagerUser

* refactor: Refresh token route calls refresh_user instead of refresh_token

* refactor: Changed assert on refresh user

* Update providers/keycloak/src/airflow/providers/keycloak/auth_manager/keycloak_auth_manager.py

Co-authored-by: Vincent <97131062+vincbeck@users.noreply.github.com>

* refactor: Fixed calls to refresh_tokens instead of refresh_token

---------

Co-authored-by: Vincent <97131062+vincbeck@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants